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

paulk 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 8d47d5687b GROOVY-11606: Lazy findAll, collect, collectMany
8d47d5687b is described below

commit 8d47d5687b08e70dc0b291a3327ce0e1e839d0b1
Author: Paul King <[email protected]>
AuthorDate: Wed Apr 9 11:27:38 2025 +1000

    GROOVY-11606: Lazy findAll, collect, collectMany
---
 .../groovy/runtime/ArrayGroovyMethods.java         | 197 ++++++++----
 .../groovy/runtime/DefaultGroovyMethods.java       | 336 +++++++++++++++++++--
 2 files changed, 462 insertions(+), 71 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java 
b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
index eee004fd55..eefa4242b8 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
@@ -1199,60 +1199,22 @@ public class ArrayGroovyMethods extends 
DefaultGroovyMethodsSupport {
     
//--------------------------------------------------------------------------
     // columns
 
-    /**
-     * An iterator of the columns of the array.
-     * <pre class="groovyTestCase">
-     * double[][] nums = [[1.0d, 2.0d], [10.0d, 20.0d]]
-     * assert nums.transpose() == nums.columns().toList()
-     * </pre>
-     *
-     * @param self a double[][]
-     * @return the iterator
-     */
+    @Deprecated
     public static Iterator<double[]> columns(double[][] self) {
         return new DoubleDoubleArrayColumnIterator(self);
     }
 
-    /**
-     * An iterator of the columns of the array.
-     * <pre class="groovyTestCase">
-     * float[][] nums = [[1.0f, 2.0f], [10.0f, 20.0f]]
-     * assert nums.transpose() == nums.columns().toList()
-     * </pre>
-     *
-     * @param self a float[][]
-     * @return the iterator
-     */
+    @Deprecated
     public static Iterator<float[]> columns(float[][] self) {
         return new FloatFloatArrayColumnIterator(self);
     }
 
-    /**
-     * An iterator of the columns of the array.
-     * <pre class="groovyTestCase">
-     * int[][] nums = [[1, 2], [10, 20]]
-     * assert nums.transpose() == nums.columns().toList()
-     * assert nums.columns().collect{ int[] col -> col.sum() } == [11, 22]
-     * </pre>
-     *
-     * @param self an int[][]
-     * @return the iterator
-     */
+    @Deprecated
     public static Iterator<int[]> columns(int[][] self) {
         return new IntIntArrayColumnIterator(self);
     }
 
-    /**
-     * An iterator of the columns of the array.
-     * <pre class="groovyTestCase">
-     * long[][] nums = [[1L, 2L], [10L, 20L]]
-     * assert nums.transpose() == nums.columns().toList()
-     * assert nums.columns().collect{ long[] col -> col.sum() } == [11L, 22L]
-     * </pre>
-     *
-     * @param self a long[][]
-     * @return the iterator
-     */
+    @Deprecated
     public static Iterator<long[]> columns(long[][] self) {
         return new LongLongArrayColumnIterator(self);
     }
@@ -9319,6 +9281,67 @@ public class ArrayGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return result;
     }
 
