diff options
author | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-04 16:46:31 +1000 |
---|---|---|
committer | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-06-04 16:46:31 +1000 |
commit | b435b8659eef0e8bc2910966d87b5b74b4cddbe2 (patch) | |
tree | 22194a911cf071766d99d7f9c1600889a6147ebb | |
parent | 6781ae52a41188e82a0354d4725a7c2718830e45 (diff) |
Move stuff over to using streams instead of iterators: much nicer!
8 files changed, 97 insertions, 322 deletions
diff --git a/src/main/java/au/id/zancanaro/javacheck/Generator.java b/src/main/java/au/id/zancanaro/javacheck/Generator.java index ac731de..5a8fec3 100644 --- a/src/main/java/au/id/zancanaro/javacheck/Generator.java +++ b/src/main/java/au/id/zancanaro/javacheck/Generator.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Random; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Generators are a way of producing random objects and their associated shrink @@ -170,7 +171,11 @@ public interface Generator<T> { return (random, size) -> this.generate(random, size).withShrinkStrategy(strategy); } - default Iterator<T> sample(Random random, int maxSize) { - return new GeneratorSampleIterator<>(this, random, maxSize); + default Stream<T> sample(Random random, int maxSize) { + return Stream.generate(() -> this.generate(random, maxSize).getValue()); + } + + default Stream<T> sample() { + return sample(new Random(), 100); } } diff --git a/src/main/java/au/id/zancanaro/javacheck/GeneratorSampleIterator.java b/src/main/java/au/id/zancanaro/javacheck/GeneratorSampleIterator.java deleted file mode 100644 index 6101d4a..0000000 --- a/src/main/java/au/id/zancanaro/javacheck/GeneratorSampleIterator.java +++ /dev/null @@ -1,30 +0,0 @@ -package au.id.zancanaro.javacheck; - -import java.util.Iterator; -import java.util.Random; - -class GeneratorSampleIterator<T> implements Iterator<T> { - private final Generator<T> generator; - private final Random random; - private final int maxSize; - private int size; - - public GeneratorSampleIterator(Generator<T> generator, Random random, int maxSize) { - this.generator = generator; - this.random = random; - this.maxSize = maxSize; - this.size = 0; - } - - @Override - public boolean hasNext() { - return true; - } - - @Override - public T next() { - return generator - .generate(random, size = Math.min(size + 1, maxSize)) - .getValue(); - } -} diff --git a/src/main/java/au/id/zancanaro/javacheck/Generators.java b/src/main/java/au/id/zancanaro/javacheck/Generators.java index 9730bac..619fe2d 100644 --- a/src/main/java/au/id/zancanaro/javacheck/Generators.java +++ b/src/main/java/au/id/zancanaro/javacheck/Generators.java @@ -1,7 +1,10 @@ package au.id.zancanaro.javacheck; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; @SuppressWarnings({"unused", "WeakerAccess"}) public final class Generators { @@ -29,7 +32,7 @@ public final class Generators { * removed */ public static <T> Generator<T> noShrink(Generator<T> gen) { - return gen.withShrinkStrategy(value -> Iterators.emptyIterator()); + return gen.withShrinkStrategy(value -> Stream.empty()); } @SafeVarargs @@ -52,23 +55,7 @@ public final class Generators { } private static ShrinkStrategy<Boolean> boolShrinkStrategy() { - return value -> new Iterator<Boolean>() { - boolean hasMore = value; - - @Override - public boolean hasNext() { - return hasMore; - } - - @Override - public Boolean next() { - if (hasMore) { - return (hasMore = false); - } else { - throw new NoSuchElementException("Boolean shrink tree exhausted"); - } - } - }; + return value -> (value ? Stream.of(false) : Stream.empty()); } public static Generator<Long> longInteger(long lower, long upper) { @@ -85,21 +72,19 @@ public final class Generators { } private static ShrinkStrategy<Long> longShrinkStrategy(final long bound) { - return value -> new Iterator<Long>() { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator<Long>(Long.MAX_VALUE, Spliterator.ORDERED) { long curr = value - bound; - @Override - public boolean hasNext() { - return curr != 0; - } - - @Override - public Long next() { - long prevCurr = curr; - curr = curr / 2; - return value - prevCurr; + public boolean tryAdvance(Consumer<? super Long> action) { + if (curr == 0) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static Generator<Integer> integer(int lower, int upper) { @@ -119,22 +104,21 @@ public final class Generators { return sized(size -> integer(0, size)); } - public static ShrinkStrategy<Integer> intShrinkStrategy(int bound) { - return value -> new Iterator<Integer>() { + private static ShrinkStrategy<Integer> intShrinkStrategy(final int bound) { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator<Integer>(Long.MAX_VALUE, Spliterator.ORDERED) { int curr = value - bound; @Override - public boolean hasNext() { - return curr != 0; - } - - @Override - public Integer next() { - int prevCurr = curr; - curr = curr / 2; - return value - prevCurr; + public boolean tryAdvance(Consumer<? super Integer> action) { + if (curr == 0) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static Generator<Double> doublePrecision(double lower, double upper) { @@ -150,22 +134,21 @@ public final class Generators { return sized(size -> doublePrecision(-size, size)); } - public static ShrinkStrategy<Double> doubleShrinkStrategy(double bound, double epsilon) { - return value -> new Iterator<Double>() { + private static ShrinkStrategy<Double> doubleShrinkStrategy(final double bound, double epsilon) { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator<Double>(Long.MAX_VALUE, Spliterator.ORDERED) { double curr = value - bound; @Override - public boolean hasNext() { - return Math.abs(curr) > epsilon; - } - - @Override - public Double next() { - double prevCurr = curr; - curr = curr / 2; - return value - prevCurr; + public boolean tryAdvance(Consumer<? super Double> action) { + if (Math.abs(curr) < epsilon) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static <T> Generator<List<T>> listOf(Generator<T> gen, int minElements, int maxElements) { diff --git a/src/main/java/au/id/zancanaro/javacheck/Iterators.java b/src/main/java/au/id/zancanaro/javacheck/Iterators.java deleted file mode 100644 index 3f365eb..0000000 --- a/src/main/java/au/id/zancanaro/javacheck/Iterators.java +++ /dev/null @@ -1,179 +0,0 @@ -package au.id.zancanaro.javacheck; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.function.Function; -import java.util.function.Predicate; - -public final class Iterators { - private Iterators() { - } - - public static <T> RangeIterator<T> rangeIterator(int countTo, Function<Integer,T> fn) { - return new RangeIterator<>(countTo, fn); - } - - private static class RangeIterator<T> implements Iterator<T> { - private final Function<Integer, T> action; - private final int countTo; - private int index = 0; - - public RangeIterator(int countTo, Function<Integer, T> action) { - this.countTo = countTo; - this.action = action; - } - - @Override - public boolean hasNext() { - return index < countTo; - } - - @Override - public T next() { - return action.apply(index++); - } - } - - public static <T> FlattenIterator<T> flatten(Iterator<Iterator<T>> iterators) { - return new FlattenIterator<>(iterators); - } - public static class FlattenIterator<T> implements Iterator<T> { - private final Iterator<Iterator<T>> iterators; - - private Iterator<T> current; - - public FlattenIterator(Iterator<Iterator<T>> iterators) { - this.current = Iterators.emptyIterator(); - this.iterators = iterators; - } - - private Iterator<T> getCurrent() { - while (!current.hasNext() && iterators.hasNext()) { - current = iterators.next(); - } - return current; - } - - @Override - public boolean hasNext() { - return getCurrent().hasNext(); - } - @Override - public T next() { - return getCurrent().next(); - } - - } - - public static <T> ConcatIterator<T> concat(Iterator<T> left, Iterator<T> right) { - return new ConcatIterator<>(left, right); - } - public static class ConcatIterator<T> implements Iterator<T> { - private final Iterator<T> left; - - private final Iterator<T> right; - - public ConcatIterator(Iterator<T> left, Iterator<T> right) { - this.left = left; - this.right = right; - } - - @Override - public boolean hasNext() { - return left.hasNext() || right.hasNext(); - } - @Override - public T next() { - if (left.hasNext()) { - return left.next(); - } else { - return right.next(); - } - } - - } - - @SuppressWarnings("WeakerAccess") - public static <T> EmptyIterator<T> emptyIterator() { - return new EmptyIterator<>(); - } - public static class EmptyIterator<T> implements Iterator<T> { - - @Override - public boolean hasNext() { - return false; - } - @Override - public T next() { - throw new NoSuchElementException("Empty iterators contain no elements"); - } - - } - - public static <T,R> MappingIterator<T,R> mappingIterator(Function<T,R> f, Iterator<T> iterator) { - return new MappingIterator<>(f, iterator); - } - private static class MappingIterator<T, R> implements Iterator<R> { - private final Function<T, R> mapping; - - private final Iterator<T> iterator; - - public MappingIterator(Function<T, R> mapping, Iterator<T> iterator) { - this.mapping = mapping; - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - @Override - public R next() { - return mapping.apply(iterator.next()); - } - - } - - public static <T> FilteringIterator<T> filteringIterator(Predicate<T> predicate, Iterator<T> iterator) { - return new FilteringIterator<>(predicate, iterator); - } - - private static class FilteringIterator<T> implements Iterator<T> { - private final Predicate<T> predicate; - private final Iterator<T> iterator; - private List<T> nextValue; - - public FilteringIterator(Predicate<T> predicate, Iterator<T> iterator) { - this.predicate = predicate; - this.iterator = iterator; - this.nextValue = null; - } - - private void populateNext() { - while (nextValue == null && iterator.hasNext()) { - T value = iterator.next(); - if (predicate.test(value)) { - nextValue = Collections.singletonList(value); - } else { - nextValue = null; - } - } - } - - @Override - public boolean hasNext() { - populateNext(); - return nextValue != null; - } - - @Override - public T next() { - populateNext(); - T result = nextValue.get(0); - nextValue = null; - return result; - } - } -} diff --git a/src/main/java/au/id/zancanaro/javacheck/ShrinkResult.java b/src/main/java/au/id/zancanaro/javacheck/ShrinkResult.java index a28c2a5..6bf3882 100644 --- a/src/main/java/au/id/zancanaro/javacheck/ShrinkResult.java +++ b/src/main/java/au/id/zancanaro/javacheck/ShrinkResult.java @@ -1,6 +1,5 @@ package au.id.zancanaro.javacheck; -import java.util.Arrays; import java.util.List; public class ShrinkResult { diff --git a/src/main/java/au/id/zancanaro/javacheck/ShrinkStrategy.java b/src/main/java/au/id/zancanaro/javacheck/ShrinkStrategy.java index 0018309..cf74020 100644 --- a/src/main/java/au/id/zancanaro/javacheck/ShrinkStrategy.java +++ b/src/main/java/au/id/zancanaro/javacheck/ShrinkStrategy.java @@ -1,8 +1,8 @@ package au.id.zancanaro.javacheck; -import java.util.Iterator; +import java.util.stream.Stream; @FunctionalInterface interface ShrinkStrategy<T> { - Iterator<T> shrink(T obj); + Stream<T> shrink(T obj); } diff --git a/src/main/java/au/id/zancanaro/javacheck/ShrinkTree.java b/src/main/java/au/id/zancanaro/javacheck/ShrinkTree.java index bdc7604..4337c14 100644 --- a/src/main/java/au/id/zancanaro/javacheck/ShrinkTree.java +++ b/src/main/java/au/id/zancanaro/javacheck/ShrinkTree.java @@ -3,20 +3,20 @@ package au.id.zancanaro.javacheck; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; - -import static au.id.zancanaro.javacheck.Iterators.*; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; @SuppressWarnings("unused") public class ShrinkTree<T> { private final T value; - private final Iterable<ShrinkTree<T>> children; + private final Supplier<Stream<ShrinkTree<T>>> children; - private ShrinkTree(T value, Iterable<ShrinkTree<T>> children) { + private ShrinkTree(T value, Supplier<Stream<ShrinkTree<T>>> children) { this.value = value; this.children = children; } @@ -25,20 +25,23 @@ public class ShrinkTree<T> { return value; } - public Iterator<ShrinkTree<T>> getChildren() { - return children.iterator(); + public Stream<ShrinkTree<T>> getChildren() { + return children.get(); } + @SuppressWarnings("Convert2MethodRef") public static <T> ShrinkTree<T> pure(T value) { - return new ShrinkTree<>(value, Collections.emptyList()); + // Converting the () -> Stream.empty() into Stream::empty actually + // breaks Java's generic type inference. Who would have thought? + return new ShrinkTree<>(value, () -> Stream.empty()); } public static <T> ShrinkTree<T> join(ShrinkTree<ShrinkTree<T>> tree) { return new ShrinkTree<>( tree.getValue().getValue(), - () -> concat( - mappingIterator(ShrinkTree::join, tree.children.iterator()), - tree.getValue().children.iterator())); + () -> Stream.concat( + tree.getChildren().map(ShrinkTree::join), + tree.getValue().getChildren())); } private static <T> List<T> makeHeadList(ShrinkTree<T>[] trees) { @@ -49,48 +52,42 @@ public class ShrinkTree<T> { return heads; } - public static <T> Iterator<ShrinkTree<T>[]> promoteChildren(ShrinkTree<T>[] trees) { - return flatten( - rangeIterator(trees.length, index -> - mappingIterator(child -> { - @SuppressWarnings("unchecked") - ShrinkTree<T>[] result = (ShrinkTree<T>[]) new ShrinkTree[trees.length]; - for (int i = 0; i < trees.length; ++i) { - result[i] = (i == index ? child : trees[i]); - } - return result; - }, trees[index].getChildren()))); - } - - public static <T> Iterator<ShrinkTree<T>[]> removeChildren(ShrinkTree<T>[] trees) { - return rangeIterator(trees.length, index -> { - @SuppressWarnings("unchecked") - ShrinkTree<T>[] result = (ShrinkTree<T>[]) new ShrinkTree[trees.length - 1]; - for (int i = 0; i < trees.length - 1; ++i) { - result[i] = trees[(i >= index ? i + 1 : i)]; - } - return result; - }); + @SuppressWarnings("unchecked") + public static <T> Stream<ShrinkTree<T>[]> promoteChildren(ShrinkTree<T>[] trees) { + return IntStream.range(0, trees.length) + .mapToObj(index -> trees[index].getChildren().map(child -> + IntStream.range(0, trees.length) + .mapToObj(i -> (i == index ? child : trees[i])) + .toArray(ShrinkTree[]::new))) + .flatMap(x -> x) + .map(x -> (ShrinkTree<T>[]) x); } - public static <T> Iterator<ShrinkTree<T>[]> removeAndPromoteChildren(ShrinkTree<T>[] trees) { - return concat(removeChildren(trees), promoteChildren(trees)); + public static <T> Stream<ShrinkTree<T>[]> removeChildren(ShrinkTree<T>[] trees) { + return IntStream.range(0, trees.length) + .mapToObj(index -> IntStream.range(0, trees.length) + .filter(i -> i != index) + .mapToObj(i -> trees[i]) + .toArray(ShrinkTree[]::new)); + } + + public static <T> Stream<ShrinkTree<T>[]> removeAndPromoteChildren(ShrinkTree<T>[] trees) { + return Stream.concat(removeChildren(trees), promoteChildren(trees)); } public static <T> ShrinkTree<List<T>> combine( ShrinkTree<T>[] trees, - Function<ShrinkTree<T>[], Iterator<ShrinkTree<T>[]>> processChildren) { + Function<ShrinkTree<T>[], Stream<ShrinkTree<T>[]>> processChildren) { return new ShrinkTree<>( makeHeadList(trees), - () -> mappingIterator( - shrinks -> ShrinkTree.combine(shrinks, processChildren), - processChildren.apply(trees))); + () -> processChildren.apply(trees) + .map(shrinks -> combine(shrinks, processChildren))); } public <R> ShrinkTree<R> map(Function<T, R> f) { return new ShrinkTree<>( f.apply(this.value), - () -> mappingIterator(tree -> tree.map(f), this.children.iterator())); + () -> this.getChildren().map(tree -> tree.map(f))); } public <R> ShrinkTree<R> flatMap(Function<T, ShrinkTree<R>> f) { @@ -101,24 +98,20 @@ public class ShrinkTree<T> { if (predicate.test(this.getValue())) { return new ShrinkTree<>( this.getValue(), - () -> mappingIterator(tree -> tree.filter(predicate), - filteringIterator( - tree -> predicate.test(tree.getValue()), - this.getChildren()))); + () -> this.getChildren() + .filter(tree -> predicate.test(tree.getValue())) + .map(tree -> tree.filter(predicate))); } else { throw new IllegalArgumentException("Current value doesn't match predicate: whoops!"); } } public ShrinkTree<T> withShrinkStrategy(ShrinkStrategy<T> strategy) { - return new ShrinkTree<>(this.getValue(), strategyIterable(this.getValue(), strategy)); + return new ShrinkTree<>(this.getValue(), () -> strategyStream(this.getValue(), strategy)); } - private static <T> Iterable<ShrinkTree<T>> strategyIterable(final T value, final ShrinkStrategy<T> strategy) { - return () -> - mappingIterator( - v -> new ShrinkTree<>(v, strategyIterable(v, strategy)), - strategy.shrink(value)); + private static <T> Stream<ShrinkTree<T>> strategyStream(final T value, final ShrinkStrategy<T> strategy) { + return strategy.shrink(value).map(v -> new ShrinkTree<>(v, () -> strategyStream(v, strategy))); } public void print(Writer output) throws IOException { @@ -128,8 +121,12 @@ public class ShrinkTree<T> { public void print(Writer output, Function<T, String> toString) throws IOException { output.write(toString.apply(this.getValue())); output.write('['); - for (ShrinkTree<T> child : children) { - child.print(output, toString); + Iterator<ShrinkTree<T>> iterator = children.get().iterator(); + while (iterator.hasNext()) { + iterator.next().print(output, toString); + if (iterator.hasNext()) { + output.write(' '); + } } output.write(']'); output.flush(); diff --git a/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java b/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java index b0dd2cd..6bba786 100644 --- a/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java +++ b/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java @@ -229,8 +229,8 @@ public class Properties extends BlockJUnit4ClassRunner { Thread shutdownHandler = makeShutdownHandler(smallest, originalEx); Runtime.getRuntime().addShutdownHook(shutdownHandler); - Iterator<ShrinkTree<List<Object>>> trees = failed.getChildren(); Set<List<Object>> seenArgs = new HashSet<>(); + Iterator<ShrinkTree<List<Object>>> trees = failed.getChildren().iterator(); while (trees.hasNext()) { ShrinkTree<List<Object>> tree = trees.next(); if (seenArgs.add(Arrays.asList(tree.getValue()))) { @@ -240,7 +240,7 @@ public class Properties extends BlockJUnit4ClassRunner { // ignore, because it's not useful } catch (Throwable ex) { smallest[0] = new ShrinkResult(tree.getValue(), ex); - Iterator<ShrinkTree<List<Object>>> children = tree.getChildren(); + Iterator<ShrinkTree<List<Object>>> children = tree.getChildren().iterator(); if (children.hasNext()) { trees = children; } else { |