From b435b8659eef0e8bc2910966d87b5b74b4cddbe2 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Thu, 4 Jun 2015 16:46:31 +1000 Subject: Move stuff over to using streams instead of iterators: much nicer! --- .../java/au/id/zancanaro/javacheck/Generator.java | 9 +- .../javacheck/GeneratorSampleIterator.java | 30 ---- .../java/au/id/zancanaro/javacheck/Generators.java | 91 +++++------ .../java/au/id/zancanaro/javacheck/Iterators.java | 179 --------------------- .../au/id/zancanaro/javacheck/ShrinkResult.java | 1 - .../au/id/zancanaro/javacheck/ShrinkStrategy.java | 4 +- .../java/au/id/zancanaro/javacheck/ShrinkTree.java | 101 ++++++------ .../id/zancanaro/javacheck/junit/Properties.java | 4 +- 8 files changed, 97 insertions(+), 322 deletions(-) delete mode 100644 src/main/java/au/id/zancanaro/javacheck/GeneratorSampleIterator.java delete mode 100644 src/main/java/au/id/zancanaro/javacheck/Iterators.java (limited to 'src/main/java') 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 { return (random, size) -> this.generate(random, size).withShrinkStrategy(strategy); } - default Iterator sample(Random random, int maxSize) { - return new GeneratorSampleIterator<>(this, random, maxSize); + default Stream sample(Random random, int maxSize) { + return Stream.generate(() -> this.generate(random, maxSize).getValue()); + } + + default Stream 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 implements Iterator { - private final Generator generator; - private final Random random; - private final int maxSize; - private int size; - - public GeneratorSampleIterator(Generator 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 Generator noShrink(Generator 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 boolShrinkStrategy() { - return value -> new Iterator() { - 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 longInteger(long lower, long upper) { @@ -85,21 +72,19 @@ public final class Generators { } private static ShrinkStrategy longShrinkStrategy(final long bound) { - return value -> new Iterator() { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(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 action) { + if (curr == 0) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static Generator integer(int lower, int upper) { @@ -119,22 +104,21 @@ public final class Generators { return sized(size -> integer(0, size)); } - public static ShrinkStrategy intShrinkStrategy(int bound) { - return value -> new Iterator() { + private static ShrinkStrategy intShrinkStrategy(final int bound) { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(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 action) { + if (curr == 0) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static Generator doublePrecision(double lower, double upper) { @@ -150,22 +134,21 @@ public final class Generators { return sized(size -> doublePrecision(-size, size)); } - public static ShrinkStrategy doubleShrinkStrategy(double bound, double epsilon) { - return value -> new Iterator() { + private static ShrinkStrategy doubleShrinkStrategy(final double bound, double epsilon) { + return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(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 action) { + if (Math.abs(curr) < epsilon) { + return false; + } else { + action.accept(value - curr); + curr /= 2; + return true; + } } - }; + }, false); } public static Generator> listOf(Generator 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 RangeIterator rangeIterator(int countTo, Function fn) { - return new RangeIterator<>(countTo, fn); - } - - private static class RangeIterator implements Iterator { - private final Function action; - private final int countTo; - private int index = 0; - - public RangeIterator(int countTo, Function action) { - this.countTo = countTo; - this.action = action; - } - - @Override - public boolean hasNext() { - return index < countTo; - } - - @Override - public T next() { - return action.apply(index++); - } - } - - public static FlattenIterator flatten(Iterator> iterators) { - return new FlattenIterator<>(iterators); - } - public static class FlattenIterator implements Iterator { - private final Iterator> iterators; - - private Iterator current; - - public FlattenIterator(Iterator> iterators) { - this.current = Iterators.emptyIterator(); - this.iterators = iterators; - } - - private Iterator 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 ConcatIterator concat(Iterator left, Iterator right) { - return new ConcatIterator<>(left, right); - } - public static class ConcatIterator implements Iterator { - private final Iterator left; - - private final Iterator right; - - public ConcatIterator(Iterator left, Iterator 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 EmptyIterator emptyIterator() { - return new EmptyIterator<>(); - } - public static class EmptyIterator implements Iterator { - - @Override - public boolean hasNext() { - return false; - } - @Override - public T next() { - throw new NoSuchElementException("Empty iterators contain no elements"); - } - - } - - public static MappingIterator mappingIterator(Function f, Iterator iterator) { - return new MappingIterator<>(f, iterator); - } - private static class MappingIterator implements Iterator { - private final Function mapping; - - private final Iterator iterator; - - public MappingIterator(Function mapping, Iterator 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 FilteringIterator filteringIterator(Predicate predicate, Iterator iterator) { - return new FilteringIterator<>(predicate, iterator); - } - - private static class FilteringIterator implements Iterator { - private final Predicate predicate; - private final Iterator iterator; - private List nextValue; - - public FilteringIterator(Predicate predicate, Iterator 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 { - Iterator shrink(T obj); + Stream 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 { private final T value; - private final Iterable> children; + private final Supplier>> children; - private ShrinkTree(T value, Iterable> children) { + private ShrinkTree(T value, Supplier>> children) { this.value = value; this.children = children; } @@ -25,20 +25,23 @@ public class ShrinkTree { return value; } - public Iterator> getChildren() { - return children.iterator(); + public Stream> getChildren() { + return children.get(); } + @SuppressWarnings("Convert2MethodRef") public static ShrinkTree 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 ShrinkTree join(ShrinkTree> 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 List makeHeadList(ShrinkTree[] trees) { @@ -49,48 +52,42 @@ public class ShrinkTree { return heads; } - public static Iterator[]> promoteChildren(ShrinkTree[] trees) { - return flatten( - rangeIterator(trees.length, index -> - mappingIterator(child -> { - @SuppressWarnings("unchecked") - ShrinkTree[] result = (ShrinkTree[]) 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 Iterator[]> removeChildren(ShrinkTree[] trees) { - return rangeIterator(trees.length, index -> { - @SuppressWarnings("unchecked") - ShrinkTree[] result = (ShrinkTree[]) 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 Stream[]> promoteChildren(ShrinkTree[] 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[]) x); } - public static Iterator[]> removeAndPromoteChildren(ShrinkTree[] trees) { - return concat(removeChildren(trees), promoteChildren(trees)); + public static Stream[]> removeChildren(ShrinkTree[] 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 Stream[]> removeAndPromoteChildren(ShrinkTree[] trees) { + return Stream.concat(removeChildren(trees), promoteChildren(trees)); } public static ShrinkTree> combine( ShrinkTree[] trees, - Function[], Iterator[]>> processChildren) { + Function[], Stream[]>> processChildren) { return new ShrinkTree<>( makeHeadList(trees), - () -> mappingIterator( - shrinks -> ShrinkTree.combine(shrinks, processChildren), - processChildren.apply(trees))); + () -> processChildren.apply(trees) + .map(shrinks -> combine(shrinks, processChildren))); } public ShrinkTree map(Function f) { return new ShrinkTree<>( f.apply(this.value), - () -> mappingIterator(tree -> tree.map(f), this.children.iterator())); + () -> this.getChildren().map(tree -> tree.map(f))); } public ShrinkTree flatMap(Function> f) { @@ -101,24 +98,20 @@ public class ShrinkTree { 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 withShrinkStrategy(ShrinkStrategy strategy) { - return new ShrinkTree<>(this.getValue(), strategyIterable(this.getValue(), strategy)); + return new ShrinkTree<>(this.getValue(), () -> strategyStream(this.getValue(), strategy)); } - private static Iterable> strategyIterable(final T value, final ShrinkStrategy strategy) { - return () -> - mappingIterator( - v -> new ShrinkTree<>(v, strategyIterable(v, strategy)), - strategy.shrink(value)); + private static Stream> strategyStream(final T value, final ShrinkStrategy 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 { public void print(Writer output, Function toString) throws IOException { output.write(toString.apply(this.getValue())); output.write('['); - for (ShrinkTree child : children) { - child.print(output, toString); + Iterator> 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>> trees = failed.getChildren(); Set> seenArgs = new HashSet<>(); + Iterator>> trees = failed.getChildren().iterator(); while (trees.hasNext()) { ShrinkTree> 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>> children = tree.getChildren(); + Iterator>> children = tree.getChildren().iterator(); if (children.hasNext()) { trees = children; } else { -- cgit v1.2.3