+    
//--------------------------------------------------------------------------
+    // transposing
+
+    /**
+     * An iterator of the columns of the array.
+     * <pre class="groovyTestCase">
+     * double[][] nums = [[1.0d, 2.0d], [10.0d, 20.0d]]
+     * assert nums.transpose() == nums.transposing().toList()
+     * </pre>
+     *
+     * @param self a double[][]
+     * @return the iterator
+     */
+    public static Iterator<double[]> transposing(double[][] self) {
+        return new DoubleDoubleArrayColumnIterator(self);
+    }
+
+    /**
+     * An iterator of the columns of the array.
+     * <pre class="groovyTestCase">
+     * float[][] nums = [[1.0f, 2.0f], [10.0f, 20.0f]]
+     * assert nums.transpose() == nums.transposing().toList()
+     * </pre>
+     *
+     * @param self a float[][]
+     * @return the iterator
+     */
+    public static Iterator<float[]> transposing(float[][] self) {
+        return new FloatFloatArrayColumnIterator(self);
+    }
+
+    /**
+     * An iterator of the columns of the array.
+     * <pre class="groovyTestCase">
+     * int[][] nums = [[1, 2], [10, 20]]
+     * assert nums.transpose() == nums.transposing().toList()
+     * assert nums.transposing().collect{ int[] col -> col.sum() } == [11, 22]
+     * </pre>
+     *
+     * @param self an int[][]
+     * @return the iterator
+     */
+    public static Iterator<int[]> transposing(int[][] self) {
+        return new IntIntArrayColumnIterator(self);
+    }
+
+    /**
+     * An iterator of the columns of the array.
+     * <pre class="groovyTestCase">
+     * long[][] nums = [[1L, 2L], [10L, 20L]]
+     * assert nums.transpose() == nums.transposing().toList()
+     * assert nums.transposing().collect{ long[] col -> col.sum() } == [11L, 
22L]
+     * </pre>
+     *
+     * @param self a long[][]
+     * @return the iterator
+     */
+    public static Iterator<long[]> transposing(long[][] self) {
+        return new LongLongArrayColumnIterator(self);
+    }
+
     
//--------------------------------------------------------------------------
     // union
 
