diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java index 105aa2e4a8b..81b3b3efbc5 100644 --- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java @@ -903,7 +903,7 @@ public static List> collate(T[] self, int size, int step, boolean ke * @since 2.5.0 */ public static List collect(E[] self, @ClosureParams(FirstParam.Component.class) Closure transform) { - return DefaultGroovyMethods.collect(new ArrayIterator<>(self), transform); + return DefaultGroovyMethods.collect(new ArrayIterable<>(self), transform); } /** @@ -1120,7 +1120,7 @@ public static Iterator columns(float[][] self) { *
      * int[][] nums = [[1, 2], [10, 20]]
      * assert nums.transpose() == nums.columns().toList()
-     * assert nums.columns().collect{ int[] col -> col.sum() } == [11, 22]
+     * assert nums.columns().collect{ int[] col -> col.sum() }.toList() == [11, 22]
      * 
* * @param self an int[][] @@ -1135,7 +1135,7 @@ public static Iterator columns(int[][] self) { *
      * long[][] nums = [[1L, 2L], [10L, 20L]]
      * assert nums.transpose() == nums.columns().toList()
-     * assert nums.columns().collect{ long[] col -> col.sum() } == [11L, 22L]
+     * assert nums.columns().collect{ long[] col -> col.sum() }.toList() == [11L, 22L]
      * 
* * @param self a long[][] @@ -2623,7 +2623,7 @@ public static List findIndexValues(T[] self, @ClosureParams(FirstPar * @since 2.5.0 */ public static List findIndexValues(T[] self, Number startIndex, @ClosureParams(FirstParam.Component.class) Closure condition) { - return DefaultGroovyMethods.findIndexValues(new ArrayIterator<>(self), startIndex, condition); + return DefaultGroovyMethods.findIndexValues(new ArrayIterable<>(self), startIndex, condition); } //-------------------------------------------------------------------------- @@ -2762,7 +2762,7 @@ public static Collection findResults(T[] self) { * @since 2.5.0 */ public static Collection findResults(U[] self, @ClosureParams(FirstParam.Component.class) Closure filteringTransform) { - return DefaultGroovyMethods.findResults(new ArrayIterator<>(self), filteringTransform); + return DefaultGroovyMethods.findResults(new ArrayIterable<>(self), filteringTransform); } //-------------------------------------------------------------------------- @@ -9489,7 +9489,7 @@ public static Iterator> zip(float[] self, float[] other) { *
      * int[] small = [1, 2, 3]
      * int[] large = [100, 200, 300]
-     * assert [101, 202, 303] == small.zip(large).collect{ a, b -> a + b }
+     * assert [101, 202, 303] == small.zip(large).collect{ a, b -> a + b }.toList()
      * assert [small, large].transpose() == small.zip(large).toList()
      * 
* @@ -9506,7 +9506,7 @@ public static Iterator> zip(int[] self, int[] other) { *
      * long[] small = [1L, 2L, 3L]
      * long[] large = [100L, 200L, 300L]
-     * assert [101L, 202L, 303L] == small.zip(large).collect{ a, b -> a + b }
+     * assert [101L, 202L, 303L] == small.zip(large).collect{ a, b -> a + b }.toList()
      * assert [small, large].transpose() == small.zip(large).toList()
      * 
* diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index f92fe5d603a..3f8325e5c23 100644 --- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -55,6 +55,7 @@ import groovy.util.PermutationGenerator; import groovy.util.ProxyGenerator; import org.apache.groovy.io.StringBuilderWriter; +import org.apache.groovy.lang.annotation.Incubating; import org.apache.groovy.util.ReversedList; import org.apache.groovy.util.SystemUtil; import org.codehaus.groovy.classgen.Verifier; @@ -153,6 +154,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Predicate; import static groovy.lang.groovydoc.Groovydoc.EMPTY_GROOVYDOC; @@ -273,6 +275,26 @@ private static Tuple2 callWithDelegateAndParameter(Closure closu return new Tuple2<>(result, object); } + private static T callWithDelegate(Closure closure, U object) { + if (object == NullObject.getNullObject()) { + object = null; // GROOVY-4526, et al. + } + @SuppressWarnings("unchecked") + final Closure clone = (Closure) closure.clone(); + clone.setDelegate(object); + return clone.call(object); + } + + private static boolean callBooleanWithDelegate(Closure closure, U object) { + if (object == NullObject.getNullObject()) { + object = null; // GROOVY-4526, et al. + } + final Closure clone = (Closure) closure.clone(); + clone.setDelegate(object); + final BooleanClosureWrapper wrapper = new BooleanClosureWrapper(clone); + return wrapper.call(object); + } + //-------------------------------------------------------------------------- // abs @@ -2351,12 +2373,103 @@ public static > C collect(Object self, C collector, C * @param self an Iterator * @param transform the closure used to transform each item * @return a List of the transformed values + * @deprecated * @since 2.5.0 */ - public static List collect(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + @Deprecated + public static List collect$$bridge(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { return collect(self, new ArrayList<>(), transform); } + /** + * Returns an iterator of transformed values from the source iterator using the + * transform closure. + * + *
+     * assert [1, 2, 3].repeat().collect(Integer::next).take(6).toList() == [2, 3, 4, 2, 3, 4]
+     * assert Iterators.iterate('a', String::next).collect{ (int)it - (int)'a' }.take(26).toList() == 0..25
+     * 
+ * + * @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 Iterator collect( + @DelegatesTo.Target Iterator self, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + return new CollectIterator<>(self, transform); + } + + private static final class CollectIterator implements Iterator { + private final Iterator source; + private final Closure transform; + + private CollectIterator(Iterator source, Closure transform) { + this.source = source; + this.transform = transform; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public T next() { + return callWithDelegate(transform, source.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns an iterator of transformed values from the source iterator using the + * transform closure. + * + *
+     * assert [1, 2, 3].repeat().map(Integer::next).take(6).toList() == [2, 3, 4, 2, 3, 4]
+     * 
+ * + * @param self a source Iterator + * @param transform a function used to transform each element + * @return an Iterator for the transformed values + * @since 5.0.0 + */ + @Incubating + public static Iterator map(Iterator self, Function transform) { + return new MapIterator<>(self, transform); + } + + private static final class MapIterator implements Iterator { + private final Iterator source; + private final Function transform; + + private MapIterator(Iterator source, Function transform) { + this.source = source; + this.transform = transform; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public R next() { + return transform.apply(source.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + /** * Iterates through this Iterator transforming each item into a new value using the transform closure * and adding it to the supplied collector. @@ -2418,7 +2531,7 @@ public static List collect(Iterable self) { * @since 2.5.0 */ public static List collect(Iterable self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { - return collect(self.iterator(), transform); + return toList(collect(self.iterator(), transform)); } /** @@ -2549,12 +2662,64 @@ public static Map collectEntries(Map self, @ClosurePara * should return a Map.Entry, a Map or a two-element list containing the resulting key and value * @return a Map of the transformed entries * @see #collectEntries(Iterable, Closure) + * @deprecated * @since 1.8.7 */ - public static Map collectEntries(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + @Deprecated + public static Map collectEntries$$bridge(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { return collectEntries(self, new LinkedHashMap<>(), transform); } + /** + * Returns an iterator of transformed values from the source iterator using the + * transform closure. + * + *
+     * assert Iterators.iterate(0, Integer::next).take(40).collectEntries { n ->
+     *     var c = 'a'
+     *     n.times{ c = c.next() }
+     *     [c, n]
+     * }.take(4).toMap() == [a:0, b:1, c:2, d:3]
+     * 
+ * + * @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 Iterator> collectEntries( + @DelegatesTo.Target Iterator self, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + return new CollectEntriesIterator<>(self, transform); + } + + private static final class CollectEntriesIterator implements Iterator> { + private final Iterator source; + private final Closure transform; + + private CollectEntriesIterator(Iterator source, Closure transform) { + this.source = source; + this.transform = transform; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @SuppressWarnings({"unchecked"}) + @Override + public Map.Entry next() { + return getEntry(callWithDelegate(transform, source.next())); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + /** * Iterates through this Iterable transforming each item using the transform closure * and returning a map of the resulting transformed entries. @@ -2579,7 +2744,7 @@ public static Map collectEntries(Iterator self, @ClosureParam * @since 1.8.7 */ public static Map collectEntries(Iterable self, @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { - return collectEntries(self.iterator(), transform); + return collectEntries(self.iterator(), new LinkedHashMap<>(), transform); } /** @@ -2591,7 +2756,7 @@ public static Map collectEntries(Iterable self, @ClosureParam * @since 1.8.7 */ public static Map collectEntries(Iterator self) { - return collectEntries(self, Closure.IDENTITY); + return collectEntries(self, new LinkedHashMap<>(), Closure.IDENTITY); } /** @@ -2798,23 +2963,30 @@ private static void addEntry(Map target, Object entrySpec) { // 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); } //-------------------------------------------------------------------------- @@ -2982,7 +3154,7 @@ public static List collectMany(Map self, @ClosureParams(MapEn *

*

      * def numsIter = [1, 2, 3, 4, 5, 6].iterator()
-     * def squaresAndCubesOfEvens = numsIter.collectMany{ if (it % 2 == 0) [it**2, it**3] }
+     * def squaresAndCubesOfEvens = numsIter.collectMany([]) { if (it % 2 == 0) [it**2, it**3] }
      * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216]
      * 
*

@@ -3013,21 +3185,155 @@ public static > C collectMany(Iterator self, C } /** - * Projects each item from a source iterator to a collection and concatenates (flattens) the resulting collections into a single list. + * Iterates through the elements produced by projecting and flattening the elements from a source iterator. *

*

      * def numsIter = [1, 2, 3, 4, 5, 6].iterator()
-     * def squaresAndCubesOfEvens = numsIter.collectMany{ it % 2 ? [] : [it**2, it**3] }
+     * def squaresAndCubesOfEvens = numsIter.collectMany { if (it % 2 == 0) [it**2, it**3] }.collect()
+     * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216]
+     * 
+ *

+ *

+     *     var letters = 'a'..'z'
+     * var pairs = letters.iterator().collectMany { a ->
+     *     letters.iterator().collectMany { b ->
+     *         if (a != b) ["$a$b"]
+     *     }.collect()
+     * }.collect()
+     * assert pairs.join(',').matches('ab,ac,ad,.*,zw,zx,zy')
+     * 
+ * + * @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 Iterator collectMany( + @DelegatesTo.Target Iterator self, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure> projection) { + return new CollectManyIterator<>(self, projection); + } + + private static final class CollectManyIterator implements Iterator { + private final Iterator source; + private final Closure> transform; + private final Queue buffer = new LinkedList<>(); + private boolean ready = false; + + private CollectManyIterator(Iterator source, Closure> 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 result = callWithDelegate(transform, source.next()); + if (result != null) { + buffer.addAll(result); + } + ready = !buffer.isEmpty(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Iterates through the elements produced by projecting and flattening the elements from a source iterator. + *

+ *

+     * def numsIter = Iterators.iterate(1, Integer::next).take(6)
+     * def squaresAndCubesOfEvens = numsIter.flatMap{ it % 2 == 0 ? [it**2, it**3] : [] }.collect()
      * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216]
      * 
* + * @param self a source iterator + * @param mapper a projecting function returning a collection of items + * @return an iterator of the projected collections flattened + * @since 5.0.0 + */ + @Incubating + public static Iterator flatMap(Iterator self, Function> mapper) { + return new FlatMapIterator<>(self, mapper); + } + + private static final class FlatMapIterator implements Iterator { + private final Iterator source; + private final Function> mapper; + private final Queue buffer = new LinkedList<>(); + private boolean ready = false; + + private FlatMapIterator(Iterator source, Function> mapper) { + this.source = source; + this.mapper = mapper; + advance(); + } + + @Override + public boolean hasNext() { + return ready; + } + + @Override + public R next() { + if (!hasNext()) { + throw new NoSuchElementException("FlatMapIterator has been exhausted and contains no more elements"); + } + R result = buffer.poll(); + ready = !buffer.isEmpty(); + advance(); + return result; + } + + private void advance() { + while (!ready && source.hasNext()) { + Collection result = mapper.apply(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. + * * @param self an iterator * @param projection a projecting Closure returning a collection of items * @return a list created from the projected collections concatenated (flattened) together * @see #collectMany(Iterator, Collection, groovy.lang.Closure) + * @deprecated * @since 1.8.1 */ - public static List collectMany(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure> projection) { + @Deprecated + public static List collectMany$$bridge(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure> projection) { return collectMany(self, new ArrayList<>(), projection); } @@ -4022,15 +4328,10 @@ public static Map drop(Map self, int num) { * Drops the given number of elements from the head of this iterator if they are available. * The original iterator is stepped along by num elements. *
-     * def iteratorCompare( Iterator a, List b ) {
-     *     a.collect { it } == b
-     * }
-     * def iter = [ 1, 2, 3, 4, 5 ].listIterator()
-     * assert iteratorCompare( iter.drop( 0 ), [ 1, 2, 3, 4, 5 ] )
-     * iter = [ 1, 2, 3, 4, 5 ].listIterator()
-     * assert iteratorCompare( iter.drop( 2 ), [ 3, 4, 5 ] )
-     * iter = [ 1, 2, 3, 4, 5 ].listIterator()
-     * assert iteratorCompare( iter.drop( 5 ), [] )
+     * def nums = [ 1, 2, 3, 4, 5 ]
+     * assert nums.iterator().drop(0).toList() == nums
+     * assert nums.iterator().drop(2).toList() == [ 3, 4, 5 ]
+     * assert nums.iterator().drop(5).toList() == []
      * 
* * @param self the original iterator @@ -5097,7 +5398,7 @@ public static boolean every(Object self) { */ public static Object find(Object self, Closure closure) { BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { + for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { Object value = iter.next(); if (bcw.call(value)) { return value; @@ -5356,6 +5657,136 @@ static > C findMany(C collector, Iterator + * def letters = Iterators.iterate('A', String::next).take(26).plus(Iterators.iterate('a', String::next).take(26)) + * assert letters.findAll { toUpperCase() < 'D' }.join() == 'ABCabc' + * + * + * @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 Iterator findAll( + @DelegatesTo.Target Iterator self, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + return new FindAllIterator<>(self, transform); + } + + private static final class FindAllIterator implements Iterator { + private final Iterator source; + private final Closure transform; + private T current; + private boolean found; + + private FindAllIterator(Iterator source, Closure transform) { + this.source = source; + this.transform = transform; + this.found = false; + advance(); + } + + @Override + public boolean hasNext() { + return found; + } + + private void advance() { + while (!found && source.hasNext()) { + current = source.next(); + if (callBooleanWithDelegate(transform, 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(); + } + } + + /** + * Finds all items matching the closure condition. + * + *
+     * def letters = Iterators.iterate('A', String::next).take(26).plus(Iterators.iterate('a', String::next).take(26))
+     * assert letters.filter{ it > 'W' && it < 'd' }.toList() == ['X', 'Y', 'Z', 'a', 'b', 'c']
+     * 
+ * + * @param self a source Iterator + * @param predicate the predicate used to test each element for selection + * @return an Iterator for the selected values + * @since 5.0.0 + */ + @Incubating + public static Iterator filter( + Iterator self, + Predicate predicate) { + return new FilterIterator<>(self, predicate); + } + + private static final class FilterIterator implements Iterator { + private final Iterator source; + private final Predicate predicate; + private T current; + private boolean found; + + private FilterIterator(Iterator source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + this.found = false; + advance(); + } + + @Override + public boolean hasNext() { + return found; + } + + private void advance() { + while (!found && source.hasNext()) { + current = source.next(); + if (predicate.test(current)) { + found = true; + return; + } + } + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("FilterIterator has been exhausted and contains no more elements"); + } + T result = current; + found = false; + advance(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + //-------------------------------------------------------------------------- // findIndexOf @@ -5495,7 +5926,7 @@ public static List findIndexValues(Object self, Closure condition) { * @since 1.5.2 */ public static List findIndexValues(Object self, Number startIndex, Closure condition) { - return findIndexValues(InvokerHelper.asIterator(self), startIndex, condition); + return toList(findIndexValues(InvokerHelper.asIterator(self), startIndex, condition)); } /** @@ -5505,10 +5936,12 @@ public static List findIndexValues(Object self, Number startIndex, Closu * @param self an Iterator * @param condition the matching condition * @return a list of numbers corresponding to the index values of all matched objects + * @deprecated * @since 2.5.0 */ - public static List findIndexValues(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { - return findIndexValues(self, 0, condition); + @Deprecated + public static List findIndexValues$$bridge(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return toList(findIndexValues(self, 0, condition)); } /** @@ -5520,9 +5953,11 @@ public static List findIndexValues(Iterator self, @ClosureParams( * @param startIndex start matching from this index * @param condition the matching condition * @return a list of numbers corresponding to the index values of all matched objects + * @deprecated * @since 2.5.0 */ - public static List findIndexValues(Iterator self, Number startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + @Deprecated + public static List findIndexValues$$bridge(Iterator self, Number startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { List result = new ArrayList<>(); long count = 0; long startCount = startIndex.longValue(); @@ -5539,6 +5974,103 @@ public static List findIndexValues(Iterator self, Number startInd return result; } + /** + * Returns an iterator of transformed values from the source iterator using the + * transform closure. + *
+     * def letters = ('a'..'z')
+     * def vowels = 'aeiou'.toSet()
+     * assert letters.iterator().findIndexValues { vowels.contains(it) }.toList() == [0, 4, 8, 14, 20]
+     * 
+ * + * @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 Iterator findIndexValues( + @DelegatesTo.Target Iterator self, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + return findIndexValues(self, 0, transform); + } + + /** + * Returns an iterator of transformed values from the source iterator using the + * transform closure and starting with index startIndex. + * + *
+     * def letters = ('a'..'z')
+     * def vowels = 'aeiou'.toSet()
+     * assert letters.iterator().findIndexValues(0) { vowels.contains(it) }.toList() == [0, 4, 8, 14, 20]
+     * assert letters.iterator().findIndexValues(1) { vowels.contains(it) }.toList() == [4, 8, 14, 20]
+     * 
+ * + * @param self an Iterator + * @param startIndex start matching from this index + * @param transform the closure used to transform each element + * @return an Iterator for the transformed values + * @since 5.0.0 + */ + public static Iterator findIndexValues( + @DelegatesTo.Target Iterator self, + Number startIndex, + @DelegatesTo(genericTypeIndex = 0) + @ClosureParams(FirstParam.FirstGenericType.class) Closure transform) { + return new FindIndexValuesIterator<>(self, startIndex, transform); + } + + private static final class FindIndexValuesIterator implements Iterator { + private final Iterator source; + private final Closure transform; + private final long startCount; + private long count = 0; + private Number current; + private boolean exhausted; + + private FindIndexValuesIterator(Iterator source, Number startIndex, Closure transform) { + this.source = source; + this.transform = transform; + this.startCount = startIndex.longValue(); + this.exhausted = false; + advance(); + } + + @Override + public boolean hasNext() { + return !exhausted; + } + + private void advance() { + while (source.hasNext()) { + Object value = source.next(); + if (count++ < startCount) { + continue; + } + if (callBooleanWithDelegate(transform, value)) { + current = count - 1; + return; + } + } + exhausted = true; + } + + @Override + public Number next() { + if (!hasNext()) { + throw new NoSuchElementException("FindIndexValuesIterator has been exhausted and contains no more elements"); + } + Number result = current; + advance(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + /** * Iterates over the elements of an Iterable and returns * the index values of the items that match the condition specified in the closure. @@ -5564,7 +6096,7 @@ public static List findIndexValues(Iterable self, @ClosureParams( * @since 2.5.0 */ public static List findIndexValues(Iterable self, Number startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { - return findIndexValues(self.iterator(), startIndex, condition); + return toList(findIndexValues(self.iterator(), startIndex, condition)); } //-------------------------------------------------------------------------- @@ -5911,6 +6443,7 @@ public static T findResult(Iterable self, @ClosureParams(FirstParam.Fi * @return the first non-null element from the iterable, or null * @since 4.0.9 */ + @SuppressWarnings("unchecked") public static T findResult(Iterable self) { return (T) findResult(self.iterator(), Closure.IDENTITY); } @@ -5982,7 +6515,7 @@ public static T findResult(Map self, U * @since 2.2.0 */ public static Collection findResults(Iterable self, @ClosureParams(FirstParam.FirstGenericType.class) Closure filteringTransform) { - return findResults(self.iterator(), filteringTransform); + return toList(findResults(self.iterator(), filteringTransform)); } /** @@ -5998,7 +6531,7 @@ public static Collection findResults(Iterable self, @ClosureParams( * @since 4.0.9 */ public static Collection findResults(Iterable self) { - return findResults(self.iterator(), Closure.IDENTITY); + return toList(findResults(self.iterator(), Closure.IDENTITY)); } /** @@ -6008,18 +6541,71 @@ public static Collection findResults(Iterable self) { * @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 the list of non-null transformed values + * @deprecated * @since 2.5.0 */ - public static Collection findResults(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure filteringTransform) { - List result = new ArrayList<>(); - while (self.hasNext()) { - U value = self.next(); - T transformed = filteringTransform.call(value); - if (transformed != null) { - result.add(transformed); + @Deprecated + public static Collection findResults$$bridge(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure filteringTransform) { + return toList(findResults(self, filteringTransform)); + } + + /** + * Iterates through the Iterator transforming items using the supplied closure + * and collecting any non-null results. + * + * @param self a source 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 Iterator findResults(Iterator self, @ClosureParams(FirstParam.FirstGenericType.class) Closure filteringTransform) { + return new FindResultsIterator<>(self, filteringTransform); + } + + private static final class FindResultsIterator implements Iterator { + private final Iterator source; + private final Closure transform; + private T current; + private boolean found; + + private FindResultsIterator(Iterator source, Closure transform) { + this.source = source; + this.transform = transform; + this.found = false; + advance(); + } + + @Override + public boolean hasNext() { + return found; + } + + private void advance() { + while (!found && source.hasNext()) { + U next = source.next(); + current = transform.call(next); + if (current != null) { + found = true; + return; + } } } - return result; + + @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(); + } } /** @@ -6030,7 +6616,7 @@ public static Collection findResults(Iterator self, @ClosureParams( * @since 4.0.9 */ public static Collection findResults(Iterator self) { - return findResults(self, Closure.IDENTITY); + return toList(findResults(self, Closure.IDENTITY)); } /** @@ -6504,8 +7090,8 @@ private static Collection flatten(Iterable elements, boolean flatte // flattenMany /** - * Flatten an Iterable. This Iterable and any nested arrays, collections or - * optionals have their contents (recursively) added to a new collection. + * Flatten an Iterable. This Iterable and any nested arrays, collections and + * optionals have their contents recursively added to a new collection. * A transform is applied to any leaf nodes before further flattening. *
      * var items = ['1', '2', 'foo', '3', 'bar']
@@ -6530,12 +7116,58 @@ private static  Collection flatten(Iterable elements, boolean flatte
      * @since 5.0.0
      */
     public static  Collection flattenMany(Iterable self, Closure transform) {
-        return flattenMany(self, (Collection) createSimilarCollection(self), transform);
+        return flattenMany(self, true, transform);
+    }
+
+    /**
+     * Flatten an Iterable. This Iterable and any nested arrays, collections and
+     * (optionally) optionals have their contents recursively added to a new collection.
+     * A transform is applied to any leaf nodes before further flattening.
+     * 
+     * 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']
+     *
+     * assert ['01/02/99', '12/12/23'].flattenMany{ it.split('/') } == ['01', '02', '99', '12', '12', '23']
+     * 
+ * The {@code flattenMany} method is somewhat similar to the {@code collectMany} method. + * While {@code collectMany} works on the basis that the transform closure returns a + * collection, {@code flattenMany} can return a collection, array, optional or modified element. + * You should use {@code flattenMany} if you need the extra functionality it offers. + * Consider using {@code collectMany} if you return only collections in the transform, since + * you will have better type inference in static typing scenarios. + * + * @param self an Iterable + * @param flattenOptionals whether non-present Optional values are removed + * @param transform a transform applied to any leaf elements + * @return a flattened Collection + * @see #collectMany(Iterable, Closure) + * @since 5.0.0 + */ + public static Collection flattenMany(Iterable self, boolean flattenOptionals, Closure transform) { + return flattenMany(self, flattenOptionals, (Collection) createSimilarCollection(self), transform); } /** * Flatten the elements from an Iterator. This iterator and any nested arrays, collections, - * iterators, or optionals have their contents (recursively) added to the elements for + * iterators, and 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 flattenUsing a transform applied to any leaf elements + * @return a flattened Collection + * @see #flattenMany(Iterable, Closure) + * @since 5.0.0 + */ + public static Iterator flattenMany(Iterator self, Closure flattenUsing) { + return new FlattenIterator<>(self, true, flattenUsing, true); + } + + /** + * Flatten the elements from an Iterator. This iterator and any nested arrays, collections, + * iterators, and (optionally) 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 @@ -6549,8 +7181,8 @@ public static Iterator flattenMany(Iterator self, boolean flattenOp return new FlattenIterator<>(self, flattenOptionals, flattenUsing, true); } - private static Collection flattenMany(Iterable self, Collection addTo, Closure flattenUsing) { - addAll(addTo, flattenMany(self.iterator(), true, flattenUsing)); + private static Collection flattenMany(Iterable self, boolean flattenOptionals, Collection addTo, Closure flattenUsing) { + addAll(addTo, flattenMany(self.iterator(), flattenOptionals, flattenUsing)); return addTo; } @@ -7704,8 +8336,14 @@ public static Map indexed(Iterable self, int offset) { *

* Example usage: *

-     * assert [[0, "a"], [1, "b"]] == ["a", "b"].iterator().indexed().collect{ tuple {@code ->} [tuple.first, tuple.second] }
-     * assert ["0: a", "1: b"] == ["a", "b"].iterator().indexed().collect { idx, str {@code ->} "$idx: $str" }.toList()
+     * assert ["a", "b"].iterator()
+     *     .indexed()
+     *     .collect{ tuple {@code ->} [tuple.first, tuple.second] }
+     *     .toList() == [[0, "a"], [1, "b"]]
+     * assert ["a", "b"].iterator()
+     *     .indexed()
+     *     .collect { idx, str {@code ->} "$idx: $str" }
+     *     .toList() == ["0: a", "1: b"]
      * 
* * @param self an iterator @@ -7719,11 +8357,11 @@ public static Iterator> indexed(Iterator self) { /** * Zips an iterator with indices in (index, value) order. - *

+ *

* Example usage: *

-     * assert [[5, "a"], [6, "b"]] == ["a", "b"].iterator().indexed(5).toList()
-     * assert ["a: 1", "b: 2"] == ["a", "b"].iterator().indexed(1).collect { idx, str {@code ->} "$str: $idx" }.toList()
+     * assert ['a', 'b'].iterator().indexed(5)*.toString() == ['[5, a]', '[6, b]']
+     * assert ['a', 'b'].iterator().indexed(1).collect { idx, str {@code ->} str + idx }.toList() == ['a1', 'b2']
      * 
* * @param self an iterator @@ -9382,7 +10020,7 @@ public static SortedSet leftShift(SortedSet self, T value) { * In case of bounded queue the method will block till space in the queue become available *
def list = new java.util.concurrent.LinkedBlockingQueue ()
      * list << 3 << 2 << 1
-     * assert list.iterator().collect{it} == [3,2,1]
+ * assert list.iterator().collect() == [3,2,1]
* * @param self a Collection * @param value an Object to be added to the collection. @@ -13823,7 +14461,7 @@ public static Map take(Map self, int num) { * def a = 0 * def iter = [ hasNext:{ true }, next:{ a++ } ] as Iterator * def iteratorCompare( Iterator a, List b ) { - * a.collect { it } == b + * a.collect() == b * } * assert iteratorCompare( iter.take( 0 ), [] ) * assert iteratorCompare( iter.take( 2 ), [ 0, 1 ] ) @@ -14500,6 +15138,25 @@ public static char toLowerCase(Character self) { return Character.toLowerCase(self); } + //-------------------------------------------------------------------------- + // toMap + + /** + * Equivalent to collectEntries() but doesn't call the identity transform. + * + * @param self a source Iterator of pairs or map entries + * @return a Map from the source pairs + * @see #collectEntries(Iterable) + * @since 5.0.0 + */ + public static Map toMap(Iterator self) { + Map collector = new LinkedHashMap<>(); + while (self.hasNext()) { + addEntry(collector, self.next()); + } + return collector; + } + //-------------------------------------------------------------------------- // toMapString @@ -17087,7 +17744,7 @@ public static Collection> zip(Iterable self, Iterable *
      * def small = [1, 2, 3].iterator()
      * def large = [100, 200, 300].iterator()
-     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + b }
+     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + b }.toList()
      * assert [small.toList(), large.toList()].transpose() == small.zip(large).toList()
      * 
* @@ -17109,7 +17766,7 @@ public static Iterator> zip(Iterator self, Iterator ot *
      * def small = [1, 2, 3].iterator()
      * def large = [100, 200, 300]
-     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + b }
+     * assert [101, 202, 303] == small.zip(large).collect{ a, b {@code ->} a + b }.toList()
      * 
* * @param self an Iterator @@ -17185,9 +17842,9 @@ public static Collection> zipAll(Iterable self, Iterable< * 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 } + * assert ['a1', 'b2', 'unknown3'] == ab.zipAll(nums, 'unknown', 0).collect{ a, b {@code ->} a + b }.toList() * nums = (1..3).iterator() - * assert ['a1', 'b2', 'c3', 'd0'] == abcd.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 }.toList() * * * @param left an Iterator @@ -17209,8 +17866,8 @@ public static Iterator> zipAll(Iterator left, Iterator * 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 } + * assert ['a1', 'b2', 'unknown3'] == ab.zipAll(nums, 'unknown', 0).collect{ a, b {@code ->} a + b }.toList() + * assert ['a1', 'b2', 'c3', 'd0'] == abcd.zipAll(nums, 'unknown', 0).collect{ a, b {@code ->} a + b }.toList() * * * @param left an Iterator diff --git a/src/test/groovy/groovy/GroovyMethodsTest.groovy b/src/test/groovy/groovy/GroovyMethodsTest.groovy index 5b8569be595..452f5b289b0 100644 --- a/src/test/groovy/groovy/GroovyMethodsTest.groovy +++ b/src/test/groovy/groovy/GroovyMethodsTest.groovy @@ -1499,7 +1499,7 @@ final class GroovyMethodsTest extends GroovyTestCase { void testCollectEntriesIterator() { def itr = ['a','bb','ccc'].iterator() def map = itr.collectEntries { [(it): it.size()] } - assert map == [a:1, bb:2, ccc:3] + assert map.collectEntries() == [a:1, bb:2, ccc:3] } void testCollectEntriesListReturn() { diff --git a/src/test/groovy/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy b/src/test/groovy/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy index d06982d27c6..47441708606 100644 --- a/src/test/groovy/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy @@ -831,7 +831,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { ''' assertScript ''' Iterator iter = ['foo', 'bar', 'baz'].iterator() - assert iter.collect { it.startsWith('ba') } == [false, true, true] + assert iter.collect { it.startsWith('ba') }.toList() == [false, true, true] ''' assertScript ''' assert [1234, 3.14].collect { it.intValue() } == [1234,3] @@ -880,7 +880,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { } void testDGM_collectEntriesIterator() { assertScript ''' - assert ['a','b','c'].iterator().collectEntries { [it, it.toUpperCase() ]} == [a:'A',b:'B',c:'C'] + assert ['a','b','c'].iterator().collectEntries { [it, it.toUpperCase() ]}.collectEntries() == [a:'A',b:'B',c:'C'] ''' } void testDGM_collectEntriesIteratorWithCollector() { @@ -947,7 +947,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { void testDGM_collectManyOnIterator() { assertScript ''' - assert (0..5).iterator().collectMany { [it, 2*it ]} == [0,0,1,2,2,4,3,6,4,8,5,10] + assert (0..5).iterator().collectMany { [it, 2*it ]}.toList() == [0,0,1,2,2,4,3,6,4,8,5,10] ''' } @@ -1334,7 +1334,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { assert items1.findIndexValues { String s -> s.startsWith('ba') } == [1, 2] def items2 = ['foo','bar','baz'] assert items2.findIndexValues { it.startsWith('ba') } == [1, 2] - assert items2.iterator().findIndexValues { it.startsWith('ba') } == [1, 2] + assert items2.iterator().findIndexValues { it.startsWith('ba') }.toList() == [1, 2] ''' } @@ -1477,7 +1477,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { assert items1.findResults { it.startsWith('ba') ? it : null } == ['bar', 'baz'] def items2 = ['foo','bar','baz'] assert items2.findResults { it.startsWith('ba') ? it : null } == ['bar', 'baz'] - assert items2.iterator().findResults { it.startsWith('ba') ? it : null } == ['bar', 'baz'] + assert items2.iterator().findResults { it.startsWith('ba') ? it : null }.toList() == ['bar', 'baz'] ''' } diff --git a/src/test/groovy/groovy/util/ObservableListTest.groovy b/src/test/groovy/groovy/util/ObservableListTest.groovy index f37a195aefd..8f278fd14ce 100644 --- a/src/test/groovy/groovy/util/ObservableListTest.groovy +++ b/src/test/groovy/groovy/util/ObservableListTest.groovy @@ -276,8 +276,8 @@ class ObservableListTest extends GroovyTestCase { void testListIterator() { def list = [1, 2, 3, 4, 5] as ObservableList - assert list.listIterator(2).collect { it } == [3, 4, 5] - assert list.listIterator().collect { it } == [1, 2, 3, 4, 5] + assert list.listIterator(2).collect() == [3, 4, 5] + assert list.listIterator().collect() == [1, 2, 3, 4, 5] } void testRetainAllBugGroovy4699() { diff --git a/src/test/groovy/groovy/util/ProxyGeneratorTest.groovy b/src/test/groovy/groovy/util/ProxyGeneratorTest.groovy index c411600ebe9..a55de5787dc 100644 --- a/src/test/groovy/groovy/util/ProxyGeneratorTest.groovy +++ b/src/test/groovy/groovy/util/ProxyGeneratorTest.groovy @@ -90,8 +90,8 @@ class ProxyGeneratorTest extends GroovyTestCase { def delegate = [1, 2, 3, 4, 5] def testClass = generator.instantiateDelegate([List], delegate) assert testClass instanceof List - assert 5 == testClass.size() - assert [1, 2, 3, 4, 5] == testClass.iterator().collect { it } + assert 5 == testClass.size() + assert [1, 2, 3, 4, 5] == testClass.iterator().collect() assert 3 == testClass[2] testClass[3] = 99 assert 99 == testClass[3] diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonLexerTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonLexerTest.groovy index f7f7e2b59ed..0aeeb6d3ea0 100644 --- a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonLexerTest.groovy +++ b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonLexerTest.groovy @@ -63,7 +63,7 @@ class JsonLexerTest extends GroovyTestCase { def content = ' [ true, null, false, { "a" : 1, "b": "hi"}, 12.34 ] ' def lexer = new JsonLexer(new StringReader(content)) - def output = lexer.collect { it.toString() } + def output = lexer.collect { it.toString() }.toList() assert output == [ "[ (OPEN_BRACKET) [1:2-1:3]", diff --git a/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy b/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy index e91aad8a639..b6d9638e44d 100644 --- a/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy +++ b/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy @@ -136,9 +136,9 @@ class UserGuideXmlSlurperTest extends GroovyTestCase { def withId2or3 = { node -> node.@id in [2, 3] } assert ['book', 'author', 'book', 'author'] == - response.value.books.depthFirst().findAll(withId2or3).collect(nodeName) + response.value.books.depthFirst().findAll(withId2or3).collect(nodeName).toList() assert ['book', 'book', 'author', 'author'] == - response.value.books.breadthFirst().findAll(withId2or3).collect(nodeName) + response.value.books.breadthFirst().findAll(withId2or3).collect(nodeName).toList() // end::testDepthVsBreadth[] }