Repository: groovy Updated Branches: refs/heads/master c89393104 -> 047c8f29b
GROOVY-8406: Various DefaultGroovyMethods missing Array variants resulting in no type inference Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/047c8f29 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/047c8f29 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/047c8f29 Branch: refs/heads/master Commit: 047c8f29b1f7a1a98b2b003e4d09c1cef05feb0e Parents: c893931 Author: paulk <[email protected]> Authored: Tue Dec 12 01:14:26 2017 +1000 Committer: paulk <[email protected]> Committed: Tue Dec 19 17:31:11 2017 +1000 ---------------------------------------------------------------------- .../groovy/runtime/DefaultGroovyMethods.java | 1012 +++++++++++++----- .../stc/ClosureParamTypeInferenceSTCTest.groovy | 98 +- 2 files changed, 856 insertions(+), 254 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/047c8f29/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index eec02af..3037fea 100644 --- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -2445,19 +2445,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * def greaterThanTwo = list.every { it > 2 } * </pre> * - * @param self the object over which we iterate - * @param closure the closure predicate used for matching + * @param self the object over which we iterate + * @param predicate the closure predicate used for matching * @return true if every iteration of the object matches the closure predicate * @since 1.0 */ - public static boolean every(Object self, Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { - if (!bcw.call(iter.next())) { - return false; - } - } - return true; + public static boolean every(Object self, Closure predicate) { + return every(InvokerHelper.asIterator(self), predicate); } /** @@ -2468,13 +2462,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * def greaterThanTwo = list.iterator().every { it > 2 } * </pre> * - * @param self the iterator over which we iterate - * @param closure the closure predicate used for matching + * @param self the iterator over which we iterate + * @param predicate the closure predicate used for matching * @return true if every iteration of the object matches the closure predicate * @since 2.3.0 */ - public static <T> boolean every(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); + public static <T> boolean every(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure predicate) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(predicate); while (self.hasNext()) { if (!bcw.call(self.next())) { return false; @@ -2485,19 +2479,32 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { /** * Used to determine if the given predicate closure is valid (i.e. returns + * <code>true</code> for all items in this Array). + * + * @param self an Array + * @param predicate the closure predicate used for matching + * @return true if every element of the Array matches the closure predicate + * @since 2.5.0 + */ + public static <T> boolean every(T[] self, @ClosureParams(FirstParam.Component.class) Closure predicate) { + return every(new ArrayIterator<T>(self), predicate); + } + + /** + * Used to determine if the given predicate closure is valid (i.e. returns * <code>true</code> for all items in this iterable). * A simple example for a list: * <pre>def list = [3,4,5] * def greaterThanTwo = list.every { it > 2 } * </pre> * - * @param self the iterable over which we iterate - * @param closure the closure predicate used for matching + * @param self the iterable over which we iterate + * @param predicate the closure predicate used for matching * @return true if every iteration of the object matches the closure predicate * @since 2.3.0 */ - public static <T> boolean every(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { - return every(self.iterator(), closure); + public static <T> boolean every(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure predicate) { + return every(self.iterator(), predicate); } /** @@ -2510,13 +2517,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert !map.every { key, value -> value instanceof Integer } * assert map.every { entry -> entry.value instanceof Number }</pre> * - * @param self the map over which we iterate - * @param closure the 1 or 2 arg Closure predicate used for matching + * @param self the map over which we iterate + * @param predicate the 1 or 2 arg Closure predicate used for matching * @return true if every entry of the map matches the closure predicate * @since 1.5.0 */ - public static <K, V> boolean every(Map<K, V> self, @ClosureParams(value=MapEntryOrKeyValue.class) Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); + public static <K, V> boolean every(Map<K, V> self, @ClosureParams(value = MapEntryOrKeyValue.class) Closure predicate) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(predicate); for (Map.Entry<K, V> entry : self.entrySet()) { if (!bcw.callForMap(entry)) { return false; @@ -2536,13 +2543,12 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * </pre> * * @param self the object over which we iterate - * @return true if every item in the collection matches the closure - * predicate + * @return true if every item in the collection matches satisfies Groovy truth * @since 1.5.0 */ public static boolean every(Object self) { BooleanReturningMethodInvoker bmi = new BooleanReturningMethodInvoker(); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { + for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); ) { if (!bmi.convertToBoolean(iter.next())) { return false; } @@ -2558,17 +2564,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert ![1, 2, 3].any { it > 3 } * </pre> * - * @param self the object over which we iterate - * @param closure the closure predicate used for matching - * @return true if any iteration for the object matches the closure predicate + * @param self the object over which we iterate + * @param predicate the closure predicate used for matching + * @return true if any iteration for the object matches the closure predicate * @since 1.0 */ - public static boolean any(Object self, Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { - if (bcw.call(iter.next())) return true; - } - return false; + public static boolean any(Object self, Closure predicate) { + return any(InvokerHelper.asIterator(self), predicate); } /** @@ -2579,15 +2581,15 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert ![1, 2, 3].iterator().any { it > 3 } * </pre> * - * @param self the iterator over which we iterate - * @param closure the closure predicate used for matching - * @return true if any iteration for the object matches the closure predicate + * @param self the iterator over which we iterate + * @param predicate the closure predicate used for matching + * @return true if any iteration for the object matches the closure predicate * @since 1.0 */ - public static <T> boolean any(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = self; iter.hasNext();) { - if (bcw.call(iter.next())) return true; + public static <T> boolean any(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure predicate) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(predicate); + while (self.hasNext()) { + if (bcw.call(self.next())) return true; } return false; } @@ -2600,17 +2602,26 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert ![1, 2, 3].any { it > 3 } * </pre> * - * @param self the iterable over which we iterate - * @param closure the closure predicate used for matching - * @return true if any iteration for the object matches the closure predicate + * @param self the iterable over which we iterate + * @param predicate the closure predicate used for matching + * @return true if any iteration for the object matches the closure predicate * @since 1.0 */ - public static <T> boolean any(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator<T> iter = self.iterator(); iter.hasNext();) { - if (bcw.call(iter.next())) return true; - } - return false; + public static <T> boolean any(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure predicate) { + return any(self.iterator(), predicate); + } + + /** + * Iterates over the contents of an Array, and checks whether a + * predicate is valid for at least one element. + * + * @param self the array over which we iterate + * @param predicate the closure predicate used for matching + * @return true if any iteration for the object matches the closure predicate + * @since 2.5.0 + */ + public static <T> boolean any(T[] self, @ClosureParams(FirstParam.Component.class) Closure predicate) { + return any(new ArrayIterator<T>(self), predicate); } /** @@ -2624,13 +2635,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert ![2:3, 4:5, 5:10].any { entry -> entry.key == entry.value * 2 } * </pre> * - * @param self the map over which we iterate - * @param closure the 1 or 2 arg closure predicate used for matching + * @param self the map over which we iterate + * @param predicate the 1 or 2 arg closure predicate used for matching * @return true if any entry in the map matches the closure predicate * @since 1.5.0 */ - public static <K, V> boolean any(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<?> closure) { - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); + public static <K, V> boolean any(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<?> predicate) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(predicate); for (Map.Entry<K, V> entry : self.entrySet()) { if (bcw.callForMap(entry)) { return true; @@ -3354,6 +3365,20 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** + * Iterates through this aggregate Object transforming each item into a new value using Closure.IDENTITY + * as a transformer, basically returning a list of items copied from the original object. + * <pre class="groovyTestCase">assert [1,2,3] == [1,2,3].iterator().collect()</pre> + * + * @param self an aggregate Object with an Iterator returning its items + * @return a Collection of the transformed values + * @see Closure#IDENTITY + * @since 1.8.5 + */ + public static Collection collect(Object self) { + return collect(self, Closure.IDENTITY); + } + + /** * Iterates through this aggregate Object transforming each item into a new value using the * <code>transform</code> closure, returning a list of transformed values. * Example: @@ -3371,62 +3396,163 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Iterates through this aggregate Object transforming each item into a new value using Closure.IDENTITY - * as a transformer, basically returning a list of items copied from the original object. - * <pre class="groovyTestCase">assert [1,2,3] == [1,2,3].iterator().collect()</pre> + * Iterates through this aggregate Object transforming each item into a new value using the <code>transform</code> closure + * and adding it to the supplied <code>collector</code>. * - * @param self an aggregate Object with an Iterator returning its items + * @param self an aggregate Object with an Iterator returning its items + * @param collector the Collection to which the transformed values are added + * @param transform the closure used to transform each item of the aggregate object + * @return the collector with all transformed values added to it + * @since 1.0 + */ + public static <T> Collection<T> collect(Object self, Collection<T> collector, Closure<? extends T> transform) { + return collect(InvokerHelper.asIterator(self), collector, transform); + } + + /** + * Iterates through this Array transforming each item into a new value using the + * <code>transform</code> closure, returning a list of transformed values. + * + * @param self an Array + * @param transform the closure used to transform each item of the Array * @return a List of the transformed values - * @see Closure#IDENTITY - * @since 1.8.5 + * @since 2.5.0 */ - public static Collection collect(Object self) { - return collect(self, Closure.IDENTITY); + public static <S,T> List<T> collect(S[] self, @ClosureParams(FirstParam.Component.class) Closure<T> transform) { + return collect(new ArrayIterator<S>(self), transform); } /** - * Iterates through this aggregate Object transforming each item into a new value using the <code>transform</code> closure + * Iterates through this Array transforming each item into a new value using the <code>transform</code> closure * and adding it to the supplied <code>collector</code>. + * <pre class="groovyTestCase"> + * Integer[] nums = [1,2,3] + * List<Integer> answer = [] + * nums.collect(answer) { it * 2 } + * assert [2,4,6] == answer + * </pre> * - * @param self an aggregate Object with an Iterator returning its items + * @param self an Array * @param collector the Collection to which the transformed values are added - * @param transform the closure used to transform each item of the aggregate object + * @param transform the closure used to transform each item * @return the collector with all transformed values added to it - * @since 1.0 + * @since 2.5.0 */ - public static <T> Collection<T> collect(Object self, Collection<T> collector, Closure<? extends T> transform) { - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); ) { - collector.add(transform.call(iter.next())); + public static <S,T> Collection<T> collect(S[] self, Collection<T> collector, @ClosureParams(FirstParam.Component.class) Closure<? extends T> transform) { + return collect(new ArrayIterator<S>(self), collector, transform); + } + + /** + * Iterates through this Iterator transforming each item into a new value using the + * <code>transform</code> closure, returning a list of transformed values. + * + * @param self an Iterator + * @param transform the closure used to transform each item + * @return a List of the transformed values + * @since 2.5.0 + */ + public static <S,T> List<T> collect(Iterator<S> self, @ClosureParams(FirstParam.Component.class) Closure<T> transform) { + return (List<T>) collect(self, new ArrayList<T>(), transform); + } + + /** + * 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>. + * + * @param self an Iterator + * @param collector the Collection to which the transformed values are added + * @param transform the closure used to transform each item + * @return the collector with all transformed values added to it + * @since 2.5.0 + */ + public static <S,T> Collection<T> collect(Iterator<S> self, Collection<T> collector, @ClosureParams(FirstParam.FirstGenericType.class) Closure<? extends T> transform) { + while (self.hasNext()) { + collector.add(transform.call(self.next())); } return collector; } /** + * 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. + * <pre class="groovyTestCase">assert [1,2,3] == [1,2,3].collect()</pre> + * + * @param self a collection + * @return a List of the transformed values + * @see Closure#IDENTITY + * @since 1.8.5 + * @deprecated use the Iterable version instead + */ + @Deprecated + public static <T> List<T> collect(Collection<T> self) { + return collect((Iterable<T>) self); + } + + /** * Iterates through this collection transforming each entry into a new value using the <code>transform</code> closure * returning a list of transformed values. - * <pre class="groovyTestCase">assert [2,4,6] == [1,2,3].collect { it * 2 }</pre> * * @param self a collection * @param transform the closure used to transform each item of the collection * @return a List of the transformed values + * @deprecated use the Iterable version instead * @since 1.0 */ + @Deprecated public static <S,T> List<T> collect(Collection<S> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> transform) { return (List<T>) collect(self, new ArrayList<T>(self.size()), transform); } /** + * Iterates through this collection transforming each value into a new value using the <code>transform</code> closure + * and adding it to the supplied <code>collector</code>. + * <pre class="groovyTestCase">assert [1,2,3] as HashSet == [2,4,5,6].collect(new HashSet()) { (int)(it / 2) }</pre> + * + * @param self a collection + * @param collector the Collection to which the transformed values are added + * @param transform the closure used to transform each item of the collection + * @return the collector with all transformed values added to it + * @deprecated use the Iterable version instead + * @since 1.0 + */ + @Deprecated + public static <S,T> Collection<T> collect(Collection<S> self, Collection<T> collector, @ClosureParams(FirstParam.FirstGenericType.class) Closure<? extends T> transform) { + for (S item : self) { + collector.add(transform.call(item)); + if (transform.getDirective() == Closure.DONE) { + break; + } + } + return collector; + } + + /** * 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. * <pre class="groovyTestCase">assert [1,2,3] == [1,2,3].collect()</pre> * - * @param self a collection + * @param self an Iterable * @return a List of the transformed values - * @since 1.8.5 * @see Closure#IDENTITY + * @since 2.5.0 */ - public static <T> List<T> collect(Collection<T> self) { - return (List<T>) collect(self, Closure.IDENTITY); + @SuppressWarnings("unchecked") + public static <T> List<T> collect(Iterable<T> self) { + return collect(self, (Closure<T>) Closure.IDENTITY); + } + + /** + * Iterates through this Iterable transforming each entry into a new value using the <code>transform</code> closure + * returning a list of transformed values. + * <pre class="groovyTestCase">assert [2,4,6] == [1,2,3].collect { it * 2 }</pre> + * + * @param self an Iterable + * @param transform the closure used to transform each item of the collection + * @return a List of the transformed values + * @since 2.5.0 + */ + public static <S,T> List<T> collect(Iterable<S> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> transform) { + return (List<T>) collect(self.iterator(), transform); } /** @@ -3434,14 +3560,14 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * and adding it to the supplied <code>collector</code>. * <pre class="groovyTestCase">assert [1,2,3] as HashSet == [2,4,5,6].collect(new HashSet()) { (int)(it / 2) }</pre> * - * @param self a collection + * @param self an Iterable * @param collector the Collection to which the transformed values are added - * @param transform the closure used to transform each item of the collection + * @param transform the closure used to transform each item * @return the collector with all transformed values added to it - * @since 1.0 + * @since 2.5.0 */ - public static <T,E> Collection<T> collect(Collection<E> self, Collection<T> collector, @ClosureParams(FirstParam.FirstGenericType.class) Closure<? extends T> transform) { - for (E item : self) { + public static <S,T> Collection<T> collect(Iterable<S> self, Collection<T> collector, @ClosureParams(FirstParam.FirstGenericType.class) Closure<? extends T> transform) { + for (S item : self) { collector.add(transform.call(item)); if (transform.getDirective() == Closure.DONE) { break; @@ -4121,52 +4247,6 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Treats the object as iterable, iterating through the values it represents and returns the first non-null result obtained from calling the closure, otherwise returns the defaultResult. - * - * <pre class="groovyTestCase"> - * int[] numbers = [1, 2, 3] - * assert numbers.findResult(5) { if(it > 1) return it } == 2 - * assert numbers.findResult(5) { if(it > 4) return it } == 5 - * </pre> - * - * @param self an Object with an iterator returning its values - * @param defaultResult an Object that should be returned if all closure results are null - * @param closure a closure that returns a non-null value when processing should stop - * @return the first non-null result of the closure, otherwise the default value - * @since 1.7.5 - */ - public static Object findResult(Object self, Object defaultResult, Closure closure) { - Object result = findResult(self, closure); - if (result == null) return defaultResult; - return result; - } - - /** - * Treats the object as iterable, iterating through the values it represents and returns the first non-null result obtained from calling the closure, otherwise returns null. - * - * <pre class="groovyTestCase"> - * int[] numbers = [1, 2, 3] - * assert numbers.findResult { if(it > 1) return it } == 2 - * assert numbers.findResult { if(it > 4) return it } == null - * </pre> - * - * @param self an Object with an iterator returning its values - * @param closure a closure that returns a non-null value when processing should stop - * @return the first non-null result of the closure - * @since 1.7.5 - */ - public static Object findResult(Object self, Closure closure) { - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { - Object value = iter.next(); - Object result = closure.call(value); - if (result != null) { - return result; - } - } - return null; - } - - /** * Finds the first value matching the closure condition. Example: * <pre class="groovyTestCase">def list = [1,2,3] * assert 2 == list.find { it > 1 } @@ -4230,46 +4310,206 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** + * Treats the object as iterable, iterating through the values it represents and returns the first non-null result obtained from calling the closure, otherwise returns null. + * <p> + * <pre class="groovyTestCase"> + * int[] numbers = [1, 2, 3] + * assert numbers.findResult { if(it > 1) return it } == 2 + * assert numbers.findResult { if(it > 4) return it } == null + * </pre> + * + * @param self an Object with an iterator returning its values + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result of the closure + * @since 1.7.5 + */ + public static Object findResult(Object self, Closure condition) { + for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); ) { + Object value = iter.next(); + Object result = condition.call(value); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Treats the object as iterable, iterating through the values it represents and returns the first non-null result obtained from calling the closure, otherwise returns the defaultResult. + * <p> + * <pre class="groovyTestCase"> + * int[] numbers = [1, 2, 3] + * assert numbers.findResult(5) { if(it > 1) return it } == 2 + * assert numbers.findResult(5) { if(it > 4) return it } == 5 + * </pre> + * + * @param self an Object with an iterator returning its values + * @param defaultResult an Object that should be returned if all closure results are null + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result of the closure, otherwise the default value + * @since 1.7.5 + */ + public static Object findResult(Object self, Object defaultResult, Closure condition) { + Object result = findResult(self, condition); + if (result == null) return defaultResult; + return result; + } + + /** + * Iterates through the collection calling the given closure for each item but stopping once the first non-null + * result is found and returning that result. If all are null, the defaultResult is returned. + * + * @param self a Collection + * @param defaultResult an Object that should be returned if all closure results are null + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or the defaultValue + * @since 1.7.5 + * @deprecated use the Iterable version instead + */ + @Deprecated + public static <S, T, U extends T, V extends T> T findResult(Collection<S> self, U defaultResult, @ClosureParams(FirstParam.FirstGenericType.class) Closure<V> condition) { + return findResult((Iterable<S>) self, defaultResult, condition); + } + + /** * Iterates through the collection calling the given closure for each item but stopping once the first non-null + * result is found and returning that result. If all results are null, null is returned. + * + * @param self a Collection + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or null + * @since 1.7.5 + * @deprecated use the Iterable version instead + */ + @Deprecated + public static <S,T> T findResult(Collection<S> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> condition) { + return findResult((Iterable<S>) self, condition); + } + + /** + * Iterates through the Iterator calling the given closure condition for each item but stopping once the first non-null * result is found and returning that result. If all are null, the defaultResult is returned. * <p> * Examples: * <pre class="groovyTestCase"> - * def list = [1,2,3] - * assert "Found 2" == list.findResult("default") { it > 1 ? "Found $it" : null } - * assert "default" == list.findResult("default") { it > 3 ? "Found $it" : null } + * def iter = [1,2,3].iterator() + * assert "Found 2" == iter.findResult("default") { it > 1 ? "Found $it" : null } + * assert "default" == iter.findResult("default") { it > 3 ? "Found $it" : null } * </pre> * - * @param self a Collection + * @param self an Iterator * @param defaultResult an Object that should be returned if all closure results are null - * @param closure a closure that returns a non-null value when processing should stop and a value should be returned + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned * @return the first non-null result from calling the closure, or the defaultValue - * @since 1.7.5 + * @since 2.5.0 */ - public static <T, U extends T, V extends T,E> T findResult(Collection<E> self, U defaultResult, @ClosureParams(FirstParam.FirstGenericType.class) Closure<V> closure) { - T result = findResult(self, closure); + public static <S, T, U extends T, V extends T> T findResult(Iterator<S> self, U defaultResult, @ClosureParams(FirstParam.FirstGenericType.class) Closure<V> condition) { + T result = findResult(self, condition); if (result == null) return defaultResult; return result; } /** - * Iterates through the collection calling the given closure for each item but stopping once the first non-null + * Iterates through the Iterator calling the given closure condition for each item but stopping once the first non-null * result is found and returning that result. If all results are null, null is returned. + * + * @param self an Iterator + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or null + * @since 2.5.0 + */ + public static <T, U> T findResult(Iterator<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> condition) { + while (self.hasNext()) { + U next = self.next(); + T result = condition.call(next); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Iterates through the Iterable calling the given closure condition for each item but stopping once the first non-null + * result is found and returning that result. If all are null, the defaultResult is returned. * <p> - * Example: + * Examples: * <pre class="groovyTestCase"> * def list = [1,2,3] - * assert "Found 2" == list.findResult { it > 1 ? "Found $it" : null } + * assert "Found 2" == list.findResult("default") { it > 1 ? "Found $it" : null } + * assert "default" == list.findResult("default") { it > 3 ? "Found $it" : null } * </pre> * - * @param self a Collection - * @param closure a closure that returns a non-null value when processing should stop and a value should be returned + * @param self an Iterable + * @param defaultResult an Object that should be returned if all closure results are null + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or the defaultValue + * @since 2.5.0 + */ + public static <S, T, U extends T, V extends T> T findResult(Iterable<S> self, U defaultResult, @ClosureParams(FirstParam.FirstGenericType.class) Closure<V> condition) { + T result = findResult(self, condition); + if (result == null) return defaultResult; + return result; + } + + /** + * Iterates through the Iterable calling the given closure condition for each item but stopping once the first non-null + * result is found and returning that result. If all results are null, null is returned. + * + * @param self an Iterable + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or null + * @since 2.5.0 + */ + public static <T, U> T findResult(Iterable<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> condition) { + return findResult(self.iterator(), condition); + } + + /** + * Iterates through the Array calling the given closure condition for each item but stopping once the first non-null + * result is found and returning that result. If all are null, the defaultResult is returned. + * + * @param self an Array + * @param defaultResult an Object that should be returned if all closure results are null + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned + * @return the first non-null result from calling the closure, or the defaultValue + * @since 2.5.0 + */ + public static <S, T, U extends T, V extends T> T findResult(S[] self, U defaultResult, @ClosureParams(FirstParam.Component.class) Closure<V> condition) { + return findResult(new ArrayIterator<S>(self), defaultResult, condition); + } + + /** + * Iterates through the Array calling the given closure condition for each item but stopping once the first non-null + * result is found and returning that result. If all results are null, null is returned. + * + * @param self an Array + * @param condition a closure that returns a non-null value to indicate that processing should stop and the value should be returned * @return the first non-null result from calling the closure, or null + * @since 2.5.0 + */ + public static <S, T> T findResult(S[] self, @ClosureParams(FirstParam.Component.class) Closure<T> condition) { + return findResult(new ArrayIterator<S>(self), condition); + } + + /** + * Returns the first non-null closure result found by passing each map entry to the closure, otherwise null is returned. + * If the closure takes two parameters, the entry key and value are passed. + * If the closure takes one parameter, the Map.Entry object is passed. + * <pre class="groovyTestCase"> + * assert "Found b:3" == [a:1, b:3].findResult { if (it.value == 3) return "Found ${it.key}:${it.value}" } + * assert null == [a:1, b:3].findResult { if (it.value == 9) return "Found ${it.key}:${it.value}" } + * assert "Found a:1" == [a:1, b:3].findResult { k, v -> if (k.size() + v == 2) return "Found $k:$v" } + * </pre> + * + * @param self a Map + * @param condition a 1 or 2 arg Closure that returns a non-null value when processing should stop and a value should be returned + * @return the first non-null result collected by calling the closure, or null if no such result was found * @since 1.7.5 */ - public static <T,U> T findResult(Collection<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> closure) { - for (Object value : self) { - T result = closure.call(value); + public static <T, K, V> T findResult(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<T> condition) { + for (Map.Entry<K, V> entry : self.entrySet()) { + T result = callClosureForMapEntry(condition, entry); if (result != null) { return result; } @@ -4278,13 +4518,35 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * @deprecated Use the Iterable version of findResults instead + * Returns the first non-null closure result found by passing each map entry to the closure, otherwise the defaultResult is returned. + * If the closure takes two parameters, the entry key and value are passed. + * If the closure takes one parameter, the Map.Entry object is passed. + * <pre class="groovyTestCase"> + * assert "Found b:3" == [a:1, b:3].findResult("default") { if (it.value == 3) return "Found ${it.key}:${it.value}" } + * assert "default" == [a:1, b:3].findResult("default") { if (it.value == 9) return "Found ${it.key}:${it.value}" } + * assert "Found a:1" == [a:1, b:3].findResult("default") { k, v -> if (k.size() + v == 2) return "Found $k:$v" } + * </pre> + * + * @param self a Map + * @param defaultResult an Object that should be returned if all closure results are null + * @param condition a 1 or 2 arg Closure that returns a non-null value when processing should stop and a value should be returned + * @return the first non-null result collected by calling the closure, or the defaultResult if no such result was found + * @since 1.7.5 + */ + public static <T, U extends T, V extends T, A, B> T findResult(Map<A, B> self, U defaultResult, @ClosureParams(MapEntryOrKeyValue.class) Closure<V> condition) { + T result = findResult(self, condition); + if (result == null) return defaultResult; + return result; + } + + /** * @see #findResults(Iterable, Closure) * @since 1.8.1 + * @deprecated Use the Iterable version of findResults instead */ @Deprecated - public static <T,U> Collection<T> findResults(Collection<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> filteringTransform) { - return findResults((Iterable<?>)self, filteringTransform); + public static <T, U> Collection<T> findResults(Collection<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> filteringTransform) { + return findResults((Iterable<?>) self, filteringTransform); } /** @@ -4298,14 +4560,28 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * assert result == ["Found 2", "Found 3"] * </pre> * - * @param self an Iterable + * @param self an Iterable + * @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 + * @since 2.2.0 + */ + public static <T, U> Collection<T> findResults(Iterable<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> filteringTransform) { + return findResults(self.iterator(), filteringTransform); + } + + /** + * Iterates through the Iterator transforming items using the supplied closure + * and collecting any non-null results. + * + * @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 - * @since 2.2.0 + * @since 2.5.0 */ - public static <T,U> Collection<T> findResults(Iterable<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> filteringTransform) { + public static <T, U> Collection<T> findResults(Iterator<U> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure<T> filteringTransform) { List<T> result = new ArrayList<T>(); - for (Object value : self) { + while (self.hasNext()) { + U value = self.next(); T transformed = filteringTransform.call(value); if (transformed != null) { result.add(transformed); @@ -4315,6 +4591,19 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** + * Iterates through the Array transforming items using the supplied closure + * and collecting any non-null results. + * + * @param self an Array + * @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 + * @since 2.5.0 + */ + public static <T, U> Collection<T> findResults(U[] self, @ClosureParams(FirstParam.Component.class) Closure<T> filteringTransform) { + return findResults(new ArrayIterator<U>(self), filteringTransform); + } + + /** * Iterates through the map transforming items using the supplied closure * and collecting any non-null results. * If the closure takes two parameters, the entry key and value are passed. @@ -4332,7 +4621,7 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * @return the list of non-null transformed values * @since 1.8.1 */ - public static <T,K,V> Collection<T> findResults(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<T> filteringTransform) { + public static <T, K, V> Collection<T> findResults(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<T> filteringTransform) { List<T> result = new ArrayList<T>(); for (Map.Entry<K, V> entry : self.entrySet()) { T transformed = callClosureForMapEntry(filteringTransform, entry); @@ -4365,53 +4654,6 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Returns the first non-null closure result found by passing each map entry to the closure, otherwise the defaultResult is returned. - * If the closure takes two parameters, the entry key and value are passed. - * If the closure takes one parameter, the Map.Entry object is passed. - * <pre class="groovyTestCase"> - * assert "Found b:3" == [a:1, b:3].findResult("default") { if (it.value == 3) return "Found ${it.key}:${it.value}" } - * assert "default" == [a:1, b:3].findResult("default") { if (it.value == 9) return "Found ${it.key}:${it.value}" } - * assert "Found a:1" == [a:1, b:3].findResult("default") { k, v -> if (k.size() + v == 2) return "Found $k:$v" } - * </pre> - * - * @param self a Map - * @param defaultResult an Object that should be returned if all closure results are null - * @param closure a 1 or 2 arg Closure that returns a non-null value when processing should stop and a value should be returned - * @return the first non-null result collected by calling the closure, or the defaultResult if no such result was found - * @since 1.7.5 - */ - public static <T, U extends T, V extends T,A,B> T findResult(Map<A, B> self, U defaultResult, @ClosureParams(MapEntryOrKeyValue.class) Closure<V> closure) { - T result = findResult(self, closure); - if (result == null) return defaultResult; - return result; - } - - /** - * Returns the first non-null closure result found by passing each map entry to the closure, otherwise null is returned. - * If the closure takes two parameters, the entry key and value are passed. - * If the closure takes one parameter, the Map.Entry object is passed. - * <pre class="groovyTestCase"> - * assert "Found b:3" == [a:1, b:3].findResult { if (it.value == 3) return "Found ${it.key}:${it.value}" } - * assert null == [a:1, b:3].findResult { if (it.value == 9) return "Found ${it.key}:${it.value}" } - * assert "Found a:1" == [a:1, b:3].findResult { k, v -> if (k.size() + v == 2) return "Found $k:$v" } - * </pre> - * - * @param self a Map - * @param closure a 1 or 2 arg Closure that returns a non-null value when processing should stop and a value should be returned - * @return the first non-null result collected by calling the closure, or null if no such result was found - * @since 1.7.5 - */ - public static <T,K,V> T findResult(Map<K, V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<T> closure) { - for (Map.Entry<K, V> entry : self.entrySet()) { - T result = callClosureForMapEntry(closure, entry); - if (result != null) { - return result; - } - } - return null; - } - - /** * Finds all values matching the closure condition. * <pre class="groovyTestCase">assert ([2,4] as Set) == ([1,2,3,4] as Set).findAll { it % 2 == 0 }</pre> * @@ -4872,6 +5114,23 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { return split(closure, accept, reject, iter); } + /** + * Splits all items into two collections based on the closure condition. + * The first list contains all items which match the closure expression. + * The second list all those that don't. + * + * @param self an Array + * @param closure a closure condition + * @return a List whose first item is the accepted values and whose second item is the rejected values + * @since 2.5.0 + */ + public static <T> Collection<Collection<T>> split(T[] self, @ClosureParams(FirstParam.Component.class) Closure closure) { + List<T> accept = new ArrayList<T>(); + List<T> reject = new ArrayList<T>(); + Iterator<T> iter = new ArrayIterator<T>(self); + return split(closure, accept, reject, iter); + } + private static <T> Collection<Collection<T>> split(Closure closure, Collection<T> accept, Collection<T> reject, Iterator<T> iter) { List<Collection<T>> answer = new ArrayList<Collection<T>>(); BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); @@ -6181,8 +6440,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item of the Iterable. * @since 2.2.0 */ - public static Object sum(Iterable self, Closure closure) { - return sum(self, null, closure, true); + public static <T> Object sum(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { + return sum(self.iterator(), null, closure, true); } /** @@ -6196,8 +6455,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item of the array. * @since 1.7.1 */ - public static Object sum(Object[] self, Closure closure) { - return sum(toList(self), null, closure, true); + public static <T> Object sum(T[] self, @ClosureParams(FirstParam.Component.class) Closure closure) { + return sum(new ArrayIterator<T>(self), null, closure, true); } /** @@ -6212,8 +6471,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item from the Iterator. * @since 1.7.1 */ - public static Object sum(Iterator<Object> self, Closure closure) { - return sum(toList(self), null, closure, true); + public static <T> Object sum(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { + return sum(self, null, closure, true); } /** @@ -6239,8 +6498,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item of the collection. * @since 1.5.0 */ - public static Object sum(Iterable self, Object initialValue, Closure closure) { - return sum(self, initialValue, closure, false); + public static <T> Object sum(Iterable<T> self, Object initialValue, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { + return sum(self.iterator(), initialValue, closure, false); } /** @@ -6255,8 +6514,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item of the array. * @since 1.7.1 */ - public static Object sum(Object[] self, Object initialValue, Closure closure) { - return sum(toList(self), initialValue, closure, false); + public static <T> Object sum(T[] self, Object initialValue, @ClosureParams(FirstParam.Component.class) Closure closure) { + return sum(new ArrayIterator<T>(self), initialValue, closure, false); } /** @@ -6272,16 +6531,16 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * item from the Iterator. * @since 1.7.1 */ - public static Object sum(Iterator<Object> self, Object initialValue, Closure closure) { - return sum(toList(self), initialValue, closure, false); + public static <T> Object sum(Iterator<T> self, Object initialValue, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { + return sum(self, initialValue, closure, false); } - private static Object sum(Iterable self, Object initialValue, Closure closure, boolean first) { + private static <T> Object sum(Iterator<T> self, Object initialValue, Closure closure, boolean first) { Object result = initialValue; Object[] closureParam = new Object[1]; Object[] plusParam = new Object[1]; - for (Object next : self) { - closureParam[0] = next; + while (self.hasNext()) { + closureParam[0] = self.next(); plusParam[0] = closure.call(closureParam); if (first) { result = plusParam[0]; @@ -16047,40 +16306,68 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Iterates over the elements of an iterable collection of items and returns + * Iterates over the elements of an aggregate of items and returns * the index of the first item that matches the condition specified in the closure. * - * @param self the iteration object over which to iterate - * @param closure the filter to perform a match on the collection + * @param self the iteration object over which to iterate + * @param condition the matching condition * @return an integer that is the index of the first matched object or -1 if no match was found * @since 1.0 */ - public static int findIndexOf(Object self, Closure closure) { - return findIndexOf(self, 0, closure); + public static int findIndexOf(Object self, Closure condition) { + return findIndexOf(self, 0, condition); } /** - * Iterates over the elements of an iterable collection of items, starting from a + * Iterates over the elements of an aggregate of items, starting from a * specified startIndex, and returns the index of the first item that matches the * condition specified in the closure. * * @param self the iteration object over which to iterate * @param startIndex start matching from this index - * @param closure the filter to perform a match on the collection + * @param condition the matching condition * @return an integer that is the index of the first matched object or -1 if no match was found * @since 1.5.0 */ - public static int findIndexOf(Object self, int startIndex, Closure closure) { + public static int findIndexOf(Object self, int startIndex, Closure condition) { + return findIndexOf(InvokerHelper.asIterator(self), condition); + } + + /** + * Iterates over the elements of an Iterator and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Iterator + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexOf(self, 0, condition); + } + + /** + * Iterates over the elements of an Iterator, starting from a + * specified startIndex, and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Iterator + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(Iterator<T> self, int startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { int result = -1; int i = 0; - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); i++) { - Object value = iter.next(); - if (i < startIndex) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition); + while (self.hasNext()) { + Object value = self.next(); + if (i++ < startIndex) { continue; } if (bcw.call(value)) { - result = i; + result = i - 1; break; } } @@ -16088,87 +16375,312 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Iterates over the elements of an iterable collection of items and returns + * Iterates over the elements of an Iterable and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Iterable + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexOf(self, 0, condition); + } + + /** + * Iterates over the elements of an Iterable, starting from a + * specified startIndex, and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Iterable + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(Iterable<T> self, int startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexOf(self.iterator(), startIndex, condition); + } + + /** + * Iterates over the elements of an Array and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Array + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(T[] self, @ClosureParams(FirstParam.Component.class) Closure condition) { + return findIndexOf(self, 0, condition); + } + + /** + * Iterates over the elements of an Array, starting from a + * specified startIndex, and returns the index of the first item that satisfies the + * condition specified by the closure. + * + * @param self an Array + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the first matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findIndexOf(T[] self, int startIndex, @ClosureParams(FirstParam.Component.class) Closure condition) { + return findIndexOf(new ArrayIterator<T>(self), startIndex, condition); + } + + /** + * Iterates over the elements of an aggregate of items and returns * the index of the last item that matches the condition specified in the closure. * - * @param self the iteration object over which to iterate - * @param closure the filter to perform a match on the collection + * @param self the iteration object over which to iterate + * @param condition the matching condition * @return an integer that is the index of the last matched object or -1 if no match was found * @since 1.5.2 */ - public static int findLastIndexOf(Object self, Closure closure) { - return findLastIndexOf(self, 0, closure); + public static int findLastIndexOf(Object self, Closure condition) { + return findLastIndexOf(self, 0, condition); } /** - * Iterates over the elements of an iterable collection of items, starting + * Iterates over the elements of an aggregate of items, starting * from a specified startIndex, and returns the index of the last item that * matches the condition specified in the closure. * * @param self the iteration object over which to iterate * @param startIndex start matching from this index - * @param closure the filter to perform a match on the collection + * @param condition the matching condition * @return an integer that is the index of the last matched object or -1 if no match was found * @since 1.5.2 */ - public static int findLastIndexOf(Object self, int startIndex, Closure closure) { + public static int findLastIndexOf(Object self, int startIndex, Closure condition) { + return findLastIndexOf(InvokerHelper.asIterator(self), startIndex, condition); + } + + /** + * Iterates over the elements of an Iterator and returns + * the index of the last item that matches the condition specified in the closure. + * + * @param self an Iterator + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findLastIndexOf(self, 0, condition); + } + + /** + * Iterates over the elements of an Iterator, starting + * from a specified startIndex, and returns the index of the last item that + * matches the condition specified in the closure. + * + * @param self an Iterator + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(Iterator<T> self, int startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { int result = -1; int i = 0; - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); i++) { - Object value = iter.next(); - if (i < startIndex) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition); + while (self.hasNext()) { + Object value = self.next(); + if (i++ < startIndex) { continue; } if (bcw.call(value)) { - result = i; + result = i - 1; } } return result; } /** - * Iterates over the elements of an iterable collection of items and returns + * Iterates over the elements of an Iterable and returns + * the index of the last item that matches the condition specified in the closure. + * + * @param self an Iterable + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findLastIndexOf(self.iterator(), 0, condition); + } + + /** + * Iterates over the elements of an Iterable, starting + * from a specified startIndex, and returns the index of the last item that + * matches the condition specified in the closure. + * + * @param self an Iterable + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(Iterable<T> self, int startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findLastIndexOf(self.iterator(), startIndex, condition); + } + + /** + * Iterates over the elements of an Array and returns + * the index of the last item that matches the condition specified in the closure. + * + * @param self an Array + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(T[] self, @ClosureParams(FirstParam.Component.class) Closure condition) { + return findLastIndexOf(new ArrayIterator<T>(self), 0, condition); + } + + /** + * Iterates over the elements of an Array, starting + * from a specified startIndex, and returns the index of the last item that + * matches the condition specified in the closure. + * + * @param self an Array + * @param startIndex start matching from this index + * @param condition the matching condition + * @return an integer that is the index of the last matched object or -1 if no match was found + * @since 2.5.0 + */ + public static <T> int findLastIndexOf(T[] self, int startIndex, @ClosureParams(FirstParam.Component.class) Closure condition) { + // TODO could be made more efficient by using a reverse index + return findLastIndexOf(new ArrayIterator<T>(self), startIndex, condition); + } + + /** + * Iterates over the elements of an aggregate of items and returns * the index values of the items that match the condition specified in the closure. * - * @param self the iteration object over which to iterate - * @param closure the filter to perform a match on the collection + * @param self the iteration object over which to iterate + * @param condition the matching condition * @return a list of numbers corresponding to the index values of all matched objects * @since 1.5.2 */ - public static List<Number> findIndexValues(Object self, Closure closure) { - return findIndexValues(self, 0, closure); + public static List<Number> findIndexValues(Object self, Closure condition) { + return findIndexValues(self, 0, condition); } /** - * Iterates over the elements of an iterable collection of items, starting from + * Iterates over the elements of an aggregate of items, starting from * a specified startIndex, and returns the index values of the items that match * the condition specified in the closure. * * @param self the iteration object over which to iterate * @param startIndex start matching from this index - * @param closure the filter to perform a match on the collection + * @param condition the matching condition * @return a list of numbers corresponding to the index values of all matched objects * @since 1.5.2 */ - public static List<Number> findIndexValues(Object self, Number startIndex, Closure closure) { + public static List<Number> findIndexValues(Object self, Number startIndex, Closure condition) { + return findIndexValues(InvokerHelper.asIterator(self), startIndex, condition); + } + + /** + * Iterates over the elements of an Iterator and returns + * the index values of the items that match the condition specified in the closure. + * + * @param self an Iterator + * @param condition the matching condition + * @return a list of numbers corresponding to the index values of all matched objects + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexValues(self, 0, condition); + } + + /** + * Iterates over the elements of an Iterator, starting from + * a specified startIndex, and returns the index values of the items that match + * the condition specified in the closure. + * + * @param self an Iterator + * @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 + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(Iterator<T> self, Number startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { List<Number> result = new ArrayList<Number>(); long count = 0; long startCount = startIndex.longValue(); - BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); - for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); count++) { - Object value = iter.next(); - if (count < startCount) { + BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition); + while (self.hasNext()) { + Object value = self.next(); + if (count++ < startCount) { continue; } if (bcw.call(value)) { - result.add(count); + result.add(count - 1); } } return result; } /** + * Iterates over the elements of an Iterable and returns + * the index values of the items that match the condition specified in the closure. + * + * @param self an Iterable + * @param condition the matching condition + * @return a list of numbers corresponding to the index values of all matched objects + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(Iterable<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexValues(self, 0, condition); + } + + /** + * Iterates over the elements of an Iterable, starting from + * a specified startIndex, and returns the index values of the items that match + * the condition specified in the closure. + * + * @param self an Iterable + * @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 + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(Iterable<T> self, Number startIndex, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { + return findIndexValues(self.iterator(), startIndex, condition); + } + + /** + * Iterates over the elements of an Array and returns + * the index values of the items that match the condition specified in the closure. + * + * @param self an Array + * @param condition the matching condition + * @return a list of numbers corresponding to the index values of all matched objects + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(T[] self, @ClosureParams(FirstParam.Component.class) Closure condition) { + return findIndexValues(self, 0, condition); + } + + /** + * Iterates over the elements of an Array, starting from + * a specified startIndex, and returns the index values of the items that match + * the condition specified in the closure. + * + * @param self an Array + * @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 + * @since 2.5.0 + */ + public static <T> List<Number> findIndexValues(T[] self, Number startIndex, @ClosureParams(FirstParam.Component.class) Closure condition) { + return findIndexValues(new ArrayIterator<T>(self), startIndex, condition); + } + + /** * Iterates through the classloader parents until it finds a loader with a class * named "org.codehaus.groovy.tools.RootLoader". If there is no such class * <code>null</code> will be returned. The name is used for comparison because http://git-wip-us.apache.org/repos/asf/groovy/blob/047c8f29/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy index cce4682..493802e 100644 --- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy +++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy @@ -18,12 +18,8 @@ */ package groovy.transform.stc -import groovy.transform.NotYetImplemented - /** * Unit tests for static type checking : closure parameter type inference. - * - * @author Cedric Champeau */ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { void testInferenceForDGM_CollectUsingExplicitIt() { @@ -225,6 +221,16 @@ def items = [] ''' } + void testDGM_collectOnArray() { + assertScript ''' + String[] arr = ['foo', 'bar', 'baz'] + assert arr.collect { it.startsWith('ba') } == [false, true, true] + List<Boolean> answer = [true] + arr.collect(answer) { it.startsWith('ba') } + assert answer == [true, false, true, true] + ''' + } + void testInferenceOnNonExtensionMethod() { assertScript '''import groovy.transform.stc.ClosureParams import groovy.transform.stc.FirstParam @@ -674,6 +680,84 @@ import groovy.transform.stc.ClosureParams assert ['foo','bar','baz'].iterator().every { it.length() == 3 } ''' } + void testInferenceForDGM_everyOnArray() { + assertScript ''' + String[] items = ['foo','bar','baz'] + assert items.every { it.length() == 3 } + assert items.every { String s -> s.length() == 3 } + ''' + } + + void testInferenceForDGM_findIndexOf() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.findIndexOf { it.startsWith('ba') == 1 } + assert items1.findIndexOf { String s -> s.startsWith('ba') == 1 } + def items2 = ['foo','bar','baz'] + assert items2.findIndexOf { it.startsWith('ba') == 1 } + assert items2.iterator().findIndexOf { it.startsWith('ba') == 1 } + ''' + } + + void testInferenceForDGM_findLastIndexOf() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.findLastIndexOf { it.startsWith('ba') == 2 } + assert items1.findLastIndexOf { String s -> s.startsWith('ba') == 2 } + def items2 = ['foo','bar','baz'] + assert items2.findLastIndexOf { it.startsWith('ba') == 2 } + assert items2.iterator().findLastIndexOf { it.startsWith('ba') == 2 } + ''' + } + + void testInferenceForDGM_findIndexValues() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.findIndexValues { it.startsWith('ba') } == [1, 2] + 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] + ''' + } + + void testInferenceForDGM_findResult() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.findResult { it.startsWith('ba') ? it : null } == 'bar' + def items2 = ['foo','bar','baz'] + assert items2.findResult { it.startsWith('ba') ? it : null } == 'bar' + assert items2.iterator().findResult { it.startsWith('ba') ? it : null } == 'bar' + ''' + } + + void testInferenceForDGM_findResults() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + 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'] + ''' + } + + void testInferenceForDGM_split() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.split { it.startsWith('ba') } == [['bar', 'baz'], ['foo']] + Collection items2 = ['foo','bar','baz'] + assert items2.split { it.startsWith('ba') } == [['bar', 'baz'], ['foo']] + ''' + } + + void testInferenceForDGM_sum() { + assertScript ''' + String[] items1 = ['foo','bar','baz'] + assert items1.sum { it.toUpperCase() } == 'FOOBARBAZ' + def items2 = ['fi','fo','fum'] + assert items2.sum('FEE') { it.toUpperCase() } == 'FEEFIFOFUM' + ''' + } void testInferenceForDGM_findOnCollection() { assertScript ''' @@ -1099,6 +1183,12 @@ import groovy.transform.stc.ClosureParams assert ['abc','de','f'].iterator().any { it.length() == 2 } ''' } + void testDGM_anyOnArray() { + assertScript ''' + String[] strings = ['abc','de','f'] + assert strings.any { it.length() == 2 } + ''' + } void testDGM_mapWithDefault() { assertScript '''