@@ -9640,68 +9663,134 @@ public class ArrayGroovyMethods extends 
DefaultGroovyMethodsSupport {
     // zip
 
     /**
-     * An iterator of all the pairs of two arrays.
+     * A list of all the pairs from two arrays.
+     * <pre class="groovyTestCase">
+     * double[] small = [1.0d, 2.0d, 3.0d]
+     * double[] large = [100d, 200d, 300d]
+     * assert [small, large].transpose() == small.zip(large)
+     * </pre>
+     *
+     * @param self a double[]
+     * @param other another double[]
+     * @return a list of all the pairs from self and other
+     */
+    public static List<Tuple2<Double, Double>> zip(double[] self, double[] 
other) {
+        return DefaultGroovyMethods.zip(new DoubleArrayIterable(self), new 
DoubleArrayIterable(other));
+    }
+
+    /**
+     * An iterator of all the pairs from two arrays.
      * <pre class="groovyTestCase">
      * double[] small = [1.0d, 2.0d, 3.0d]
      * double[] large = [100d, 200d, 300d]
-     * assert [small, large].transpose() == small.zip(large).toList()
+     * assert [small, large].transpose() == small.zipping(large).toList()
      * </pre>
      *
      * @param self a double[]
      * @param other another double[]
      * @return an iterator of all the pairs from self and other
      */
-    public static Iterator<Tuple2<Double, Double>> zip(double[] self, double[] 
other) {
+    public static Iterator<Tuple2<Double, Double>> zipping(double[] self, 
double[] other) {
         return DefaultGroovyMethods.zip(new DoubleArrayIterator(self), new 
DoubleArrayIterator(other));
     }
 
     /**
-     * An iterator of all the pairs of two arrays.
+     * A list of all the pairs from two arrays.
      * <pre class="groovyTestCase">
      * float[] small = [1.0f, 2.0f, 3.0f]
      * float[] large = [100f, 200f, 300f]
-     * assert [small, large].transpose() == small.zip(large).toList()
+     * assert [small, large].transpose() == small.zip(large)
+     * </pre>
+     *
+     * @param self a float[]
+     * @param other another float[]
+     * @return a list of all the pairs from self and other
+     */
+    public static List<Tuple2<Float, Float>> zip(float[] self, float[] other) {
+        return DefaultGroovyMethods.zip(new FloatArrayIterable(self), new 
FloatArrayIterable(other));
+    }
+
+    /**
+     * An iterator of all the pairs from two arrays.
+     * <pre class="groovyTestCase">
+     * float[] small = [1.0f, 2.0f, 3.0f]
+     * float[] large = [100f, 200f, 300f]
+     * assert [small, large].transpose() == small.zipping(large).toList()
      * </pre>
      *
      * @param self a float[]
      * @param other another float[]
      * @return an iterator of all the pairs from self and other
      */
-    public static Iterator<Tuple2<Float, Float>> zip(float[] self, float[] 
other) {
+    public static Iterator<Tuple2<Float, Float>> zipping(float[] self, float[] 
other) {
         return DefaultGroovyMethods.zip(new FloatArrayIterator(self), new 
FloatArrayIterator(other));
     }
 
     /**
-     * An iterator of all the pairs of two arrays.
+     * A list of all the pairs from two arrays.
      * <pre class="groovyTestCase">
      * int[] small = [1, 2, 3]
      * int[] large = [100, 200, 300]
      * assert [101, 202, 303] == small.zip(large).collect{ a, b -> a + b }
-     * assert [small, large].transpose() == small.zip(large).toList()
+     * assert [small, large].transpose() == small.zip(large)
+     * </pre>
+     *
+     * @param self an int[]
+     * @param other another int[]
+     * @return a list of all the pairs from self and other
+     */
+    public static List<Tuple2<Integer, Integer>> zip(int[] self, int[] other) {
+        return DefaultGroovyMethods.zip(new IntArrayIterable(self), new 
IntArrayIterable(other));
+    }
+
+    /**
+     * An iterator of all the pairs from two arrays.
+     * <pre class="groovyTestCase">
+     * int[] small = [1, 2, 3]
+     * int[] large = [100, 200, 300]
+     * assert [101, 202, 303] == small.zipping(large).collect{ a, b -> a + b }
+     * assert [small, large].transpose() == small.zipping(large).toList()
      * </pre>
      *
      * @param self an int[]
      * @param other another int[]
      * @return an iterator of all the pairs from self and other
      */
-    public static Iterator<Tuple2<Integer, Integer>> zip(int[] self, int[] 
other) {
+    public static Iterator<Tuple2<Integer, Integer>> zipping(int[] self, int[] 
other) {
         return DefaultGroovyMethods.zip(new IntArrayIterator(self), new 
IntArrayIterator(other));
     }
 
     /**
-     * An iterator of all the pairs of two arrays.
+     * A list of all the pairs from two arrays.
      * <pre class="groovyTestCase">
      * long[] small = [1L, 2L, 3L]
      * long[] large = [100L, 200L, 300L]
      * assert [101L, 202L, 303L] == small.zip(large).collect{ a, b -> a + b }
-     * assert [small, large].transpose() == small.zip(large).toList()
+     * assert [small, large].transpose() == small.zip(large)
+     * </pre>
+     *
+     * @param self a long[]
+     * @param other another long[]
+     * @return a list of all the pairs from self and other
+     */
+    public static List<Tuple2<Long, Long>> zip(long[] self, long[] other) {
+        return DefaultGroovyMethods.zip(new LongArrayIterable(self), new 
LongArrayIterable(other));
+    }
+
+    /**
+     * An iterator of all the pairs from two arrays.
+     * <pre class="groovyTestCase">
+     * long[] small = [1L, 2L, 3L]
+     * long[] large = [100L, 200L, 300L]
+     * assert [101L, 202L, 303L] == small.zipping(large).collect{ a, b -> a + 
b }
+     * assert [small, large].transpose() == small.zipping(large).toList()
      * </pre>
      *
      * @param self a long[]
      * @param other another long[]
      * @return an iterator of all the pairs from self and other
      */
-    public static Iterator<Tuple2<Long, Long>> zip(long[] self, long[] other) {
+    public static Iterator<Tuple2<Long, Long>> zipping(long[] self, long[] 
other) {
         return DefaultGroovyMethods.zip(new LongArrayIterator(self), new 
LongArrayIterator(other));
     }
 
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java 
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 4cbfb50234..1bd64cf45f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -2358,6 +2358,52 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collect(self, new ArrayList<>(), transform);
     }
 
+    /**
+     * Returns an iterator of transformed values from the source iterator 
using the
+     * <code>transform</code> closure.
+     *
+     * <pre class="groovyTestCase">
+     * assert [1, 2, 3].repeat().collecting(Integer::next).take(6).toList() == 
[2, 3, 4, 2, 3, 4]
+     * assert Iterators.iterate('a', String::next).collecting{ (int)it - 
(int)'a' }.take(26).toList() == 0..25
+     * </pre>
+     *
+     * @param self      an Iterator
+     * @param transform the closure used to transform each element
+     * @return an Iterator for the transformed values
+     * @since 5.0.0
+     */
+    public static <E, T> Iterator<T> collecting(
+        @DelegatesTo.Target Iterator<E> self,
+        @DelegatesTo(genericTypeIndex = 0)
+        @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> 
transform) {
+        return new CollectIterator<>(self, transform);
+    }
+
+    private static final class CollectIterator<E, T> implements Iterator<T> {
+        private final Iterator<E> source;
+        private final Closure<T> transform;
+
+        private CollectIterator(Iterator<E> source, Closure<T> transform) {
+            this.source = source;
+            this.transform = transform;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return source.hasNext();
+        }
+
+        @Override
+        public T next() {
+            return transform.call(source.next());
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /**
      * Iterates through this Iterator transforming each item into a new value 
using the <code>transform</code> closure
      * and adding it to the supplied <code>collector</code>.
@@ -2560,6 +2606,56 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collectEntries(self, new LinkedHashMap<>(), transform);
     }
 
+    /**
+     * Returns an iterator of transformed values from the source iterator 
using the
+     * <code>transform</code> closure.
+     *
+     * <pre class="groovyTestCase">
+     * assert Iterators.iterate(0, Integer::next).take(40).collectingEntries { 
n -&gt;
+     *     var c = 'a'
+     *     n.times{ c = c.next() }
+     *     [c, n]
+     * }.take(4).collect(e -&gt; e.key + e.value) == ['a0', 'b1', 'c2', 'd3']
+     * </pre>
+     *
+     * @param self      an Iterator
+     * @param transform the closure used to transform each element
+     * @return an Iterator for the transformed values
+     * @since 5.0.0
+     */
+    public static <K, V, E> Iterator<Map.Entry<K, V>> collectingEntries(
+        @DelegatesTo.Target Iterator<E> self,
+        @DelegatesTo(genericTypeIndex = 0)
+        @ClosureParams(FirstParam.FirstGenericType.class) Closure<?> 
transform) {
+        return new CollectEntriesIterator<>(self, transform);
+    }
+
+    private static final class CollectEntriesIterator<E, K, V> implements 
Iterator<Map.Entry<K, V>> {
+        private final Iterator<E> source;
+        private final Closure<?> transform;
+
+        private CollectEntriesIterator(Iterator<E> source, Closure<?> 
transform) {
+            this.source = source;
+            this.transform = transform;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return source.hasNext();
+        }
+
+        @SuppressWarnings({"unchecked"})
+        @Override
+        public Map.Entry<K, V> next() {
+            return getEntry(transform.call(source.next()));
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /**
      * Iterates through this Iterable transforming each item using the 
<code>transform</code> closure
      * and returning a map of the resulting transformed entries.
@@ -2803,23 +2899,30 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
             // GROOVY-10893: insert nothing
         } else if (entrySpec instanceof Map) {
             leftShift(target, (Map) entrySpec);
-        } else if (entrySpec instanceof List) {
+        } else {
+            leftShift(target, getEntry(entrySpec));
+        }
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    private static Map.Entry getEntry(Object entrySpec) {
+        if (entrySpec instanceof List) {
             var list = (List) entrySpec;
             // def (key, value) == list
             Object key = list.isEmpty() ? null : list.get(0);
             Object value = list.size() <= 1 ? null : list.get(1);
-            leftShift(target, new MapEntry(key, value));
-        } else if (entrySpec.getClass().isArray()) {
+            return new MapEntry(key, value);
+        }
+        if (entrySpec.getClass().isArray()) {
             Object[] array = (Object[]) entrySpec;
             // def (key, value) == array.toList()
             Object key = array.length == 0 ? null : array[0];
             Object value = array.length <= 1 ? null : array[1];
-            leftShift(target, new MapEntry(key, value));
-        } else {
-            // given Map.Entry is an interface, we get a proxy which gives us 
lots
-            // of flexibility but sometimes the error messages might be 
unexpected
-            leftShift(target, asType(entrySpec, Map.Entry.class));
+            return new MapEntry(key, value);
         }
+        // given Map.Entry is an interface, we get a proxy which gives us lots
+        // of flexibility but sometimes the error messages might be unexpected
+        return asType(entrySpec, Map.Entry.class);
     }
 
     
//--------------------------------------------------------------------------
@@ -3017,6 +3120,81 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collector;
     }
 
+    /**
+     * Iterates through the elements produced by projecting and flattening the 
elements from a source iterator.
+     * <p>
+     * <pre class="groovyTestCase">
+     * def numsIter = [1, 2, 3, 4, 5, 6].iterator()
+     * def squaresAndCubesOfEvens = numsIter.collectingMany{ if (it % 2 == 0) 
[it**2, it**3] }.collect()
+     * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216]
+     * </pre>
+     * <p>
+     * <pre class="groovyTestCase">
+     *     var letters = 'a'..'z'
+     * var pairs = letters.iterator().collectingMany{ a ->
+     *     letters.iterator().collectingMany{ b ->
+     *         if (a != b) ["$a$b"]
+     *     }.collect()
+     * }.collect()
+     * assert pairs.join(',').matches('ab,ac,ad,.*,zw,zx,zy')
+     * </pre>
+     *
+     * @param self       a source iterator
+     * @param projection a projecting Closure returning a collection of items
+     * @return an iterator of the projected collections flattened
+     * @since 5.0.0
+     */
+    public static <T, E> Iterator<T> collectingMany(
+        @DelegatesTo.Target Iterator<E> self,
+        @DelegatesTo(genericTypeIndex = 0)
+        @ClosureParams(FirstParam.FirstGenericType.class) Closure<? extends 
Collection<? extends T>> projection) {
+        return new CollectManyIterator<>(self, projection);
+    }
+
+    private static final class CollectManyIterator<E, T> implements 
Iterator<T> {
+        private final Iterator<E> source;
+        private final Closure<? extends Collection<? extends T>> transform;
+        private final Queue<T> buffer = new LinkedList<>();
+        private boolean ready = false;
+
+        private CollectManyIterator(Iterator<E> source, Closure<? extends 
Collection<? extends T>> transform) {
+            this.source = source;
+            this.transform = transform;
+            advance();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return ready;
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException("CollectManyIterator has been 
exhausted and contains no more elements");
+            }
+            T result = buffer.poll();
+            ready = !buffer.isEmpty();
+            advance();
+            return result;
+        }
+
+        private void advance() {
+            while (!ready && source.hasNext()) {
+                Collection<? extends T> result = transform.call(source.next());
+                if (result != null) {
+                    buffer.addAll(result);
+                }
+                ready = !buffer.isEmpty();
+            }
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /**
      * Projects each item from a source iterator to a collection and 
concatenates (flattens) the resulting collections into a single list.
      * <p>
@@ -5361,6 +5539,71 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return collector;
     }
 
+    /**
+     * Lazily finds all items matching the closure condition.
+     *
+     * <pre class="groovyTestCase">
+     * def letters = Iterators.iterate('A', 
String::next).take(26).plus(Iterators.iterate('a', String::next).take(26))
+     * assert letters.findingAll{ it.toUpperCase() < 'D' }.toList() == ['A', 
'B', 'C', 'a', 'b', 'c']
+     * </pre>
+     *
+     * @param self      a source Iterator
+     * @param transform the closure used to select elements
+     * @return an Iterator returning the selected elements
+     * @since 5.0.0
+     */
+    public static <T> Iterator<T> findingAll(
+        @DelegatesTo.Target Iterator<T> self,
+        @DelegatesTo(genericTypeIndex = 0)
+        @ClosureParams(FirstParam.FirstGenericType.class) Closure<?> 
transform) {
+        return new FindAllIterator<>(self, transform);
+    }
+
+    private static final class FindAllIterator<T> implements Iterator<T> {
+        private final Iterator<T> source;
+        private T current;
+        private boolean found;
+        private final BooleanClosureWrapper test;
+
+        private FindAllIterator(Iterator<T> source, Closure<?> transform) {
+            this.source = source;
+            this.test = new BooleanClosureWrapper(transform);
+            this.found = false;
+            advance();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return found;
+        }
+
+        private void advance() {
+            while (!found && source.hasNext()) {
+                current = source.next();
+                if (test.call(current)) {
+                    found = true;
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException("FindAllIterator has been 
exhausted and contains no more elements");
+            }
+            T result = current;
+            found = false;
+            advance();
+            return result;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     
//--------------------------------------------------------------------------
     // findIndexOf
 
@@ -6104,15 +6347,29 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
      * @since 2.5.0
      */
     public static <T, U> Collection<T> findResults(Iterator<U> self, 
@ClosureParams(FirstParam.FirstGenericType.class) Closure<T> 
filteringTransform) {
-        List<T> result = new ArrayList<>();
-        while (self.hasNext()) {
-            U value = self.next();
-            T transformed = filteringTransform.call(value);
-            if (transformed != null) {
-                result.add(transformed);
-            }
-        }
-        return result;
+        return toList(findingResults(self, filteringTransform));
+    }
+
+    /**
+     * Iterates through the Iterator transforming items using the supplied 
closure
+     * and finding any non-null results.
+     * <p>
+     * Example:
+     * <pre class="groovyTestCase">
+     * def vowels = [a:1, e:5, i:9, o:15, u:21]
+     * assert Iterators.iterate('a', n -> ++n).findingResults{ vowels[it] 
}.take(3).toList() == [1, 5, 9]
+     * </pre>
+     *
+     * @param self               an Iterator
+     * @param filteringTransform a Closure that should return either a 
non-null transformed value or null for items which should be discarded
+     * @return an iterator for the non-null transformed values
+     * @since 5.0.0
+     */
+    public static <T, U> Iterator<T> findingResults(
+        @DelegatesTo.Target Iterator<U> self,
+        @DelegatesTo(genericTypeIndex = 0)
+        @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> 
filteringTransform) {
+        return new FindResultsIterator<>(self, filteringTransform);
     }
 
     /**
@@ -6126,6 +6383,51 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return findResults(self, Closure.IDENTITY);
     }
 
+    private static final class FindResultsIterator<T, U> implements 
Iterator<T> {
+        private final Iterator<U> source;
+        private T current;
+        private boolean found;
+        private final Closure<T> filteringTransform;
+
+        private FindResultsIterator(Iterator<U> source, Closure<T> 
filteringTransform) {
+            this.source = source;
+            this.filteringTransform = filteringTransform;
+            this.found = false;
+            advance();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return found;
+        }
+
+        private void advance() {
+            while (!found && source.hasNext()) {
+                current = filteringTransform.call(source.next());
+                if (current != null) {
+                    found = true;
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException("FindResultsIterator has been 
exhausted and contains no more elements");
+            }
+            T result = current;
+            found = false;
+            advance();
+            return result;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
     /**
      * Iterates through the map transforming items using the supplied closure
      * and collecting any non-null results.

Reply via email to