/*
* Copyright 2003-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.runtime;
import groovy.io.EncodingAwareBufferedWriter;
import groovy.io.FileType;
import groovy.io.FileVisitResult;
import groovy.io.GroovyPrintWriter;
import groovy.lang.Closure;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.EmptyRange;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovySystem;
import groovy.lang.IntRange;
import groovy.lang.MapWithDefault;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingPropertyException;
import groovy.lang.ObjectRange;
import groovy.lang.PropertyValue;
import groovy.lang.Range;
import groovy.lang.SpreadMap;
import groovy.lang.StringWriterIOException;
import groovy.lang.Writable;
import groovy.util.CharsetToolkit;
import groovy.util.ClosureComparator;
import groovy.util.GroovyCollections;
import groovy.util.MapEntry;
import groovy.util.OrderBy;
import groovy.util.PermutationGenerator;
import groovy.util.ProxyGenerator;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.reflection.MixinInMetaClass;
import org.codehaus.groovy.reflection.ReflectionCache;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberDiv;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberMinus;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberMultiply;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberPlus;
import org.codehaus.groovy.runtime.dgmimpl.arrays.BooleanArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.BooleanArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ByteArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ByteArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.CharacterArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.CharacterArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.DoubleArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.DoubleArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.FloatArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.FloatArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.IntegerArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.IntegerArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.LongArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.LongArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ObjectArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ObjectArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ShortArrayGetAtMetaMethod;
import org.codehaus.groovy.runtime.dgmimpl.arrays.ShortArrayPutAtMetaMethod;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.codehaus.groovy.runtime.typehandling.GroovyCastException;
import org.codehaus.groovy.runtime.typehandling.NumberMath;
import org.codehaus.groovy.tools.RootLoader;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* This class defines new groovy methods which appear on normal JDK
* classes inside the Groovy environment. Static methods are used with the
* first parameter being the destination class,
* i.e. public static String reverse(String self)
* provides a reverse()
method for String
.
*
* NOTE: While this class contains many 'public' static methods, it is
* primarily regarded as an internal class (its internal package name
* suggests this also). We value backwards compatibility of these
* methods when used within Groovy but value less backwards compatibility
* at the Java method call level. I.e. future versions of Groovy may
* remove or move a method call in this file but would normally
* aim to keep the method available from within Groovy.
*
* @author James Strachan
* @author Jeremy Rayner
* @author Sam Pullara
* @author Rod Cope
* @author Guillaume Laforge
* @author John Wilson
* @author Hein Meling
* @author Dierk Koenig
* @author Pilho Kim
* @author Marc Guillemot
* @author Russel Winder
* @author bing ran
* @author Jochen Theodorou
* @author Paul King
* @author Michael Baehr
* @author Joachim Baumann
* @author Alex Tkachman
* @author Ted Naleid
* @author Brad Long
* @author Jim Jagielski
* @author Rodolfo Velasco
* @author jeremi Joslin
* @author Hamlet D'Arcy
* @author Cedric Champeau
* @author Tim Yates
* @author Dinko Srkoc
*/
public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
//private static final Logger LOG = Logger.getLogger(DefaultGroovyMethods.class.getName());
private static final InternalLogger LOG = InternalLoggerFactory.getInstance(DefaultGroovyMethods.class.getName());
private static final Integer ONE = 1;
private static final BigInteger BI_INT_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger BI_INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
private static final BigInteger BI_LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
private static final BigInteger BI_LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
public static final Class [] additionals = {
NumberNumberPlus.class,
NumberNumberMultiply.class,
NumberNumberMinus.class,
NumberNumberDiv.class,
ObjectArrayGetAtMetaMethod.class,
ObjectArrayPutAtMetaMethod.class,
BooleanArrayGetAtMetaMethod.class,
BooleanArrayPutAtMetaMethod.class,
ByteArrayGetAtMetaMethod.class,
ByteArrayPutAtMetaMethod.class,
CharacterArrayGetAtMetaMethod.class,
CharacterArrayPutAtMetaMethod.class,
ShortArrayGetAtMetaMethod.class,
ShortArrayPutAtMetaMethod.class,
IntegerArrayGetAtMetaMethod.class,
IntegerArrayPutAtMetaMethod.class,
LongArrayGetAtMetaMethod.class,
LongArrayPutAtMetaMethod.class,
FloatArrayGetAtMetaMethod.class,
FloatArrayPutAtMetaMethod.class,
DoubleArrayGetAtMetaMethod.class,
DoubleArrayPutAtMetaMethod.class,
};
/**
* Identity check. Since == is overridden in Groovy with the meaning of equality
* we need some fallback to check for object identity. Invoke using the
* 'is' method, like so: def same = this.is(that)
*
* @param self an object
* @param other an object to compare identity with
* @return true if self and other are both references to the same
* instance, false otherwise
* @since 1.0
*/
public static boolean is(Object self, Object other) {
return self == other;
}
/**
* Allows the closure to be called for the object reference self.
* Synonym for 'with()'.
*
* @param self the object to have a closure act upon
* @param closure the closure to call on the object
* @return result of calling the closure
* @since 1.0
*/
public static
* Any method invoked inside the closure will first be invoked on the
* self reference. For instance, the following method calls to the append()
* method are invoked on the StringBuilder instance:
*
* def b = new StringBuilder().with {
* append('foo')
* append('bar')
* return it
* }
* assert b.toString() == 'foobar'
*
* This is commonly used to simplify object creation, such as this example:
*
* def p = new Person().with {
* firstName = 'John'
* lastName = 'Doe'
* return it
* }
*
*
* @param self the object to have a closure act upon
* @param closure the closure to call on the object
* @return result of calling the closure
* @since 1.5.0
*/
public static bean[somePropertyNameExpression]
. The normal property notation
* of groovy is neater and more concise but only works with compile-time known
* property names.
*
* @param self the object to act upon
* @param property the property name of interest
* @return the property value
* @since 1.0
*/
public static Object getAt(Object self, String property) {
return InvokerHelper.getProperty(self, property);
}
/**
* Allows the subscript operator to be used to set dynamically named property values.
* bean[somePropertyNameExpression] = foo
. The normal property notation
* of groovy is neater and more concise but only works with property names which
* are known at compile time.
*
* @param self the object to act upon
* @param property the name of the property to set
* @param newValue the value to set
* @since 1.0
*/
public static void putAt(Object self, String property, Object newValue) {
InvokerHelper.setProperty(self, property, newValue);
}
/**
* Generates a detailed dump string of an object showing its class,
* hashCode and fields.
*
* @param self an object
* @return the dump representation
* @since 1.0
*/
public static String dump(Object self) {
if (self == null) {
return "null";
}
StringBuilder buffer = new StringBuilder("<");
Class klass = self.getClass();
buffer.append(klass.getName());
buffer.append("@");
buffer.append(Integer.toHexString(self.hashCode()));
boolean groovyObject = self instanceof GroovyObject;
/*jes this may be rewritten to use the new getProperties() stuff
* but the original pulls out private variables, whereas getProperties()
* does not. What's the real use of dump() here?
*/
while (klass != null) {
for (final Field field : klass.getDeclaredFields()) {
if ((field.getModifiers() & Modifier.STATIC) == 0) {
if (groovyObject && field.getName().equals("metaClass")) {
continue;
}
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
field.setAccessible(true);
return null;
}
});
buffer.append(" ");
buffer.append(field.getName());
buffer.append("=");
try {
buffer.append(InvokerHelper.toString(field.get(self)));
} catch (Exception e) {
buffer.append(e);
}
}
}
klass = klass.getSuperclass();
}
/* here is a different implementation that uses getProperties(). I have left
* it commented out because it returns a slightly different list of properties;
* i.e. it does not return privates. I don't know what dump() really should be doing,
* although IMO showing private fields is a no-no
*/
/*
List props = getProperties(self);
for(Iterator itr = props.keySet().iterator(); itr.hasNext(); ) {
String propName = itr.next().toString();
// the original skipped this, so I will too
if(pv.getName().equals("class")) continue;
if(pv.getName().equals("metaClass")) continue;
buffer.append(" ");
buffer.append(propName);
buffer.append("=");
try {
buffer.append(InvokerHelper.toString(props.get(propName)));
}
catch (Exception e) {
buffer.append(e);
}
}
*/
buffer.append(">");
return buffer.toString();
}
/**
* Retrieves the list of {@link groovy.lang.MetaProperty} objects for 'self' and wraps it
* in a list of {@link groovy.lang.PropertyValue} objects that additionally provide
* the value for each property of 'self'.
*
* @param self the receiver object
* @return list of {@link groovy.lang.PropertyValue} objects
* @see groovy.util.Expando#getMetaPropertyValues()
* @since 1.0
*/
public static Listuse(CategoryClass1, CategoryClass2) { ... }
* This method saves having to wrap the the category
* classes in a list.
*
* @param self any Object
* @param array a list of category classes and a Closure
* @return the value returned from the closure
* @since 1.0
*/
public static Object use(Object self, Object[] array) {
if (array.length < 2)
throw new IllegalArgumentException(
"Expecting at least 2 arguments, a category class and a Closure");
Closure closure;
try {
closure = (Closure) array[array.length - 1];
} catch (ClassCastException e) {
throw new IllegalArgumentException("Expecting a Closure to be the last argument");
}
List
* printf ( "Hello, %s!\n" , [ "world" ] as String[] )
* printf ( "Hello, %s!\n" , [ "Groovy" ])
* printf ( "%d + %d = %d\n" , [ 1 , 2 , 1+2 ] as Integer[] )
* printf ( "%d + %d = %d\n" , [ 3 , 3 , 3+3 ])
*
* ( 1..5 ).each { printf ( "-- %d\n" , [ it ] as Integer[] ) }
* ( 1..5 ).each { printf ( "-- %d\n" , [ it ] as int[] ) }
* ( 0x41..0x45 ).each { printf ( "-- %c\n" , [ it ] as char[] ) }
* ( 07..011 ).each { printf ( "-- %d\n" , [ it ] as byte[] ) }
* ( 7..11 ).each { printf ( "-- %d\n" , [ it ] as short[] ) }
* ( 7..11 ).each { printf ( "-- %d\n" , [ it ] as long[] ) }
* ( 7..11 ).each { printf ( "-- %5.2f\n" , [ it ] as float[] ) }
* ( 7..11 ).each { printf ( "-- %5.2g\n" , [ it ] as double[] ) }
*
*
*
* @param self any Object
* @param format A format string
* @param arg Argument which is referenced by the format specifiers in the format
* string. The type of arg
should be one of Object[], List,
* int[], short[], byte[], char[], boolean[], long[], float[], or double[].
* @since 1.0
*/
public static void printf(Object self, String format, Object arg) {
if (self instanceof PrintStream)
printf((PrintStream) self, format, arg);
else if (self instanceof Writer)
printf((Writer) self, format, arg);
else
printf(System.out, format, arg);
}
private static void printf(PrintStream self, String format, Object arg) {
self.print(sprintf(self, format, arg));
}
private static void printf(Writer self, String format, Object arg) {
try {
self.write(sprintf(self, format, arg));
} catch (IOException e) {
printf(System.out, format, arg);
}
}
/**
* Returns a formatted string using the specified format string and
* arguments.
*
*
* @param self any Object
* @param format A format string
* @param arg Argument which is referenced by the format specifiers in the format
* string. The type of arg
should be one of Object[], List,
* int[], short[], byte[], char[], boolean[], long[], float[], or double[].
* @return the resulting printf'd string
* @since 1.5.0
*/
public static String sprintf(Object self, String format, Object arg) {
if (arg instanceof Object[]) {
return sprintf(self, format, (Object[]) arg);
}
if (arg instanceof List) {
return sprintf(self, format, ((List) arg).toArray());
}
if (!arg.getClass().isArray()) {
Object[] o = (Object[]) java.lang.reflect.Array.newInstance(arg.getClass(), 1);
o[0] = arg;
return sprintf(self, format, o);
}
Object[] ans;
String elemType = arg.getClass().getName();
if (elemType.equals("[I")) {
int[] ia = (int[]) arg;
ans = new Integer[ia.length];
for (int i = 0; i < ia.length; i++) {
ans[i] = ia[i];
}
} else if (elemType.equals("[C")) {
char[] ca = (char[]) arg;
ans = new Character[ca.length];
for (int i = 0; i < ca.length; i++) {
ans[i] = ca[i];
}
} else if (elemType.equals("[Z")) {
boolean[] ba = (boolean[]) arg;
ans = new Boolean[ba.length];
for (int i = 0; i < ba.length; i++) {
ans[i] = ba[i];
}
} else if (elemType.equals("[B")) {
byte[] ba = (byte[]) arg;
ans = new Byte[ba.length];
for (int i = 0; i < ba.length; i++) {
ans[i] = ba[i];
}
} else if (elemType.equals("[S")) {
short[] sa = (short[]) arg;
ans = new Short[sa.length];
for (int i = 0; i < sa.length; i++) {
ans[i] = sa[i];
}
} else if (elemType.equals("[F")) {
float[] fa = (float[]) arg;
ans = new Float[fa.length];
for (int i = 0; i < fa.length; i++) {
ans[i] = fa[i];
}
} else if (elemType.equals("[J")) {
long[] la = (long[]) arg;
ans = new Long[la.length];
for (int i = 0; i < la.length; i++) {
ans[i] = la[i];
}
} else if (elemType.equals("[D")) {
double[] da = (double[]) arg;
ans = new Double[da.length];
for (int i = 0; i < da.length; i++) {
ans[i] = da[i];
}
} else {
throw new RuntimeException("sprintf(String," + arg + ")");
}
return sprintf(self, format, ans);
}
/**
* Inspects returns the String that matches what would be typed into a
* terminal to create this object.
*
* @param self any Object
* @return a String that matches what would be typed into a terminal to
* create this object. e.g. [1, 'hello'].inspect() -> [1, "hello"]
* @since 1.0
*/
public static String inspect(Object self) {
return InvokerHelper.inspect(self);
}
/**
* Print to a console in interactive format.
*
* @param self any Object
* @param out the PrintWriter used for printing
* @since 1.0
*/
public static void print(Object self, PrintWriter out) {
if (out == null) {
out = new PrintWriter(System.out);
}
out.print(InvokerHelper.toString(self));
}
/**
* Print to a console in interactive format.
*
* @param self any Object
* @param out the PrintWriter used for printing
* @since 1.0
*/
public static void println(Object self, PrintWriter out) {
if (out == null) {
out = new PrintWriter(System.out);
}
out.println(InvokerHelper.toString(self));
}
/**
* Provide a dynamic method invocation method which can be overloaded in
* classes to implement dynamic proxies easily.
*
* @param object any Object
* @param method the name of the method to call
* @param arguments the arguments to use
* @return the result of the method call
* @since 1.0
*/
public static Object invokeMethod(Object object, String method, Object arguments) {
return InvokerHelper.invokeMethod(object, method, arguments);
}
// isCase methods
//-------------------------------------------------------------------------
/**
* Method for overloading the behavior of the 'case' method in switch statements.
* The default implementation handles arrays types but otherwise simply delegates
* to Object#equals, but this may be overridden for other types. In this example:
* switch( a ) {
* case b: //some code
* }
* "some code" is called when b.isCase( a )
returns
* true
.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue is deemed to be equal to the caseValue
* @since 1.0
*/
public static boolean isCase(Object caseValue, Object switchValue) {
if (caseValue.getClass().isArray()) {
return isCase(DefaultTypeTransformation.asCollection(caseValue), switchValue);
}
return caseValue.equals(switchValue);
}
/**
* 'Case' implementation for a String, which uses String#equals(Object)
* in order to allow Strings to be used in switch statements.
* For example:
* switch( str ) {
* case 'one' :
* // etc...
* }
* Note that this returns true
for the case where both the
* 'switch' and 'case' operand is null
.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.0
*/
public static boolean isCase(String caseValue, Object switchValue) {
if (switchValue == null) {
return caseValue == null;
}
return caseValue.equals(switchValue.toString());
}
/**
* 'Case' implementation for a CharSequence, which simply calls the equivalent method for String.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.8.2
*/
public static boolean isCase(CharSequence caseValue, Object switchValue) {
return isCase(caseValue.toString(), switchValue);
}
/**
* 'Case' implementation for a GString, which simply calls the equivalent method for String.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.6.0
*/
public static boolean isCase(GString caseValue, Object switchValue) {
return isCase(caseValue.toString(), switchValue);
}
/**
* Special 'Case' implementation for Class, which allows testing
* for a certain class in a switch statement.
* For example:
* switch( obj ) {
* case List :
* // obj is a list
* break;
* case Set :
* // etc
* }
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue is deemed to be assignable from the given class
* @since 1.0
*/
public static boolean isCase(Class caseValue, Object switchValue) {
if (switchValue instanceof Class) {
Class val = (Class) switchValue;
return caseValue.isAssignableFrom(val);
}
return caseValue.isInstance(switchValue);
}
/**
* 'Case' implementation for collections which tests if the 'switch'
* operand is contained in any of the 'case' values.
* For example:
* switch( 3 ) {
* case [1,3,5]:
* assert true
* break
* default:
* assert false
* }
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the caseValue is deemed to contain the switchValue
* @see java.util.Collection#contains(java.lang.Object)
* @since 1.0
*/
public static boolean isCase(Collection caseValue, Object switchValue) {
return caseValue.contains(switchValue);
}
/**
* 'Case' implementation for maps which tests the groovy truth
* value obtained using the 'switch' operand as key.
* For example:
* switch( 'foo' ) {
* case [foo:true, bar:false]:
* assert true
* break
* default:
* assert false
* }
*
* @param caseValue the case value
* @param switchValue the switch value
* @return the groovy truth value from caseValue corresponding to the switchValue key
* @since 1.7.6
*/
public static boolean isCase(Map caseValue, Object switchValue) {
return DefaultTypeTransformation.castToBoolean(caseValue.get(switchValue));
}
/**
* 'Case' implementation for the {@link java.util.regex.Pattern} class, which allows
* testing a String against a number of regular expressions.
* For example:
* switch( str ) {
* case ~/one/ :
* // the regex 'one' matches the value of str
* }
*
* Note that this returns true for the case where both the pattern and
* the 'switch' values are null
.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue is deemed to match the caseValue
* @since 1.0
*/
public static boolean isCase(Pattern caseValue, Object switchValue) {
if (switchValue == null) {
return caseValue == null;
}
final Matcher matcher = caseValue.matcher(switchValue.toString());
if (matcher.matches()) {
RegexSupport.setLastMatcher(matcher);
return true;
} else {
return false;
}
}
/**
* Special 'case' implementation for all numbers, which delegates to the
* compareTo()
method for comparing numbers of different
* types.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the numbers are deemed equal
* @since 1.5.0
*/
public static boolean isCase(Number caseValue, Number switchValue) {
return NumberMath.compareTo(caseValue, switchValue) == 0;
}
/**
* Returns an iterator equivalent to this iterator all duplicated items removed
* by using the default comparator. The original iterator will become
* exhausted of elements after determining the unique values. A new iterator
* for the unique values will be returned.
*
* @param self an Iterator
* @return the modified Iterator
* @since 1.5.5
*/
public static assert [1,3] == [1,3,3].unique()
*
* @param self a collection
* @return the now modified collection
* @see #unique(Collection, boolean)
* @since 1.0
*/
public static
* assert [1,3] == [1,3,3].unique()
*
*
* def orig = [1, 3, 2, 3]
* def uniq = orig.unique(false)
* assert orig == [1, 3, 2, 3]
* assert uniq == [1, 3, 2]
*
*
* @param self a collection
* @param mutate false will cause a new list containing unique items from the collection to be created, true will mutate collections in place
* @return the now modified collection
* @since 1.8.1
*/
public static
assert [1,4] == [1,3,4,5].unique { it % 2 }*
assert [2,3,4] == [2,3,3,4].unique { a, b -> a <=> b }* * @param self a Collection * @param closure a 1 or 2 arg Closure used to determine unique items * @return self without any duplicates * @see #unique(Collection, boolean, Closure) * @since 1.0 */ public static
* def orig = [1, 3, 4, 5] * def uniq = orig.unique(false) { it % 2 } * assert orig == [1, 3, 4, 5] * assert uniq == [1, 4] **
* def orig = [2, 3, 3, 4] * def uniq = orig.unique(false) { a, b -> a <=> b } * assert orig == [2, 3, 3, 4] * assert uniq == [2, 3, 4] ** * @param self a Collection * @param mutate false will always cause a new list to be created, true will mutate lists in place * @param closure a 1 or 2 arg Closure used to determine unique items * @return self without any duplicates * @since 1.8.1 */ public static
* class Person {
* def fname, lname
* String toString() {
* return fname + " " + lname
* }
* }
*
* class PersonComparator implements Comparator {
* int compare(Object o1, Object o2) {
* Person p1 = (Person) o1
* Person p2 = (Person) o2
* if (p1.lname != p2.lname)
* return p1.lname.compareTo(p2.lname)
* else
* return p1.fname.compareTo(p2.fname)
* }
*
* boolean equals(Object obj) {
* return this.equals(obj)
* }
* }
*
* Person a = new Person(fname:"John", lname:"Taylor")
* Person b = new Person(fname:"Clark", lname:"Taylor")
* Person c = new Person(fname:"Tom", lname:"Cruz")
* Person d = new Person(fname:"Clark", lname:"Taylor")
*
* def list = [a, b, c, d]
* List list2 = list.unique(new PersonComparator())
* assert( list2 == list && list == [a, b, c] )
*
*
* @param self a Collection
* @param comparator a Comparator
* @return self the now modified collection without duplicates
* @see #unique(java.util.Collection, boolean, java.util.Comparator)
* @since 1.0
*/
public static
* class Person {
* def fname, lname
* String toString() {
* return fname + " " + lname
* }
* }
*
* class PersonComparator implements Comparator {
* int compare(Object o1, Object o2) {
* Person p1 = (Person) o1
* Person p2 = (Person) o2
* if (p1.lname != p2.lname)
* return p1.lname.compareTo(p2.lname)
* else
* return p1.fname.compareTo(p2.fname)
* }
*
* boolean equals(Object obj) {
* return this.equals(obj)
* }
* }
*
* Person a = new Person(fname:"John", lname:"Taylor")
* Person b = new Person(fname:"Clark", lname:"Taylor")
* Person c = new Person(fname:"Tom", lname:"Cruz")
* Person d = new Person(fname:"Clark", lname:"Taylor")
*
* def list = [a, b, c, d]
* List list2 = list.unique(false, new PersonComparator())
* assert( list2 != list && list2 == [a, b, c] )
*
*
*
* @param self a Collection
* @param mutate false will always cause a new collection to be created, true will mutate collections in place
* @param comparator a Comparator
* @return self the collection without duplicates
* @since 1.8.1
*/
public static def result = "" * [a:1, b:3].each { key, value -> result += "$key$value" } * assert result == "a1b3"*
def result = "" * [a:1, b:3].each { entry -> result += entry } * assert result == "a=1b=3"* * In general, the order in which the map contents are processed * cannot be guaranteed. In practise, specialized forms of Map, * e.g. a TreeMap will have its contents processed according to * the natural ordering of the map. * * @param self the map over which we iterate * @param closure the 1 or 2 arg closure applied on each entry of the map * @return returns the self parameter * @since 1.5.0 */ public static
def result = "" * [a:1, b:3].eachWithIndex { key, value, index -> result += "$index($key$value)" } * assert result == "0(a1)1(b3)"*
def result = "" * [a:1, b:3].eachWithIndex { entry, index -> result += "$index($entry)" } * assert result == "0(a=1)1(b=3)"* * @param self the map over which we iterate * @param closure a 2 or 3 arg Closure to operate on each item * @return the self Object * @since 1.5.0 */ public static
def result = [] * [1,2,3].reverseEach { result << it } * assert result == [3,2,1]* * @param self a List * @param closure a closure to which each item is passed. * @return the original list * @since 1.5.0 */ public static
true
for all items in this data structure).
* A simple example for a list:
* def list = [3,4,5] * def greaterThanTwo = list.every { it > 2 } ** * @param self the object over which we iterate * @param closure 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) { for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { if (!DefaultTypeTransformation.castToBoolean(closure.call(iter.next()))) { return false; } } return true; } /** * Iterates over the entries of a map, and checks whether a predicate is * valid for all entries. If the * closure takes one parameter then it will be passed the Map.Entry * otherwise if the closure takes two parameters then it will be * passed the key and the value. *
def map = [a:1, b:2.0, c:2L] * assert !map.every { key, value -> value instanceof Integer } * assert map.every { entry -> entry.value instanceof Number }* * @param self the map over which we iterate * @param closure 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
true
according to the Groovy Truth.
* Equivalent to self.every({element -> element})
*
* @param self the object over which we iterate
* @return true if every item in the collection matches the closure
* predicate
* @since 1.5.0
*/
public static boolean every(Object self) {
for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
if (!DefaultTypeTransformation.castToBoolean(iter.next())) {
return false;
}
}
return true;
}
/**
* Iterates over the contents of an object or collection, and checks whether a
* predicate is valid for at least one element.
*
* @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
* @since 1.0
*/
public static boolean any(Object self, Closure closure) {
for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
if (DefaultTypeTransformation.castToBoolean(closure.call(iter.next()))) {
return true;
}
}
return false;
}
/**
* Iterates over the entries of a map, and checks whether a predicate is
* valid for at least one entry. If the
* closure takes one parameter then it will be passed the Map.Entry
* otherwise if the closure takes two parameters then it will be
* passed the key and the value.
* * assert [2:3, 4:5, 5:10].any { key, value -> key * 2 == value } * assert ![2:3, 4:5, 5:10].any { entry -> entry.key == entry.value * 2 } ** * @param self the map over which we iterate * @param closure 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
{@link #isCase(java.lang.Object, java.lang.Object)}
* method used by switch statements. This method can be used with different
* kinds of filters like regular expressions, classes, ranges etc.
* Example:
* * def list = ['a', 'b', 'aa', 'bc', 3, 4.5] * assert list.grep( ~/a+/ ) == ['a', 'aa'] * assert list.grep( ~/../ ) == ['aa', 'bc'] * assert list.grep( Number ) == [ 3, 4.5 ] * assert list.grep{ it.toString().size() == 1 } == [ 'a', 'b', 3 ] ** * @param self the object over which we iterate * @param filter the filter to perform on the object (using the {@link #isCase(java.lang.Object, java.lang.Object)} method) * @return a collection of objects which match the filter * @since 1.5.6 */ public static Collection grep(Object self, Object filter) { Collection answer = createSimilarOrDefaultCollection(self); MetaClass metaClass = InvokerHelper.getMetaClass(filter); for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { Object object = iter.next(); if (DefaultTypeTransformation.castToBoolean(metaClass.invokeMethod(filter, "isCase", object))) { answer.add(object); } } return answer; } /** * Iterates over the collection of items which this Object represents and returns each item that matches * using the IDENTITY Closure as a filter - effectively returning all elements which satisfy Groovy truth. * * Example: *
* def items = [1, 2, 0, false, true, '', 'foo', [], [4, 5], null] * assert items.grep() == [1, 2, true, 'foo', [4, 5]] ** * @param self the object over which we iterate * @return a collection of objects which match the filter * @since 1.8.1 * @see Closure#IDENTITY */ public static Collection grep(Object self) { return grep(self, Closure.IDENTITY); } /** * Counts the number of occurrences of the given value from the * items within this Iterator. * Comparison is done using Groovy's == operator (using *
compareTo(value) == 0
or equals(value)
).
* The iterator will become exhausted of elements after determining the count value.
*
* @param self the Iterator from which we count the number of matching occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.5.0
*/
public static Number count(Iterator self, Object value) {
long answer = 0;
while (self.hasNext()) {
if (DefaultTypeTransformation.compareEqual(self.next(), value)) {
++answer;
}
}
// for b/c with Java return an int if we can
if (answer <= Integer.MAX_VALUE) return (int) answer;
return answer;
}
/**
* Counts the number of occurrences which satisfy the given closure from the
* items within this Iterator.
* The iterator will become exhausted of elements after determining the count value.
* * Example usage: *
assert [2,4,2,1,3,5,2,4,3].toSet().iterator().count{ it % 2 == 0 } == 2* * @param self the Iterator from which we count the number of matching occurrences * @param closure a closure condition * @return the number of occurrences * @since 1.8.0 */ public static Number count(Iterator self, Closure closure) { long answer = 0; while (self.hasNext()) { if (DefaultTypeTransformation.castToBoolean(closure.call(self.next()))) { ++answer; } } // for b/c with Java return an int if we can if (answer <= Integer.MAX_VALUE) return (int) answer; return answer; } /** * Counts the number of occurrences of the given value inside this collection. * Comparison is done using Groovy's == operator (using *
compareTo(value) == 0
or equals(value)
).
* * Example usage: *
assert [2,4,2,1,3,5,2,4,3].count(4) == 2* * @param self the collection within which we count the number of occurrences * @param value the value being searched for * @return the number of occurrences * @since 1.0 */ public static Number count(Collection self, Object value) { return count(self.iterator(), value); } /** * Counts the number of occurrences which satisfy the given closure from inside this collection. *
* Example usage: *
assert [2,4,2,1,3,5,2,4,3].count{ it % 2 == 0 } == 5* * @param self the collection within which we count the number of occurrences * @param closure a closure condition * @return the number of occurrences * @since 1.8.0 */ public static Number count(Collection self, Closure closure) { return count(self.iterator(), closure); } /** * Counts the number of occurrences which satisfy the given closure from inside this map. * If the closure takes one parameter then it will be passed the Map.Entry. * Otherwise, the closure should take two parameters and will be passed the key and value. *
* Example usage: *
assert [a:1, b:1, c:2, d:2].count{ k,v -> k == 'a' || v == 2 } == 3* * @param self the map within which we count the number of occurrences * @param closure a 1 or 2 arg Closure condition applying on the entries * @return the number of occurrences * @since 1.8.0 */ public static Number count(Map self, Closure> closure) { long answer = 0; for (Object entry : self.entrySet()) { if (DefaultTypeTransformation.castToBoolean(callClosureForMapEntry(closure, (Map.Entry) entry))) { ++answer; } } // for b/c with Java return an int if we can if (answer <= Integer.MAX_VALUE) return (int) answer; return answer; } /** * Counts the number of occurrences of the given value inside this array. * Comparison is done using Groovy's == operator (using *
compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(Object[] self, Object value) {
return count(Arrays.asList(self), value);
}
/**
* Counts the number of occurrences which satisfy the given closure from inside this array.
*
* @param self the array within which we count the number of occurrences
* @param closure a closure condition
* @return the number of occurrences
* @since 1.8.0
*/
public static Number count(Object[] self, Closure closure) {
return count(Arrays.asList(self), closure);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(int[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(long[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(short[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(char[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(boolean[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(double[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(float[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Counts the number of occurrences of the given value inside this array.
* Comparison is done using Groovy's == operator (using
* compareTo(value) == 0
or equals(value)
).
*
* @param self the array within which we count the number of occurrences
* @param value the value being searched for
* @return the number of occurrences
* @since 1.6.4
*/
public static Number count(byte[] self, Object value) {
return count(InvokerHelper.asIterator(self), value);
}
/**
* Convert a Collection to a List. Always returns a new List
* even if the Collection is already a List.
* * Example usage: *
def x = [1,2,3] as HashSet * assert x.class == HashSet * assert x.toList() instanceof List* * @param self a collection * @return a List * @since 1.0 */ public static
size
.
* Example:
* def list = [ 1, 2, 3, 4, 5, 6, 7 ] * def coll = list.collate( 3 ) * assert coll == [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ]* * @param self a List * @param size the length of each sub-list in the returned list * @return a List containing the data collated into sub-lists * @since 1.8.6 */ public static
size
stepping through the code step
* elements for each subList.
* Example:
* def list = [ 1, 2, 3, 4 ] * def coll = list.collate( 3, 1 ) * assert coll == [ [ 1, 2, 3 ], [ 2, 3, 4 ], [ 3, 4 ], [ 4 ] ]* * @param self a List * @param size the length of each sub-list in the returned list * @param step the number of elements to step through for each sub-list * @return a List containing the data collated into sub-lists * @since 1.8.6 */ public static
size
. Any remaining elements in
* the list after the subdivision will be dropped if keepRemainder
is false.
* Example:
* def list = [ 1, 2, 3, 4, 5, 6, 7 ] * def coll = list.collate( 3, false ) * assert coll == [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]* * @param self a List * @param size the length of each sub-list in the returned list * @param keepRemainder if true, any rmeaining elements are returned as sub-lists. Otherwise they are discarded * @return a List containing the data collated into sub-lists * @since 1.8.6 */ public static
size
stepping through the code step
* elements for each sub-list. Any remaining elements in the list after the subdivision will be dropped if
* keepRemainder
is false.
* Example:
* def list = [ 1, 2, 3, 4 ] * assert list.collate( 3, 1, true ) == [ [ 1, 2, 3 ], [ 2, 3, 4 ], [ 3, 4 ], [ 4 ] ] * assert list.collate( 3, 1, false ) == [ [ 1, 2, 3 ], [ 2, 3, 4 ] ]* * @param self a List * @param size the length of each sub-list in the returned list * @param step the number of elements to step through for each sub-list * @param keepRemainder if true, any rmeaining elements are returned as sub-lists. Otherwise they are discarded * @return a List containing the data collated into sub-lists * @since 1.8.6 */ public static
transform
closure, returning a list of transformed values.
* Example:
* def list = [1, 'a', 1.23, true ] * def types = list.collect { it.class } * assert types == [Integer, String, BigDecimal, Boolean]* * @param self an aggregate Object with an Iterator returning its items * @param transform the closure used to transform each item of the aggregate object * @return a List of the transformed values * @since 1.0 */ public static
assert [1,2,3] == [1,2,3].iterator().collect()* * @param self an aggregate Object with an Iterator returning its items * @return a List 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
transform
closure
* and adding it to the supplied collector
.
*
* @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 transform
closure
* returning a list of transformed values.
* assert [2,4,6] == [1,2,3].collect { it * 2 }* * @param self a collection * @param transform the closure used to transform each item of the collection * @return a List of the transformed values * @since 1.0 */ public static
assert [1,2,3] == [1,2,3].collect()* * @param self a collection * @return a List of the transformed values * @since 1.8.5 * @see Closure#IDENTITY */ public static
transform
closure
* and adding it to the supplied collector
.
* assert [1,2,3] as HashSet == [2,4,5,6].collect(new HashSet()) { (int)(it / 2) }* * @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 * @since 1.0 */ public static
* assert [2,[4,6],[8],[]] == [1,[2,3],[4],[]].collectNested { it * 2 } ** * @param self a collection * @param transform the closure used to transform each item of the collection * @return the resultant collection * @since 1.8.1 */ public static List collectNested(Collection self, Closure transform) { return (List) collectNested(self, new ArrayList(self.size()), transform); } /** * Deprecated alias for collectNested * * @deprecated Use collectNested instead * @see #collectNested(Collection, Collection, Closure) */ public static Collection collectAll(Collection self, Collection collector, Closure transform) { return collectNested(self, collector, transform); } /** * Recursively iterates through this collection transforming each non-Collection value * into a new value using the
transform
closure. Returns a potentially nested
* collection of transformed values.
* def x = [1,[2,3],[4],[]].collectNested(new Vector()) { it * 2 } * assert x == [2,[4,6],[8],[]] * assert x instanceof Vector* * @param self a collection * @param collector an initial Collection to which the transformed values are added * @param transform the closure used to transform each element of the collection * @return the collector with all transformed values added to it * @since 1.8.1 */ public static Collection collectNested(Collection self, Collection collector, Closure transform) { for (Object item : self) { if (item instanceof Collection) { Collection c = (Collection) item; collector.add(collectNested(c, createSimilarCollection(collector, c.size()), transform)); } else { collector.add(transform.call(item)); } if (transform.getDirective() == Closure.DONE) { break; } } return collector; } /** * Projects each item from a source collection to a collection and concatenates (flattens) the resulting collections into a single list. * *
* def nums = 1..10 * def squaresAndCubesOfEvens = nums.collectMany{ it % 2 ? [] : [it**2, it**3] } * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216, 64, 512, 100, 1000] * * def animals = ['CAT', 'DOG', 'ELEPHANT'] as Set * def smallAnimals = animals.collectMany{ it.size() > 3 ? [] : [it.toLowerCase()] } * assert smallAnimals == ['cat', 'dog'] * * def orig = nums as Set * def origPlusIncrements = orig.collectMany{ [it, it+1] } * assert origPlusIncrements.size() == orig.size() * 2 * assert origPlusIncrements.unique().size() == orig.size() + 1 ** * @param self a collection * @param projection a projecting Closure returning a collection of items * @return a list created from the projected collections concatenated (flattened) together * @see #sum(java.util.Collection, groovy.lang.Closure) * @since 1.8.1 */ public static
collector
.
*
* * def animals = ['CAT', 'DOG', 'ELEPHANT'] as Set * def smallAnimals = animals.collectMany(['ant', 'bee']){ it.size() > 3 ? [] : [it.toLowerCase()] } * assert smallAnimals == ['ant', 'bee', 'cat', 'dog'] * * def nums = 1..5 * def origPlusIncrements = nums.collectMany([] as Set){ [it, it+1] } * assert origPlusIncrements.size() == nums.size() + 1 ** * @param self a collection * @param collector an initial collection to add the projected items to * @param projection a projecting Closure returning a collection of items * @return the collector with the projected collections concatenated (flattened) to it * @since 1.8.5 */ public static
* def nums = [1, 2, 3, 4, 5, 6] as Object[] * def squaresAndCubesOfEvens = nums.collectMany{ it % 2 ? [] : [it**2, it**3] } * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216] ** * @param self an object array * @param projection a projecting Closure returning a collection of items * @return a list created from the projected collections concatenated (flattened) together * @see #sum(Object[], groovy.lang.Closure) * @since 1.8.1 */ public static
* def numsIter = [1, 2, 3, 4, 5, 6].iterator() * def squaresAndCubesOfEvens = numsIter.collectMany{ it % 2 ? [] : [it**2, it**3] } * assert squaresAndCubesOfEvens == [4, 8, 16, 64, 36, 216] ** * @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 #sum(Iterator, groovy.lang.Closure) * @since 1.8.1 */ public static