From b435b8659eef0e8bc2910966d87b5b74b4cddbe2 Mon Sep 17 00:00:00 2001
From: Carlo Zancanaro <carlo@zancanaro.id.au>
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/au')

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 {
-- 
cgit v1.2.3