This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 99015bc8f2 GROOVY-11596: Additional DGM lazy iterator methods
99015bc8f2 is described below

commit 99015bc8f2a2269d1177316764b889818946384b
Author: Paul King <[email protected]>
AuthorDate: Mon Mar 31 20:25:32 2025 +1000

    GROOVY-11596: Additional DGM lazy iterator methods
---
 .../groovy/runtime/DefaultGroovyMethods.java       | 1250 ++++++++++++++++++--
 1 file changed, 1141 insertions(+), 109 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java 
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 3e8dbaaf56..b4f1be2380 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -1666,6 +1666,8 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Averages the result of applying a closure to each item of an Iterable.
      * <code>iter.average(closure)</code> is equivalent to:
      * <code>iter.collect(closure).average()</code>.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * assert 20 == [1, 3].average { it * 10 }
      * assert 3 == ['to', 'from'].average { it.size() }
@@ -1686,6 +1688,11 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * <code>iter.average(closure)</code> is equivalent to:
      * <code>iter.collect(closure).average()</code>.
      * The iterator will become exhausted of elements after determining the 
average value.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert ['to', 'from'].iterator().average{ it.size() } == 3
+     * </pre>
      *
      * @param self    An Iterator
      * @param closure a single parameter closure that returns a (typically) 
numeric value.
@@ -1828,6 +1835,11 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Chops the iterator items into pieces, returning lists with sizes 
corresponding to the supplied chop sizes.
      * If the iterator is exhausted early, truncated (possibly empty) pieces 
are returned.
      * Using a chop size of -1 will cause that piece to contain all remaining 
items from the iterator.
+     * <p>
+     * Example usage:
+     * <pre class="groovyTestCase">
+     * assert (1..6).iterator().chop(1, 2, -1) == [[1], [2, 3], [4, 5, 6]]
+     * </pre>
      *
      * @param self      an Iterator to be chopped
      * @param chopSizes the sizes for the returned pieces
@@ -1907,6 +1919,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Collates this iterable into sub-lists of length <code>size</code> 
stepping through the code <code>step</code>
      * elements for each sub-list.  Any remaining elements in the iterable 
after the subdivision will be dropped if
      * <code>keepRemainder</code> is false.
+     * <p>
      * Example:
      * <pre class="groovyTestCase">
      * def list = [ 1, 2, 3, 4 ]
@@ -1947,6 +1960,179 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return answer;
     }
 
+    /**
+     * Collates this iterator into sub-lists of length <code>size</code> 
stepping through the code <code>step</code>
+     * elements for each sub-list.  Any remaining elements in the iterator 
after the subdivision will be dropped if
+     * <code>keepRemainder</code> is false.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * var letters = 'A'..'G'
+     * assert letters.iterator().collate(2, 2, false).collect() == [['A', 
'B'], ['C', 'D'], ['E', 'F']]
+     * assert letters.iterator().collate(3, 2, true).collect() == [['A', 'B', 
'C'], ['C', 'D', 'E'], ['E', 'F', 'G'], ['G']]
+     * assert letters.iterator().collate(2, 3, true).collect() == [['A', 'B'], 
['D', 'E'], ['G']]
+     * </pre>
+     *
+     * @param self          an Iterator
+     * @param size          the length of each sub-list in the returned 
elements
+     * @param step          the number of elements to step through for each 
sub-list
+     * @param keepRemainder if true, any remaining elements are returned as 
sub-lists.  Otherwise they are discarded
+     * @return an iterator for the collated sub-lists
+     */
+    public static <T> Iterator<Collection<T>> collate(Iterator<T> self, int 
size, int step, boolean keepRemainder) {
+        return new CollateIterator<>(self, size, step, keepRemainder);
+    }
+
+    /**
+     * Collates this iterator into sub-lists of length <code>size</code> 
stepping through the code <code>step</code>
+     * elements for each sub-list including any remaining elements as the last 
sub-list.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert ('A'..'I').iterator().collate(4, 4).collect() == [['A', 'B', 
'C', 'D'], ['E', 'F', 'G', 'H'], ['I']]
+     * </pre>
+     *
+     * @param self          an Iterator
+     * @param size          the length of each sub-list in the returned 
elements
+     * @param step          the number of elements to step through for each 
sub-list
+     * @return an iterator for the collated sub-lists
+     */
+    public static <T> Iterator<Collection<T>> collate(Iterator<T> self, int 
size, int step) {
+        return new CollateIterator<>(self, size, step);
+    }
+
+    /**
+     * Collates this iterator into sub-lists of length <code>size</code>.
+     * Any remaining elements in the iterator after the subdivision will be 
dropped if
+     * <code>keepRemainder</code> is false.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert ('A'..'I').iterator().collate(4, false).collect() == [['A', 'B', 
'C', 'D'], ['E', 'F', 'G', 'H']]
+     * </pre>
+     *
+     * @param self          an Iterator
+     * @param size          the length of each sub-list in the returned 
elements
+     * @param keepRemainder if true, any remaining elements are returned as 
sub-lists.  Otherwise they are discarded
+     * @return an iterator for the collated sub-lists
+     */
+    public static <T> Iterator<Collection<T>> collate(Iterator<T> self, int 
size, boolean keepRemainder) {
+        return new CollateIterator<>(self, size, keepRemainder);
+    }
+
+    /**
+     * Collates this iterator into sub-lists of length <code>size</code> 
including
+     * any remaining elements as the last sub-list.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert ('A'..'H').iterator().collate(3).collect() == [['A', 'B', 'C'], 
['D', 'E', 'F'], ['G', 'H']]
+     * </pre>
+     *
+     * @param self          an Iterator
+     * @param size          the length of each sub-list in the returned 
elements
+     * @return an iterator for the collated sub-lists
+     */
+    public static <T> Iterator<Collection<T>> collate(Iterator<T> self, int 
size) {
+        return new CollateIterator<>(self, size);
+    }
+
+    private static class CollateIterator<T> implements Iterator<Collection<T>> 
{
+        private final Queue<Collection<T>> cache = new LinkedList<>();
+        private final Iterator<T> parent;
+        private Collection<T> current;
+        private boolean loaded;
+        private boolean exhausted;
+        private final int size;
+        private final int step;
+        private final boolean keepRemainder;
+        private int index = 0;
+
+        private CollateIterator(Iterator<T> parent, int size) {
+            this(parent, size, size, true);
+        }
+
+        private CollateIterator(Iterator<T> parent, int size, int step) {
+            this(parent, size, step, true);
+        }
+
+        private CollateIterator(Iterator<T> parent, int size, boolean 
keepRemainder) {
+            this(parent, size, size, keepRemainder);
+        }
+
+        private CollateIterator(Iterator<T> parent, int size, int step, 
boolean keepRemainder) {
+            if (size < 1) {
+                throw new IllegalArgumentException("Collation size must be > 
0");
+            }
+            if (step < 1) {
+                throw new IllegalArgumentException("Step size must be > 0");
+            }
+            this.parent = parent;
+            this.loaded = false;
+            this.current = null;
+            this.exhausted = false;
+            this.size = size;
+            this.step = step;
+            this.keepRemainder = keepRemainder;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (!loaded) {
+                loadNext();
+                loaded = true;
+            }
+            return !exhausted;
+        }
+
+        private void addElementToEachCachedList(T next) {
+            for (Collection<T> item : cache) {
+                item.add(next);
+            }
+        }
+
+        private boolean isFinished() {
+            if (!keepRemainder && (current == null || current.size() < size)) {
+                return true;
+            }
+            return current == null && !parent.hasNext();
+        }
+
+        private void loadNext() {
+            while (parent.hasNext()) {
+                T next = parent.next();
+                if (index % step == 0) {
+                    cache.offer(new ArrayList<>());
+                }
+                index++;
+                addElementToEachCachedList(next);
+                if (cache.peek() != null && cache.peek().size() == size) {
+                    break;
+                }
+            }
+            current = cache.poll();
+            if (isFinished()) {
+                exhausted = true;
+            }
+        }
+
+        @Override
+        public Collection<T> next() {
+            hasNext();
+            if (exhausted) {
+                throw new NoSuchElementException("CollateIterator has been 
exhausted and contains no more elements");
+            }
+            List<T> ret = new ArrayList<T>(current);
+            loaded = false;
+            return ret;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // collect
 
@@ -2031,6 +2217,20 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collector;
     }
 
+    /**
+     * Convert an iterator to a List. The iterator will become
+     * exhausted of elements after making this conversion. Alias for 
<code>toList</code>.
+     *
+     * @param self an iterator
+     * @return a List of the elements from the iterator
+     * @see #toList(Iterator)
+     * @since 5.0.0
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> List<T> collect(Iterator<T> self) {
+        return toList(self);
+    }
+
     /**
      * Iterates through this collection transforming each entry into a new 
value using Closure.IDENTITY
      * as a transformer, basically returning a list of items copied from the 
original collection.
@@ -2534,6 +2734,17 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collectMany(self, new ArrayList<>(), projection);
     }
 
+    /**
+     * Flattens an iterable of collections into a list.
+     *
+     * @param self an iterable of iterables
+     * @return the flattened list of elements
+     * @since 5.0.0
+     */
+    public static <T, E> List<T> collectMany(Iterable<E> self) {
+        return collectMany(self, new ArrayList<>(), Closure.IDENTITY);
+    }
+
     /**
      * Projects each item from a source collection to a result collection and 
concatenates (flattens) the resulting
      * collections adding them into the <code>collector</code>.
@@ -2618,8 +2829,8 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * <p>
      * <pre class="groovyTestCase">
      * var letters = 'a'..'z'
-     * var pairs = letters.collectMany{ a ->
-     *     letters.collectMany{ b ->
+     * var pairs = letters.collectMany{ a {@code ->}
+     *     letters.collectMany{ b {@code ->}
      *         if (a != b) ["$a$b"]
      *     }
      * }
@@ -2661,6 +2872,22 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collectMany(self, new ArrayList<>(), projection);
     }
 
+    /**
+     * Flattens an iterator of collections into a single list.
+     * <p>
+     * <pre class="groovyTestCase">
+     * assert [1, 2].zip([10, 20]).collectMany() == [1, 10, 2, 20]
+     * </pre>
+     *
+     * @param self       an iterator
+     * @return a list created from the source collections concatenated 
(flattened) together
+     * @see #collectMany(Iterator, groovy.lang.Closure)
+     * @since 5.0.0
+     */
+    public static <T, E> List<T> collectMany(Iterator<E> self) {
+        return collectMany(self, new ArrayList<>(), Closure.IDENTITY);
+    }
+
     
//--------------------------------------------------------------------------
     // collectNested
 
@@ -2924,6 +3151,13 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Comparison is done using Groovy's == operator (using
      * <code>compareTo(value) == 0</code> or <code>equals(value)</code> ).
      * The iterator will become exhausted of elements after determining the 
count value.
+     * <p>
+     * Example usage:
+     * <pre class="groovyTestCase">
+     * var nums = [2,4,2,1,3,2,4]
+     * assert nums.iterator().count(2) == 3
+     * assert nums.iterator().count(4) == 2
+     * </pre>
      *
      * @param self  the Iterator from which we count the number of matching 
occurrences
      * @param value the value being searched for
@@ -4080,7 +4314,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      *
      * @param self    the Iterator over which we iterate
      * @param closure the closure applied on each element found
-     * @return the self Iterator
+     * @return the (now exhausted) self Iterator
      * @since 2.4.0
      */
     public static <T> Iterator<T> each(Iterator<T> self, 
@ClosureParams(FirstParam.FirstGenericType.class) Closure closure) {
@@ -4262,7 +4496,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      *
      * @param self    an Iterator
      * @param closure a Closure to operate on each item
-     * @return the self Iterator (now exhausted)
+     * @return the (now exhausted) self Iterator
      * @since 2.3.0
      */
     public static <T> Iterator<T> eachWithIndex(Iterator<T> self, 
@ClosureParams(value=FromString.class, options="T,Integer") Closure closure) {
@@ -5751,6 +5985,8 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Flatten an Iterable. This Iterable and any nested arrays or
      * collections have their contents (recursively) added to the new 
collection.
      * If flattenOptionals is true, the non-empty optionals are also flattened.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * var items = [1..2, [3, [4]], Optional.of(5), Optional.empty()]
      * assert items.flatten() == [1, 2, 3, 4, 5]
@@ -5768,6 +6004,162 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return flatten(self, (Collection<T>) createSimilarCollection(self), 
flattenOptionals);
     }
 
+    /**
+     * Flatten an Iterator. This Iterator and any nested arrays, iterators or
+     * collections have their contents (recursively) added to the elements of 
the new iterator.
+     * Non-empty optionals are also flattened.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * var items = [1..2, [3, [4]], Optional.of(5)]
+     * assert items.iterator().flatten().collect() == [1, 2, 3, 4, 5]
+     * </pre>
+     *
+     * @param self an Iterator to flatten
+     * @return an iterator of the flattened elements
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> flatten(Iterator<E> self) {
+        return flatten(self, true);
+    }
+
+    /**
+     * Flatten an Iterator. This Iterator and any nested arrays, iterators or
+     * collections have their contents (recursively) added to the elements of 
the new iterator.
+     * If flattenOptionals is true, the non-empty optionals are also flattened.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * var items = [1..2, [3, [4]], Optional.of(5), Optional.empty()]
+     * assert items.iterator().flatten(true).toList() == [1, 2, 3, 4, 5]
+     * assert items.iterator().flatten(false).toList() == [1, 2, 3, 4, 
Optional.of(5), Optional.empty()]
+     * </pre>
+     *
+     * @param self an Iterator to flatten
+     * @param flattenOptionals whether to treat an Optional as a container to 
flatten
+     * @return an iterator of the flattened elements
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> flatten(Iterator<E> self, boolean 
flattenOptionals) {
+        return new FlattenIterator<>(self, flattenOptionals);
+    }
+
+    private static final class FlattenIterator<V> implements Iterator<V> {
+        private final Stack<Iterator<?>> cache = new Stack<>();
+        private final boolean flattenOptionals;
+        private final Stack<Closure<?>> flattenUsingStack = new Stack<>();
+        private final boolean flattenMany;
+        private boolean exhausted = false;
+        private V buffer;
+
+        private FlattenIterator(Iterator<?> delegate, boolean 
flattenOptionals) {
+            this(delegate, flattenOptionals, null);
+        }
+
+        private FlattenIterator(Iterator<?> delegate, boolean 
flattenOptionals, Closure<?> flattenUsing) {
+            this(delegate, flattenOptionals, flattenUsing, false);
+        }
+
+        private FlattenIterator(Iterator<?> delegate, boolean 
flattenOptionals, Closure<?> flattenUsing, boolean flattenMany) {
+            cache.push(delegate);
+            this.flattenOptionals = flattenOptionals;
+            this.flattenMany = flattenMany;
+            this.flattenUsingStack.push(flattenUsing);
+            step();
+        }
+
+        void step() {
+            outer:
+            while (!exhausted) {
+                if (cache.empty()) {
+                    exhausted = true;
+                    return;
+                }
+
+                Iterator<?> current = cache.peek();
+                Closure<?> flattenUsing = flattenUsingStack.peek();
+                while (current.hasNext()) {
+                    Object next = current.next();
+                    if (next instanceof Iterator) {
+                        cache.push((Iterator<?>) next);
+                        flattenUsingStack.push(flattenUsing);
+                        continue outer;
+                    } else if (next instanceof Collection) {
+                        cache.push(((Collection<?>) next).iterator());
+                        flattenUsingStack.push(flattenUsing);
+                        continue outer;
+                    } else if (next != null && next.getClass().isArray()) {
+                        cache.push((Iterator<?>) 
DefaultTypeTransformation.primitiveArrayToUnmodifiableList(next).iterator());
+                        flattenUsingStack.push(flattenUsing);
+                        continue outer;
+                    } else {
+                        Object flattened = next;
+                        if (flattened instanceof Optional && flattenOptionals) 
{
+                            Optional<?> opt = (Optional<?>) flattened;
+                            if (opt.isPresent()) {
+                                flattened = opt.get();
+                            } else {
+                                continue;
+                            }
+                        }
+                        if (flattenUsing != null) {
+                            Object transformed = flattenUsing.call(new 
Object[]{flattened});
+                            if (transformed instanceof Optional && 
flattenOptionals) {
+                                Optional<?> opt = (Optional<?>) transformed;
+                                if (opt.isPresent()) {
+                                    transformed = opt.get();
+                                } else {
+                                    continue;
+                                }
+                            }
+                            // handle some simple recursive cases to avoid 
infinite loop
+                            boolean returnedSelf = (flattened == transformed);
+                            if (!returnedSelf && transformed instanceof 
Collection) {
+                                Collection<?> c = (Collection<?>) transformed;
+                                if (!c.isEmpty() && head(c) == transformed) {
+                                    returnedSelf = true;
+                                }
+                                if (!returnedSelf) {
+                                    cache.push(flatten((Collection<?>) 
transformed, flattenOptionals).iterator());
+                                    flattenUsingStack.push(null);
+                                    continue outer;
+                                }
+                            }
+                            if (flattenMany && transformed != null && 
transformed.getClass().isArray()) {
+                                cache.push(new ArrayIterable((Object[]) 
transformed).iterator());
+                                flattenUsingStack.push(null);
+                                continue outer;
+                            }
+                            flattened = transformed;
+                        }
+                        buffer = (V) flattened;
+                        return;
+                    }
+                }
+                cache.pop();
+                flattenUsingStack.pop();
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return !exhausted;
+        }
+
+        @Override
+        public V next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            V result = buffer;
+            step();
+            return result;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /**
      * Flatten a List. This List and any nested arrays or
      * collections have their contents (recursively) added to the new List.
@@ -5813,33 +6205,10 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
     }
 
     private static <T, E> Collection<T> flatten(Iterable<E> elements, 
Collection<T> addTo, boolean flattenOptionals) {
-        for (E element : elements) {
-            flattenSingle(element, addTo, flattenOptionals);
-        }
+        addAll(addTo, flatten(elements.iterator(), flattenOptionals));
         return addTo;
     }
 
-    private static <T> void flattenSingle(Object element, Collection<T> addTo, 
boolean flattenOptionals) {
-        if (element instanceof Collection) {
-            flatten((Collection<T>) element, addTo, flattenOptionals);
-        } else if (element != null && element.getClass().isArray()) {
-            // handles non-primitive case too
-            
flatten(DefaultTypeTransformation.primitiveArrayToUnmodifiableList(element), 
addTo, flattenOptionals);
-        } else {
-            Object flattened = element;
-            if (flattened instanceof Optional && flattenOptionals) {
-                Optional<?> opt = (Optional<?>) flattened;
-                if (opt.isPresent()) {
-                    flattened = opt.get();
-                } else {
-                    return;
-                }
-            }
-            // add a leaf
-            addTo.add((T) flattened);
-        }
-    }
-
     /**
      * Flatten an Iterable. This Iterable and any nested arrays, collections, 
or
      * optionals have their contents (recursively) added to a new collection.
@@ -5848,10 +6217,12 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * If a leaf node represents some other kind of collective type,
      * the supplied closure should yield the contained items;
      * otherwise, the closure should just return the (optionally modified) 
leaf.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * // some examples just transforming the leaf
      * var items = [1..2, [3, [4]]]
-     * assert items.flatten(n -> n + 5) == 6..9
+     * assert items.flatten(n {@code ->} n + 5) == 6..9
      * assert items.flatten{ 'x' * it } == ['x', 'xx', 'xxx', 'xxxx']
      *
      * // Here our String is a "container" of day, month, year parts
@@ -5910,7 +6281,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
 
     /**
      * Flatten an Optional. This yields a collection containing the Optional 
value
-     * if the Optional is present, or an empty collect otherwise.
+     * if the Optional is present, or an empty collection otherwise.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * assert Optional.of(1).flatten() == [1]
      * assert Optional.empty().flatten() == []
@@ -5920,41 +6293,52 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * @return a flattened Optional
      * @since 5.0.0
      */
+    @SuppressWarnings("unchecked")
     public static <T, E> Collection<T> flatten(Optional<E> self) {
         List<T> result = new ArrayList<>();
-        self.ifPresent(e -> flattenSingle(e, result, true));
+        self.ifPresent(e -> result.add((T) self.get()));
         return result;
     }
 
-    private static <T, E> Collection<T> flatten(Iterable<E> elements, boolean 
flattenOptionals, Collection<T> addTo, Closure<?> flattenUsing) {
-        for (E element : elements) {
-            if (element instanceof Collection) {
-                flatten((Collection<?>) element, flattenOptionals, addTo, 
flattenUsing);
-            } else if (element != null && element.getClass().isArray()) {
-                
flatten(DefaultTypeTransformation.primitiveArrayToUnmodifiableList(element), 
flattenOptionals, addTo, flattenUsing);
-            } else if (element instanceof Optional && flattenOptionals) {
-                flatten(flatten((Optional<?>) element), flattenOptionals, 
addTo, flattenUsing);
-            } else {
-                flattenSingle(element, flattenOptionals, addTo, flattenUsing);
-            }
-        }
-        return addTo;
+    /**
+     * Flatten an Iterator. This Iterator and any nested arrays, iterators, 
collections, and potentially
+     * Optional elements have their contents (recursively) added to the 
elements for a new iterator.
+     * Non-container elements are leaf elements.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert [1..2, [3, [4]]].iterator().flatten(n {@code ->} n + 5).toList() 
== 6..9
+     * </pre>
+     *
+     * @param self an Iterable
+     * @param flattenUsing a closure to determine how to flatten leaf elements
+     * @return an iterator of the flattened items
+     * @see #flatten(Iterable, Closure)
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> flatten(Iterator<E> self, Closure<?> 
flattenUsing) {
+        return flatten(self, true, flattenUsing);
     }
 
-    private static <T> void flattenSingle(Object element, boolean 
flattenOptionals, Collection<T> addTo, Closure<?> flattenUsing) {
-        Object flattened = flattenUsing.call(new Object[]{element});
-        boolean returnedSelf = (flattened == element);
-        if (!returnedSelf && flattened instanceof Collection) {
-            List<?> list = toList((Iterable<?>) flattened);
-            if (list.size() == 1 && list.get(0) == element) {
-                returnedSelf = true;
-            }
-        }
-        if (flattened instanceof Collection && !returnedSelf) {
-            addTo.addAll(flatten((Collection<?>) flattened, flattenOptionals));
-        } else {
-            addTo.add((T) flattened);
-        }
+    /**
+     * Flatten an Iterator. This Iterator and any nested arrays, iterators, 
collections, and potentially
+     * Optional elements have their contents (recursively) added to the 
elements for a new iterator.
+     * Non-container elements are leaf elements.
+     *
+     * @param self an Iterable
+     * @param flattenOptionals whether to treat an Optional as a container to 
flatten or a leaf
+     * @param flattenUsing a closure to determine how to flatten leaf elements
+     * @return an iterator of the flattened items
+     * @see #flatten(Iterable, boolean, Closure)
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> flatten(Iterator<E> self, boolean 
flattenOptionals, Closure<?> flattenUsing) {
+        return new FlattenIterator<>(self, flattenOptionals, flattenUsing);
+    }
+
+    private static <T, E> Collection<T> flatten(Iterable<E> elements, boolean 
flattenOptionals, Collection<T> addTo, Closure<?> flattenUsing) {
+        addAll(addTo, flatten(elements.iterator(), flattenOptionals, 
flattenUsing));
+        return addTo;
     }
 
     
//--------------------------------------------------------------------------
@@ -5965,8 +6349,8 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * optionals have their contents (recursively) added to a new collection.
      * A transform is applied to any leaf nodes before further flattening.
      * <pre class="groovyTestCase">
-     * var items = ["1", "2", "foo", "3", "bar"]
-     * var toInt = s -> s.number ? Optional.of(s.toInteger()) : 
Optional.empty()
+     * var items = ['1', '2', 'foo', '3', 'bar']
+     * var toInt = s {@code ->} s.number ? Optional.of(s.toInteger()) : 
Optional.empty()
      * assert items.flattenMany(toInt) == [1, 2, 3]
      * assert items.flattenMany(String::toList) == ['1', '2', 'f', 'o', 'o', 
'3', 'b', 'a', 'r']
      * assert items.flattenMany{ it.split(/[aeiou]/) } == ['1', '2', 'f', '3', 
'b', 'r']
@@ -5990,24 +6374,25 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return flattenMany(self, (Collection<T>) 
createSimilarCollection(self), transform);
     }
 
-    private static <T> Collection<T> flattenMany(Iterable<?> self, 
Collection<T> addTo, Closure<?> flattenUsing) {
-        for (Object element : self) {
-            flattenSingle(element, addTo, flattenUsing);
-        }
-        return addTo;
+    /**
+     * Flatten the elements from an Iterator. This iterator and any nested 
arrays, collections,
+     * iterators, or optionals have their contents (recursively) added to to 
the elements for
+     * the resulting iterator. A transform is applied to any leaf nodes before 
further flattening.
+     *
+     * @param self an Iterable
+     * @param flattenOptionals whether non-present Optional values are removed
+     * @param flattenUsing a transform applied to any leaf elements
+     * @return a flattened Collection
+     * @see #flattenMany(Iterable, Closure)
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> flattenMany(Iterator<E> self, boolean 
flattenOptionals, Closure<?> flattenUsing) {
+        return new FlattenIterator<>(self, flattenOptionals, flattenUsing, 
true);
     }
 
-    private static <T> void flattenSingle(Object element, Collection<T> addTo, 
Closure<?> transform) {
-        if (element instanceof Collection) {
-            addTo.addAll(flattenMany((Collection) element, addTo, transform));
-        } else if (element != null && element.getClass().isArray()) {
-            addTo.addAll(flattenMany(new ArrayIterable((Object[]) element), 
addTo, transform));
-        } else if (element instanceof Optional) {
-            addTo.addAll(flattenMany(flatten((Optional) element), addTo, 
transform));
-        } else {
-            Object flattened = transform.call(new Object[]{element});
-            flattenSingle(flattened, addTo, true);
-        }
+    private static <T, E> Collection<T> flattenMany(Iterable<E> self, 
Collection<T> addTo, Closure<?> flattenUsing) {
+        addAll(addTo, flattenMany(self.iterator(), true, flattenUsing));
+        return addTo;
     }
 
     
//--------------------------------------------------------------------------
@@ -7385,11 +7770,11 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      *
      * <pre class="groovyTestCase">
      * def items = [1, 2, 3, 4]
-     * def value = items.inject { acc, val -> acc * val }
+     * def value = items.inject { acc, val {@code ->} acc * val }
      * assert value == 1 * 2 * 3 * 4
      *
      * items = [['a','b'], ['b','c'], ['d','b']]
-     * value = items.inject { acc, val -> acc.intersect(val) }
+     * value = items.inject { acc, val {@code ->} acc.intersect(val) }
      * assert value == ['b']
      *
      * items = ['j', 'o', 'i', 'n'] as Set
@@ -7551,6 +7936,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * but returns the list of all calculated values instead of just the final 
result.
      * <p>
      * Also known as <em>prefix sum</em> or <em>scan</em> in functional 
parlance.
+     * Example:
      * <pre class="groovyTestCase">
      * assert (1..3).injectAll(''){ carry, next {@code ->} carry + next } == 
['1', '12', '123']
      * var runningAvg = [1.0, 2.0, 3.0].injectAll([0.0, 0, null]){ accum, next 
{@code ->}
@@ -7568,13 +7954,23 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * @since 5.0.0
      */
     public static <E, T, U extends T, V extends T> List<T> 
injectAll(Iterable<E> self, U initialValue, 
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
-        return injectAll(self.iterator(), initialValue, closure);
+        Iterator<E> iter = self.iterator();
+        List<T> result = new ArrayList<>();
+        addAll(result, injectAll(iter, initialValue, closure));
+        return result;
     }
 
     /**
      * Iterates through the given iterable, injecting values as per 
<tt>inject</tt>
      * but returns the list of all calculated values instead of just the final 
result.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert (1..5).injectAll { carry, next {@code ->} carry + next } == [3, 
6, 10, 15]
+     * assert (1..5).injectAll(Integer::plus) == [3, 6, 10, 15]
+     * </pre>
      *
+     * @see #injectAll(Iterable, Closure)
      * @since 5.0.0
      */
     public static <E extends T, T, V extends T> List<T> injectAll(Iterable<E> 
self, @ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
@@ -7582,26 +7978,79 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         if (!iter.hasNext()) {
             throw new NoSuchElementException("Cannot call injectAll() on an 
empty iterable without passing an initial value.");
         }
-        return injectAll(iter, iter.next(), closure);
+        List<T> result = new ArrayList<>();
+        addAll(result, injectAll(iter, iter.next(), closure));
+        return result;
     }
 
     /**
      * Iterates through the given iterator, injecting values as per 
<tt>inject</tt>
      * but returns the list of all calculated values instead of just the final 
result.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert (1..3)
+     *     .iterator()
+     *     .injectAll(''){ carry, next {@code ->} carry + next }
+     *     .collect() == ['1', '12', '123']
+     * </pre>
      *
+     * @see #injectAll(Iterable, Object, Closure)
      * @since 5.0.0
      */
-    public static <E, T, U extends T, V extends T> List<T> 
injectAll(Iterator<E> self, U initialValue, 
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
-        List<T> result = new ArrayList<>();
-        T value = initialValue;
-        Object[] params = new Object[2];
-        while (self.hasNext()) {
+    public static <E, T, U extends T, V extends T> Iterator<T> 
injectAll(Iterator<E> self, U initialValue, 
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+        return new InjectAllIterator<>(self, initialValue, closure);
+    }
+
+    /**
+     * Iterates through the given iterator, injecting values as per 
<tt>inject</tt>
+     * but returns the list of all calculated values instead of just the final 
result.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * assert (1..5).iterator().injectAll(Integer::plus).collect() == [3, 6, 
10, 15]
+     * </pre>
+     *
+     * @since 5.0.0
+     */
+    public static <E, T, V extends T> Iterator<T> injectAll(Iterator<E> self, 
@ClosureParams(value=FromString.class,options="T,E") Closure<V> closure) {
+        if (!self.hasNext()) {
+            throw new NoSuchElementException("Cannot call injectAll() on an 
empty iterable without passing an initial value.");
+        }
+        return (Iterator<T>) injectAll(self, self.next(), closure);
+    }
+
+    private static final class InjectAllIterator<E, T, U, V> implements 
Iterator<T> {
+        private final Iterator<E> delegate;
+        private final Closure<V> closure;
+        private U value;
+
+        private InjectAllIterator(Iterator<E> delegate, U initialValue, 
Closure<V> closure) {
+            this.delegate = delegate;
+            this.value = initialValue;
+            this.closure = closure;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public T next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            Object[] params = new Object[2];
             params[0] = value;
-            params[1] = self.next();
-            value = closure.call(params);
-            result.add(value);
+            params[1] = delegate.next();
+            value = (U) closure.call(params);
+            return (T) value;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
         }
-        return result;
     }
 
     /**
@@ -7694,20 +8143,204 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * @return a Number (an Integer) resulting from the integer division 
operation
      * @since 1.0
      */
-    public static Number intdiv(Character left, Character right) {
-        return intdiv(Integer.valueOf(left), right);
+    public static Number intdiv(Character left, Character right) {
+        return intdiv(Integer.valueOf(left), right);
+    }
+
+    /**
+     * Integer Divide two Numbers.
+     *
+     * @param left  a Number
+     * @param right another Number
+     * @return a Number (an Integer) resulting from the integer division 
operation
+     * @since 1.0
+     */
+    public static Number intdiv(Number left, Number right) {
+        return NumberMath.intdiv(left, right);
+    }
+
+    
//--------------------------------------------------------------------------
+    // interleave
+
+    /**
+     * The elements from two Iterables interleaved.
+     * If the iterables are of different sizes, the result will have the same
+     * size as the shorter one.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def abc = 'A'..'C'
+     * def nums = 1..2
+     * assert ['A', 1, 'B', 2] == abc.interleave(nums)
+     * assert [1, 'A', 2, 'B'] == nums.interleave(abc)
+     * </pre>
+     *
+     * @param self an Iterable
+     * @param other another Iterable
+     * @return a collection of all the pairs from self and other
+     * @since 5.0.0
+     */
+    public static <T, U extends T, V extends T> Collection<T> 
interleave(Iterable<U> self, Iterable<V> other) {
+        Collection<T> result = new ArrayList<>();
+        addAll(result, interleave(self.iterator(), other.iterator()));
+        return result;
+    }
+
+    /**
+     * The elements from two Iterables interleaved.
+     * If the sources are of different sizes, and 
<code>includeRemainder</code> is true,
+     * the extra elements from the longer source will be appended after 
interleaving.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def items = (1..4)
+     * def other = [10, 20]
+     * assert [1, 10, 2, 20, 3, 4] == items.interleave(other, true)
+     * </pre>
+     * <p>
+     * When <code>includeRemainder</code> is false, this is equivalent to 
<code>zip</code> then <code>collectMany</code>.
+     *
+     * @param self an Iterable
+     * @param other another Iterable
+     * @return a collection of all the pairs from self and other
+     * @see #transpose(List) for zipping more than two lists
+     * @since 5.0.0
+     */
+    public static <T, U extends T, V extends T> Collection<T> 
interleave(Iterable<U> self, Iterable<V> other, boolean includeRemainder) {
+        Collection<T> result = new ArrayList<>();
+        addAll(result, interleave(self.iterator(), other.iterator(), 
includeRemainder));
+        return result;
+    }
+
+    /**
+     * An iterator of the elements from two Iterators interleaved.
+     * If the sources are of different sizes, the result will have twice the
+     * size of the shorter one.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def items = (1..4).iterator()
+     * def other = [10, 20].iterator()
+     * assert [1, 10, 2, 20] == items.interleave(other).collect()
+     * </pre>
+     *
+     * @param self an Iterator
+     * @param other another Iterator
+     * @return an iterator of all the pairs from self and other
+     * @since 5.0.0
+     */
+    public static <T, U extends T, V extends T> Iterator<T> 
interleave(Iterator<U> self, Iterator<V> other) {
+        return interleave(self, other, false);
+    }
+
+    /**
+     * An iterator of the elements from two Iterators interleaved.
+     * If the sources are of different sizes, and 
<code>includeRemainder</code> is true,
+     * the extra elements from the longer source will be appended after 
interleaving.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def items = (1..4).iterator()
+     * def other = [10, 20].iterator()
+     * assert [1, 10, 2, 20, 3, 4] == items.interleave(other, true).collect()
+     * </pre>
+     * <p>
+     * When <code>includeRemainder</code> is false, this is equivalent to 
<code>zip</code> then <code>collectMany</code>.
+     *
+     * @param self an Iterator
+     * @param other an Iterable
+     * @param includeRemainder whether to process extra elements if the 
operands are of different sizes
+     * @return an iterator of all the pairs from self and other
+     * @since 5.0.0
+     */
+    public static <T, U extends T, V extends T> Iterator<T> 
interleave(Iterator<U> self, Iterator<V> other, boolean includeRemainder) {
+        return new InterleaveIterator<>(self, other, includeRemainder);
+    }
+
+    /**
+     * An iterator of all the elements from this iterator and the iterable 
interleaved.
+     * If the sources are of different sizes, the result will have twice the
+     * size of the shorter one.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def items = (1..4).iterator()
+     * def other = [10, 20]
+     * assert [1, 10, 2, 20] == items.interleave(other).collect()
+     * </pre>
+     *
+     * @param self an Iterator
+     * @param other another Iterator
+     * @return an iterator of all the elements from self and other interleaved 
(until one source is exhausted)
+     * @since 5.0.0
+     */
+    public static <T, U extends T, V extends T> Iterator<T> 
interleave(Iterator<U> self, Iterable<V> other) {
+        return interleave(self, other, false);
     }
 
     /**
-     * Integer Divide two Numbers.
+     * An iterator of all the elements from this iterator and the iterable 
interleaved.
+     * If the sources are of different sizes, and 
<code>includeRemainder</code> is true,
+     * the extra elements from the longer source will be appended after 
interleaving.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def items = (1..4).iterator()
+     * def other = [10, 20]
+     * assert [1, 10, 2, 20, 3, 4] == items.interleave(other, true).collect()
+     * </pre>
+     * <p>
+     * When <code>includeRemainder</code> is false, this is equivalent to 
<code>zip</code> then <code>collectMany</code>.
      *
-     * @param left  a Number
-     * @param right another Number
-     * @return a Number (an Integer) resulting from the integer division 
operation
-     * @since 1.0
+     * @param self an Iterator
+     * @param other an Iterable
+     * @param includeRemainder whether to process extra elements if the 
operands are of different sizes
+     * @return an iterator of all the pairs from self and other
+     * @since 5.0.0
      */
-    public static Number intdiv(Number left, Number right) {
-        return NumberMath.intdiv(left, right);
+    public static <T, U extends T, V extends T> Iterator<T> 
interleave(Iterator<U> self, Iterable<V> other, boolean includeRemainder) {
+        return interleave(self, other.iterator(), includeRemainder);
+    }
+
+    private static final class InterleaveIterator<T, U extends T, V extends T> 
implements Iterator<T> {
+        private final Iterator<U> delegate;
+        private final Iterator<V> other;
+        private final boolean includeRemainder;
+        private Iterator<T> current;
+
+        private InterleaveIterator(Iterator<U> delegate, Iterator<V> other, 
boolean includeRemainder) {
+            this.delegate = delegate;
+            this.other = other;
+            this.includeRemainder = includeRemainder;
+            current = (Iterator<T>) delegate;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (includeRemainder) {
+                return delegate.hasNext() || other.hasNext();
+            }
+            return current != null && current.hasNext();
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            T result = current.next();
+            if (current == delegate && other.hasNext()) {
+                current = (Iterator<T>) other;
+            } else if (current == other && delegate.hasNext() && 
(other.hasNext() || includeRemainder)) {
+                current = (Iterator<T>) delegate;
+            } else if (!includeRemainder) {
+                current = null;
+            }
+            return result;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
     }
 
     
//--------------------------------------------------------------------------
@@ -10366,6 +10999,66 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return DefaultGroovyMethods.toString(left) + right;
     }
 
+    /**
+     * Appends two iterators.
+     *
+     * <pre class="groovyTestCase">
+     * assert [1, 2].iterator().plus([3, 4].iterator()).collect() == 1..4
+     * </pre>
+     *
+     * @param left  an Iterator
+     * @param right an Iterator
+     * @return an iterator of all the items from the first then second iterator
+     * @since 5.0.0
+     */
+    public static <T> Iterator<T> plus(Iterator<T> left, Iterator<T> right) {
+        return new PlusIterator<>(left, right);
+    }
+
+    /**
+     * Appends an iterator and an iterable.
+     *
+     * <pre class="groovyTestCase">
+     * assert [1, 2].iterator().plus([3, 4]).collect() == 1..4
+     * </pre>
+     *
+     * @param left  an Iterator
+     * @param right an Iterable
+     * @return an iterator of all the items from first then second
+     * @since 5.0.0
+     */
+    public static <T> Iterator<T> plus(Iterator<T> left, Iterable<T> right) {
+        return plus(left, right.iterator());
+    }
+
+    private static final class PlusIterator<E> implements Iterator<E> {
+        private final Iterator<E> first;
+        private final Iterator<E> second;
+        private Iterator<E> current;
+
+        private PlusIterator(Iterator<E> first, Iterator<E> second) {
+            this.first = first;
+            this.second = second;
+            current = first;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return first.hasNext() || second.hasNext();
+        }
+
+        @Override
+        public E next() {
+            current = first.hasNext() ? first : second;
+            return current.next();
+        }
+
+        @Override
+        public void remove() {
+            current.remove();
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // pop
 
@@ -11253,9 +11946,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Removes the last item from the List.
      *
      * <pre class="groovyTestCase">
-     * def list = ["a", false, 2]
+     * def list = ['a', false, 2]
      * assert list.removeLast() == 2
-     * assert list == ["a", false]
+     * assert list == ['a', false]
      * </pre>
      *
      * Using add() and removeLast() is similar to push and pop on a Stack
@@ -11273,6 +11966,102 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return self.remove(self.size() - 1);
     }
 
+    
//--------------------------------------------------------------------------
+    // repeat
+
+    /**
+     * Repeat the elements from an iterable. An alias for multiply.
+     * <pre class="groovyTestCase">
+     * assert ['a', 42].repeat(2) == ['a', 42, 'a', 42]
+     * </pre>
+     *
+     * @param self an Iterable
+     * @param count the (non-negative) number of times to repeat
+     * @return a collection containing the repeated elements
+     */
+    public static <T> Collection<T> repeat(Iterable<T> self, int count) {
+        Collection<T> result = self instanceof Collection ? 
createSimilarCollection((Collection<T>) self, Math.max(count, 0)) : new 
ArrayList<>();
+        addAll(result, repeat(self.iterator(), count));
+        return result;
+    }
+
+    /**
+     * Repeat the elements from an iterator.
+     *
+     * <pre class="groovyTestCase">
+     * assert ['a', 42].iterator().repeat(2).collect() == ['a', 42, 'a', 42]
+     * </pre>
+     *
+     * @param self an Iterator
+     * @param count the (non-negative) number of times to repeat
+     * @return an iterator containing the repeated elements
+     */
+    public static <T> Iterator<T> repeat(Iterator<T> self, int count) {
+        return new RepeatIterator<>(self, count);
+    }
+
+    /**
+     * Repeat the elements from an iterator infinitely.
+     *
+     * <pre class="groovyTestCase">
+     * assert ['a', 42].iterator().repeat().take(5).collect() == ['a', 42, 
'a', 42, 'a']
+     * </pre>
+     *
+     * @param self an Iterable
+     * @return a collection containing the repeated elements
+     */
+    public static <T> Iterator<T> repeat(Iterator<T> self) {
+        return new RepeatIterator<>(self);
+    }
+
+    private static final class RepeatIterator<T> implements Iterator<T> {
+        private static final Integer FOREVER = -1;
+        private final Queue<T> queue = new LinkedList<>();
+        private boolean repeating;
+        private Integer repeatCount;
+        private Iterator<T> parent;
+
+        private RepeatIterator(Iterator<T> parent) {
+            this(parent, FOREVER);
+        }
+
+        private RepeatIterator(Iterator<T> parent, Integer count) {
+            if(count < 0 && !count.equals(FOREVER)) {
+                throw new IllegalArgumentException("Cannot repeat a negative 
number of times");
+            }
+            this.repeatCount = count;
+            this.parent = count == 0 ? Collections.emptyIterator() : parent;
+            this.repeating = false;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return repeatCount == FOREVER || repeatCount > 1 || 
parent.hasNext();
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public T next() {
+            if (!parent.hasNext()) {
+                if (repeatCount == FOREVER || repeatCount-- > 1) {
+                    this.repeating = true;
+                    parent = queue.iterator();
+                } else {
+                    throw new NoSuchElementException("RepeatIterator has been 
exhausted and contains no more elements");
+                }
+            }
+            T ret = parent.next();
+            if (!repeating) {
+                queue.offer(ret);
+            }
+            return ret;
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // respondsTo
 
@@ -11999,19 +12788,25 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * assumed to be comparable. The original iterator will become
      * exhausted of elements after completing this method call.
      * A new iterator is produced that traverses the items in sorted order.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self the Iterator to be sorted
      * @return the sorted items as an Iterator
      * @since 1.5.5
      */
     public static <T> Iterator<T> sort(Iterator<T> self) {
-        return sort((Iterable<T>) toList(self)).listIterator();
+        return sort(toList(self)).listIterator();
     }
 
     /**
      * Sorts the given iterator items into a sorted iterator using the 
comparator. The
      * original iterator will become exhausted of elements after completing 
this method call.
      * A new iterator is produced that traverses the items in sorted order.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self       the Iterator to be sorted
      * @param comparator a Comparator used for comparing items
@@ -12058,6 +12853,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * or greater than the second respectively. Otherwise, the Closure is 
assumed
      * to take a single parameter and return a Comparable (typically an 
Integer)
      * which is then used for further comparison.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self    the Iterator to be sorted
      * @param closure a Closure used to determine the correct ordering
@@ -12065,7 +12863,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * @since 1.5.5
      */
     public static <T> Iterator<T> sort(Iterator<T> self, 
@ClosureParams(value=FromString.class, options={"T","T,T"}) Closure closure) {
-        return sort((Iterable<T>) toList(self), closure).listIterator();
+        return sort(toList(self), closure).listIterator();
     }
 
     /**
@@ -13236,6 +14034,93 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return callWithDelegateAndParameter(closure,self).getV2();
     }
 
+    /**
+     * Allows the closure to be called for each (or some) elements in the 
iterator.
+     * <p>
+     * Example:
+     * <pre>
+     * def items = (1..9).iterator()
+     * def collected = []
+     * assert items.tapEvery(3) { collected {@code <<} it }.collect() == 1..9
+     * assert collected == [1, 4, 7]
+     * </pre>
+     *
+     * @param self    an Iterator
+     * @param closure the closure to call
+     * @param every call the closure every this many elements
+     * @return an Iterator for the original elements
+     * @since 5.0.0
+     */
+    public static <T, U> Iterator<T> tapEvery(Iterator<T> self, int every, 
Closure<U> closure) {
+        return new TapIterator<>(self, closure, every);
+    }
+
+    /**
+     * Allows the closure to be called for each element in the iterator.
+     * <p>
+     * Example:
+     * <pre>
+     * def items = (1..9).iterator()
+     * def collected = []
+     * assert items.tapEvery { collected {@code <<} it }.collect() == 1..9
+     * assert collected == 1..9
+     *
+     * var nums = []
+     * assert [
+     *     [x: 3, y: 4],
+     *     [x: 5, y: 12],
+     *     [x: 8, y: 15],
+     *     [x: 7, y: 24]
+     * ].iterator().tapEvery {
+     *     nums {@code <<} Math.sqrt(x ** 2 + y ** 2).intValue()
+     * }.collect()*.x == [3, 5, 8, 7]
+     * assert nums == [5, 13, 17, 25]
+     * </pre>
+     *
+     * @param self    an Iterator
+     * @param closure the closure to call
+     * @return an Iterator for the original elements
+     * @since 5.0.0
+     */
+    public static <T, U> Iterator<T> tapEvery(Iterator<T> self, Closure<U> 
closure) {
+        return new TapIterator<>(self, closure, 1);
+    }
+
+    private static final class TapIterator<T, U> implements Iterator<T> {
+        private final Iterator<T> delegate;
+        private final Closure<U> closure;
+        private final int every;
+        private int index = -1;
+
+        private TapIterator(Iterator<T> delegate, Closure<U> closure, int 
every) {
+            if (every == 0) throw new IllegalArgumentException("every cannot 
be zero");
+            this.delegate = delegate;
+            this.closure = closure;
+            this.every = every;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            T next = delegate.next();
+            if (++index % every == 0) {
+                closure.setDelegate(next);
+                closure.call(next);
+            }
+            return next;
+        }
+
+        @Override
+        public void remove() {
+            delegate.remove();
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // times
 
@@ -13370,7 +14255,7 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * exhausted of elements after making this conversion.
      *
      * @param self an iterator
-     * @return a List
+     * @return a List of the elements from the iterator
      * @since 1.5.0
      */
     public static <T> List<T> toList(Iterator<T> self) {
@@ -13657,6 +14542,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * {@code NumberAwareComparator} has special treatment for numbers but 
otherwise uses the
      * natural ordering of the Iterator elements.
      * A new iterator is produced that traverses the items in sorted order.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self       the Iterator to be sorted
      * @return the sorted items as an Iterator
@@ -13671,6 +14559,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * Sorts the given iterator items using the comparator. The
      * original iterator will become exhausted of elements after completing 
this method call.
      * A new iterator is produced that traverses the items in sorted order.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self       the Iterator to be sorted
      * @param comparator a Comparator used for comparing items
@@ -13691,6 +14582,9 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * or greater than the second respectively. Otherwise, the Closure is 
assumed
      * to take a single parameter and return a Comparable (typically an 
Integer)
      * which is then used for further comparison.
+     * <p>
+     * The iterator is exhausted before elements are produced, so this method
+     * isn't suitable for use with infinite iterators.
      *
      * @param self    the Iterator to be sorted
      * @param closure a Closure used to determine the correct ordering
@@ -16002,29 +16896,40 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
     // zip
 
     /**
-     * An iterator of all the pairs of two Iterables.
+     * A collection of all the pairs of two Iterables.
+     * If the iterables are of different sizes, the result will have the same
+     * size as the shorter one.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * def one = ['cat', 'spider']
      * def two = ['fish', 'monkey']
-     * assert ['catfish', 'spidermonkey'] == one.zip(two).collect{ a, b -> a + 
b }
+     * assert ['catfish', 'spidermonkey'] == one.zip(two).collect{ a, b {@code 
->} a + b }
      * assert [one, two].transpose() == one.zip(two).toList()
      * </pre>
      *
      * @param self an Iterable
      * @param other another Iterable
-     * @return an iterator of all the pairs from self and other
+     * @return a collection of all the pairs from self and other
+     * @see #transpose(List) for zipping more than two lists
      * @since 5.0.0
      */
-    public static <U, V> Iterator<Tuple2<U, V>> zip(Iterable<U> self, 
Iterable<V> other) {
-        return zip(self.iterator(), other.iterator());
+    public static <U, V> Collection<Tuple2<U, V>> zip(Iterable<U> self, 
Iterable<V> other) {
+        Collection<Tuple2<U, V>> result = new ArrayList<>();
+        addAll(result, zip(self.iterator(), other.iterator()));
+        return result;
     }
 
     /**
      * An iterator of all the pairs of two Iterators.
+     * If the iterators are of different sizes, the result will have the same
+     * size as the shorter one.
+     * <p>
+     * Example:
      * <pre class="groovyTestCase">
      * def small = [1, 2, 3].iterator()
      * def large = [100, 200, 300].iterator()
-     * assert [101, 202, 303] == small.zip(large).collect{ a, b -> a + b }
+     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + 
b }
      * assert [small.toList(), large.toList()].transpose() == 
small.zip(large).toList()
      * </pre>
      *
@@ -16037,6 +16942,27 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return new ZipIterator<>(self, other);
     }
 
+    /**
+     * An iterator of all the pairs from this iterator and the iterable.
+     * If the sources are of different sizes, the result will have the same
+     * size as the shorter one.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def small = [1, 2, 3].iterator()
+     * def large = [100, 200, 300]
+     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + 
b }
+     * </pre>
+     *
+     * @param self an Iterator
+     * @param other an Iterable
+     * @return an iterator of all the pairs from self and other
+     * @since 5.0.0
+     */
+    public static <U, V> Iterator<Tuple2<U, V>> zip(Iterator<U> self, 
Iterable<V> other) {
+        return zip(self, other.iterator());
+    }
+
     private static final class ZipIterator<U, V> implements Iterator<Tuple2<U, 
V>> {
         private final Iterator<U> delegate;
         private final Iterator<V> other;
@@ -16063,6 +16989,112 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         }
     }
 
+    
//--------------------------------------------------------------------------
+    // zipAll
+
+    /**
+     * A collection of all the pairs of two Iterables of potentially different 
size.
+     * If the iterables are of different sizes, the result will have the same
+     * size as the longer one with values completed using the provided default.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def ab = ['a', 'b']
+     * def abcd = 'a'..'d'
+     * def nums = 1..3
+     * assert ['a1', 'b2', 'unknown3'] == ab.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * assert ['a1', 'b2', 'c3', 'd0'] == abcd.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * </pre>
+     *
+     * @param self an Iterable
+     * @param other another Iterable
+     * @return a collection of all the pairs from self and other
+     * @since 5.0.0
+     */
+    public static <U, V> Collection<Tuple2<U, V>> zipAll(Iterable<U> self, 
Iterable<V> other, U selfDefault, V otherDefault) {
+        Collection<Tuple2<U, V>> result = new ArrayList<>();
+        addAll(result, zipAll(self.iterator(), other.iterator(), selfDefault, 
otherDefault));
+        return result;
+    }
+
+    /**
+     * An iterator of all the pairs of two Iterators with potentially 
different numbers of elements.
+     * If the iterables are of different sizes, the result will have the same
+     * size as the longer one with values completed using the provided default.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def ab = ['a', 'b'].iterator()
+     * def abcd = ('a'..'d').iterator()
+     * def nums = (1..3).iterator()
+     * assert ['a1', 'b2', 'unknown3'] == ab.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * nums = (1..3).iterator()
+     * assert ['a1', 'b2', 'c3', 'd0'] == abcd.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * </pre>
+     *
+     * @param left an Iterator
+     * @param right another Iterator
+     * @return an iterator of all the pairs from left and right
+     * @since 5.0.0
+     */
+    public static <U, V> Iterator<Tuple2<U, V>> zipAll(Iterator<U> left, 
Iterator<V> right, U leftDefault, V rightDefault) {
+        return new ZipAllIterator<>(left, right, leftDefault, rightDefault);
+    }
+
+    /**
+     * An iterator of all the pairs of an Iterator and an Iterable with 
potentially different numbers of elements.
+     * If the iterables are of different sizes, the result will have the same
+     * size as the longer one with values completed using the provided default.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def ab = ['a', 'b'].iterator()
+     * def abcd = ('a'..'d').iterator()
+     * def nums = (1..3)
+     * assert ['a1', 'b2', 'unknown3'] == ab.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * assert ['a1', 'b2', 'c3', 'd0'] == abcd.zipAll(nums, 'unknown', 
0).collect{ a, b {@code ->} a + b }
+     * </pre>
+     *
+     * @param left an Iterator
+     * @param right an Iterable
+     * @return an iterator of all the pairs from left and right
+     * @since 5.0.0
+     */
+    public static <U, V> Iterator<Tuple2<U, V>> zipAll(Iterator<U> left, 
Iterable<V> right, U leftDefault, V rightDefault) {
+        return zipAll(left, right.iterator(), leftDefault, rightDefault);
+    }
+
+    private static final class ZipAllIterator<U, V> implements 
Iterator<Tuple2<U, V>> {
+        private final Iterator<U> left;
+        private final Iterator<V> right;
+        private final U leftDefault;
+        private final V rightDefault;
+
+        private ZipAllIterator(Iterator<U> left, Iterator<V> right, U 
leftDefault, V rightDefault) {
+            this.left = left;
+            this.right = right;
+            this.leftDefault = leftDefault;
+            this.rightDefault = rightDefault;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return left.hasNext() || right.hasNext();
+        }
+
+        @Override
+        public Tuple2<U, V> next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            return new Tuple2<>(left.hasNext() ? left.next() : leftDefault,
+                                right.hasNext() ? right.next() : rightDefault);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // withTraits
 

Reply via email to