http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/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 new file mode 100644 index 0000000..eec02af --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -0,0 +1,18930 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.FileType; +import groovy.io.GroovyPrintWriter; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +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.Groovydoc; +import groovy.lang.IntRange; +import groovy.lang.ListWithDefault; +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.Tuple2; +import groovy.lang.Writable; +import groovy.transform.stc.ClosureParams; +import groovy.transform.stc.FirstParam; +import groovy.transform.stc.FromString; +import groovy.transform.stc.MapEntryOrKeyValue; +import groovy.transform.stc.SimpleType; +import groovy.util.BufferedIterator; +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 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.reflection.stdclasses.CachedSAMClass; +import org.codehaus.groovy.runtime.callsite.BooleanClosureWrapper; +import org.codehaus.groovy.runtime.callsite.BooleanReturningMethodInvoker; +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.codehaus.groovy.transform.trait.Traits; +import org.codehaus.groovy.util.ArrayIterator; +import org.codehaus.groovy.util.IteratorBufferedIterator; +import org.codehaus.groovy.util.ListBufferedIterator; + +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.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +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.security.AccessController; +import java.security.PrivilegedAction; +import java.text.MessageFormat; +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.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.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 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. <code>public static String reverse(String self)</code> + * provides a <code>reverse()</code> method for <code>String</code>. + * <p> + * 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. + */ +public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { + + private static final Logger LOG = Logger.getLogger(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[] ADDITIONAL_CLASSES = { + 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, + }; + + public static final Class[] DGM_LIKE_CLASSES = new Class[]{ + DefaultGroovyMethods.class, + DateGroovyMethods.class, + EncodingGroovyMethods.class, + IOGroovyMethods.class, + ProcessGroovyMethods.class, + ResourceGroovyMethods.class, + SocketGroovyMethods.class, + StringGroovyMethods.class//, + // TODO provide alternative way for these to be registered +// SqlGroovyMethods.class, +// SwingGroovyMethods.class, +// XmlGroovyMethods.class, +// NioGroovyMethods.class + }; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + private static final NumberAwareComparator<Comparable> COMPARABLE_NUMBER_AWARE_COMPARATOR = new NumberAwareComparator<Comparable>(); + + /** + * 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: <code>def same = this.is(that)</code> + * + * @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 + * @see #with(Object, Closure) + * @since 1.0 + */ + public static <T,U> T identity( + @DelegatesTo.Target("self") U self, + @DelegatesTo(value=DelegatesTo.Target.class, + target="self", + strategy=Closure.DELEGATE_FIRST) + @ClosureParams(FirstParam.class) + Closure<T> closure) { + return DefaultGroovyMethods.with(self, closure); + } + + /** + * Allows the closure to be called for the object reference self. + * <p> + * 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: + * <pre class="groovyTestCase"> + * def b = new StringBuilder().with { + * append('foo') + * append('bar') + * return it + * } + * assert b.toString() == 'foobar' + * </pre> + * This is commonly used to simplify object creation, such as this example: + * <pre> + * def p = new Person().with { + * firstName = 'John' + * lastName = 'Doe' + * return it + * } + * </pre> + * The other typical usage, uses the self object while creating some value: + * <pre> + * def fullName = person.with{ "$firstName $lastName" } + * </pre> + * + * @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 + * @see #with(Object, boolean, Closure) + * @see #tap(Object, Closure) + * @since 1.5.0 + */ + @SuppressWarnings("unchecked") + public static <T,U> T with( + @DelegatesTo.Target("self") U self, + @DelegatesTo(value=DelegatesTo.Target.class, + target="self", + strategy=Closure.DELEGATE_FIRST) + @ClosureParams(FirstParam.class) + Closure<T> closure) { + return (T) with(self, false, (Closure<Object>)closure); + } + + /** + * Allows the closure to be called for the object reference self. + * <p/> + * Any method invoked inside the closure will first be invoked on the + * self reference. For example, the following method calls to the append() + * method are invoked on the StringBuilder instance and then, because + * 'returning' is true, the self instance is returned: + * <pre class="groovyTestCase"> + * def b = new StringBuilder().with(true) { + * append('foo') + * append('bar') + * } + * assert b.toString() == 'foobar' + * </pre> + * The returning parameter is commonly set to true when using with to simplify object + * creation, such as this example: + * <pre> + * def p = new Person().with(true) { + * firstName = 'John' + * lastName = 'Doe' + * } + * </pre> + * Alternatively, 'tap' is an alias for 'with(true)', so that method can be used instead. + * + * The other main use case for with is when returning a value calculated using self as shown here: + * <pre> + * def fullName = person.with(false){ "$firstName $lastName" } + * </pre> + * Alternatively, 'with' is an alias for 'with(false)', so the boolean parameter can be ommitted instead. + * + * @param self the object to have a closure act upon + * @param returning if true, return the self object; otherwise, the result of calling the closure + * @param closure the closure to call on the object + * @return the self object or the result of calling the closure depending on 'returning' + * @see #with(Object, Closure) + * @see #tap(Object, Closure) + * @since 2.5.0 + */ + public static <T,U extends T, V extends T> T with( + @DelegatesTo.Target("self") U self, + boolean returning, + @DelegatesTo(value=DelegatesTo.Target.class, + target="self", + strategy=Closure.DELEGATE_FIRST) + @ClosureParams(FirstParam.class) + Closure<T> closure) { + @SuppressWarnings("unchecked") + final Closure<V> clonedClosure = (Closure<V>) closure.clone(); + clonedClosure.setResolveStrategy(Closure.DELEGATE_FIRST); + clonedClosure.setDelegate(self); + V result = clonedClosure.call(self); + return returning ? self : result; + } + + /** + * Allows the closure to be called for the object reference self (similar + * to <code>with</code> and always returns self. + * <p> + * 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: + * <pre> + * def b = new StringBuilder().tap { + * append('foo') + * append('bar') + * } + * assert b.toString() == 'foobar' + * </pre> + * This is commonly used to simplify object creation, such as this example: + * <pre> + * def p = new Person().tap { + * firstName = 'John' + * lastName = 'Doe' + * } + * </pre> + * + * @param self the object to have a closure act upon + * @param closure the closure to call on the object + * @return self + * @see #with(Object, boolean, Closure) + * @see #with(Object, Closure) + * @since 2.5.0 + */ + @SuppressWarnings("unchecked") + public static <T,U> U tap( + @DelegatesTo.Target("self") U self, + @DelegatesTo(value=DelegatesTo.Target.class, + target="self", + strategy=Closure.DELEGATE_FIRST) + @ClosureParams(FirstParam.class) + Closure<T> closure) { + return (U) with(self, true, (Closure<Object>)closure); + } + + /** + * Allows the subscript operator to be used to lookup dynamic property values. + * <code>bean[somePropertyNameExpression]</code>. 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. + * <code>bean[somePropertyNameExpression] = foo</code>. 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 List<PropertyValue> getMetaPropertyValues(Object self) { + MetaClass metaClass = InvokerHelper.getMetaClass(self); + List<MetaProperty> mps = metaClass.getProperties(); + List<PropertyValue> props = new ArrayList<PropertyValue>(mps.size()); + for (MetaProperty mp : mps) { + props.add(new PropertyValue(self, mp)); + } + return props; + } + + /** + * Convenience method that calls {@link #getMetaPropertyValues(java.lang.Object)}(self) + * and provides the data in form of simple key/value pairs, i.e. without + * type() information. + * + * @param self the receiver object + * @return meta properties as Map of key/value pairs + * @since 1.0 + */ + public static Map getProperties(Object self) { + List<PropertyValue> metaProps = getMetaPropertyValues(self); + Map<String, Object> props = new LinkedHashMap<String, Object>(metaProps.size()); + + for (PropertyValue mp : metaProps) { + try { + props.put(mp.getName(), mp.getValue()); + } catch (Exception e) { + LOG.throwing(self.getClass().getName(), "getProperty(" + mp.getName() + ")", e); + } + } + return props; + } + + /** + * Scoped use method + * + * @param self any Object + * @param categoryClass a category class to use + * @param closure the closure to invoke with the category in place + * @return the value returned from the closure + * @since 1.0 + */ + public static <T> T use(Object self, Class categoryClass, Closure<T> closure) { + return GroovyCategorySupport.use(categoryClass, closure); + } + + /** + * Extend object with category methods. + * All methods for given class and all super classes will be added to the object. + * + * @param self any Class + * @param categoryClasses a category classes to use + * @since 1.6.0 + */ + public static void mixin(MetaClass self, List<Class> categoryClasses) { + MixinInMetaClass.mixinClassesToMetaClass(self, categoryClasses); + } + + /** + * Extend class globally with category methods. + * All methods for given class and all super classes will be added to the class. + * + * @param self any Class + * @param categoryClasses a category classes to use + * @since 1.6.0 + */ + public static void mixin(Class self, List<Class> categoryClasses) { + mixin(getMetaClass(self), categoryClasses); + } + + /** + * Extend class globally with category methods. + * + * @param self any Class + * @param categoryClass a category class to use + * @since 1.6.0 + */ + public static void mixin(Class self, Class categoryClass) { + mixin(getMetaClass(self), Collections.singletonList(categoryClass)); + } + + /** + * Extend class globally with category methods. + * + * @param self any Class + * @param categoryClass a category class to use + * @since 1.6.0 + */ + public static void mixin(Class self, Class[] categoryClass) { + mixin(getMetaClass(self), Arrays.asList(categoryClass)); + } + + /** + * Extend class globally with category methods. + * + * @param self any Class + * @param categoryClass a category class to use + * @since 1.6.0 + */ + public static void mixin(MetaClass self, Class categoryClass) { + mixin(self, Collections.singletonList(categoryClass)); + } + + /** + * Extend class globally with category methods. + * + * @param self any Class + * @param categoryClass a category class to use + * @since 1.6.0 + */ + public static void mixin(MetaClass self, Class[] categoryClass) { + mixin(self, Arrays.asList(categoryClass)); + } + + /** + * Scoped use method with list of categories. + * + * @param self any Object + * @param categoryClassList a list of category classes + * @param closure the closure to invoke with the categories in place + * @return the value returned from the closure + * @since 1.0 + */ + public static <T> T use(Object self, List<Class> categoryClassList, Closure<T> closure) { + return GroovyCategorySupport.use(categoryClassList, closure); + } + + /** + * Allows the usage of addShutdownHook without getting the runtime first. + * + * @param self the object the method is called on (ignored) + * @param closure the shutdown hook action + * @since 1.5.0 + */ + public static void addShutdownHook(Object self, Closure closure) { + Runtime.getRuntime().addShutdownHook(new Thread(closure)); + } + + /** + * Allows you to use a list of categories, specifying the list as varargs. + * <code>use(CategoryClass1, CategoryClass2) { ... }</code> + * 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<Class> list = new ArrayList<Class>(array.length - 1); + for (int i = 0; i < array.length - 1; ++i) { + Class categoryClass; + try { + categoryClass = (Class) array[i]; + } catch (ClassCastException e) { + throw new IllegalArgumentException("Expecting a Category Class for argument " + i); + } + list.add(categoryClass); + } + return GroovyCategorySupport.use(list, closure); + } + + /** + * Print a value formatted Groovy style to self if it + * is a Writer, otherwise to the standard output stream. + * + * @param self any Object + * @param value the value to print + * @since 1.0 + */ + public static void print(Object self, Object value) { + // we won't get here if we are a PrintWriter + if (self instanceof Writer) { + try { + ((Writer) self).write(InvokerHelper.toString(value)); + } catch (IOException e) { + // TODO: Should we have some unified function like PrintWriter.checkError()? + } + } else { + System.out.print(InvokerHelper.toString(value)); + } + } + + /** + * Print a value formatted Groovy style to the print writer. + * + * @param self a PrintWriter + * @param value the value to print + * @since 1.0 + */ + public static void print(PrintWriter self, Object value) { + self.print(InvokerHelper.toString(value)); + } + + /** + * Print a value formatted Groovy style to the print stream. + * + * @param self a PrintStream + * @param value the value to print + * @since 1.6.0 + */ + public static void print(PrintStream self, Object value) { + self.print(InvokerHelper.toString(value)); + } + + /** + * Print a value to the standard output stream. + * This method delegates to the owner to execute the method. + * + * @param self a generated closure + * @param value the value to print + * @since 1.0 + */ + public static void print(Closure self, Object value) { + Object owner = getClosureOwner(self); + InvokerHelper.invokeMethod(owner, "print", new Object[]{value}); + } + + /** + * Print a linebreak to the standard output stream. + * + * @param self any Object + * @since 1.0 + */ + public static void println(Object self) { + // we won't get here if we are a PrintWriter + if (self instanceof Writer) { + PrintWriter pw = new GroovyPrintWriter((Writer) self); + pw.println(); + } else { + System.out.println(); + } + } + + /** + * Print a linebreak to the standard output stream. + * This method delegates to the owner to execute the method. + * + * @param self a closure + * @since 1.0 + */ + public static void println(Closure self) { + Object owner = getClosureOwner(self); + InvokerHelper.invokeMethod(owner, "println", EMPTY_OBJECT_ARRAY); + } + + private static Object getClosureOwner(Closure cls) { + Object owner = cls.getOwner(); + while (owner instanceof GeneratedClosure) { + owner = ((Closure) owner).getOwner(); + } + return owner; + } + + /** + * Print a value formatted Groovy style (followed by a newline) to self + * if it is a Writer, otherwise to the standard output stream. + * + * @param self any Object + * @param value the value to print + * @since 1.0 + */ + public static void println(Object self, Object value) { + // we won't get here if we are a PrintWriter + if (self instanceof Writer) { + final PrintWriter pw = new GroovyPrintWriter((Writer) self); + pw.println(value); + } else { + System.out.println(InvokerHelper.toString(value)); + } + } + + /** + * Print a value formatted Groovy style (followed by a newline) to the print writer. + * + * @param self a PrintWriter + * @param value the value to print + * @since 1.0 + */ + public static void println(PrintWriter self, Object value) { + self.println(InvokerHelper.toString(value)); + } + + /** + * Print a value formatted Groovy style (followed by a newline) to the print stream. + * + * @param self any Object + * @param value the value to print + * @since 1.6.0 + */ + public static void println(PrintStream self, Object value) { + self.println(InvokerHelper.toString(value)); + } + + /** + * Print a value (followed by a newline) to the standard output stream. + * This method delegates to the owner to execute the method. + * + * @param self a closure + * @param value the value to print + * @since 1.0 + */ + public static void println(Closure self, Object value) { + Object owner = getClosureOwner(self); + InvokerHelper.invokeMethod(owner, "println", new Object[]{value}); + } + + /** + * Printf to a console. + * + * @param self any Object + * @param format a format string + * @param values values referenced by the format specifiers in the format string. + * @since 1.0 + */ + public static void printf(Object self, String format, Object[] values) { + if (self instanceof PrintStream) + ((PrintStream)self).printf(format, values); + else + System.out.printf(format, values); + } + + /** + * Sprintf to a string. + * + * @param self any Object + * @param format a format string + * @param values values referenced by the format specifiers in the format string. + * @return the resulting formatted string + * @since 1.5.0 + */ + public static String sprintf(Object self, String format, Object[] values) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outputStream); + out.printf(format, values); + return outputStream.toString(); + } + + /** + * Prints a formatted string using the specified format string and + * arguments. + * <p> + * Examples: + * <pre> + * 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[] ) } + * </pre> + * + * @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 <code>arg</code> 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 <code>arg</code> 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: + * <pre> switch( a ) { + * case b: //some code + * }</pre> + * "some code" is called when <code>b.isCase( a )</code> returns + * <code>true</code>. + * + * @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); + } + + /** + * Special 'Case' implementation for Class, which allows testing + * for a certain class in a switch statement. + * For example: + * <pre>switch( obj ) { + * case List : + * // obj is a list + * break; + * case Set : + * // etc + * }</pre> + * + * @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: + * <pre class="groovyTestCase">switch( 3 ) { + * case [1,3,5]: + * assert true + * break + * default: + * assert false + * }</pre> + * + * @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: + * <pre class="groovyTestCase">switch( 'foo' ) { + * case [foo:true, bar:false]: + * assert true + * break + * default: + * assert false + * }</pre> + * + * @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)); + } + + /** + * Special 'case' implementation for all numbers, which delegates to the + * <code>compareTo()</code> 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 with all duplicated items removed + * by using Groovy's default number-aware 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 a new Iterator of the unique items from the original iterator + * @since 1.5.5 + */ + public static <T> Iterator<T> unique(Iterator<T> self) { + return uniqueItems(new IteratorIterableAdapter<T>(self)).listIterator(); + } + + /** + * Modifies this collection to remove all duplicated items, using Groovy's + * default number-aware comparator. + * <pre class="groovyTestCase">assert [1,3] == [1,3,3].unique()</pre> + * + * @param self a collection + * @return the now modified collection + * @see #unique(Collection, boolean) + * @since 1.0 + */ + public static <T> Collection<T> unique(Collection<T> self) { + return unique(self, true); + } + + /** + * Modifies this List to remove all duplicated items, using Groovy's + * default number-aware comparator. + * <pre class="groovyTestCase">assert [1,3] == [1,3,3].unique()</pre> + * + * @param self a List + * @return the now modified List + * @see #unique(Collection, boolean) + * @since 2.4.0 + */ + public static <T> List<T> unique(List<T> self) { + return (List<T>) unique((Collection<T>) self, true); + } + + /** + * Remove all duplicates from a given Collection using Groovy's default number-aware comparator. + * If mutate is true, it works by modifying the original object (and also returning it). + * If mutate is false, a new collection is returned leaving the original unchanged. + * <pre class="groovyTestCase"> + * assert [1,3] == [1,3,3].unique() + * </pre> + * <pre class="groovyTestCase"> + * def orig = [1, 3, 2, 3] + * def uniq = orig.unique(false) + * assert orig == [1, 3, 2, 3] + * assert uniq == [1, 3, 2] + * </pre> + * + * @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 <T> Collection<T> unique(Collection<T> self, boolean mutate) { + List<T> answer = uniqueItems(self); + if (mutate) { + self.clear(); + self.addAll(answer); + } + return mutate ? self : answer ; + } + + private static <T> List<T> uniqueItems(Iterable<T> self) { + List<T> answer = new ArrayList<T>(); + for (T t : self) { + boolean duplicated = false; + for (T t2 : answer) { + if (coercedEquals(t, t2)) { + duplicated = true; + break; + } + } + if (!duplicated) + answer.add(t); + } + return answer; + } + + /** + * Remove all duplicates from a given List using Groovy's default number-aware comparator. + * If mutate is true, it works by modifying the original object (and also returning it). + * If mutate is false, a new collection is returned leaving the original unchanged. + * <pre class="groovyTestCase"> + * assert [1,3] == [1,3,3].unique() + * </pre> + * <pre class="groovyTestCase"> + * def orig = [1, 3, 2, 3] + * def uniq = orig.unique(false) + * assert orig == [1, 3, 2, 3] + * assert uniq == [1, 3, 2] + * </pre> + * + * @param self a List + * @param mutate false will cause a new List containing unique items from the List to be created, true will mutate List in place + * @return the now modified List + * @since 2.4.0 + */ + public static <T> List<T> unique(List<T> self, boolean mutate) { + return (List<T>) unique((Collection<T>) self, mutate); + } + + /** + * Provides a method that compares two comparables using Groovy's + * default number aware comparator. + * + * @param self a Comparable + * @param other another Comparable + * @return a -ve number, 0 or a +ve number according to Groovy's compareTo contract + * @since 1.6.0 + */ + public static int numberAwareCompareTo(Comparable self, Comparable other) { + return COMPARABLE_NUMBER_AWARE_COMPARATOR.compare(self, other); + } + + /** + * Returns an iterator equivalent to this iterator but with all duplicated items + * removed by using a Closure to determine duplicate (equal) items. + * The original iterator will be fully processed after the call. + * <p> + * If the closure takes a single parameter, the argument passed will be each element, + * and the closure should return a value used for comparison (either using + * {@link java.lang.Comparable#compareTo(java.lang.Object)} or {@link java.lang.Object#equals(java.lang.Object)}). + * If the closure takes two parameters, two items from the Iterator + * will be passed as arguments, and the closure should return an + * int value (with 0 indicating the items are not unique). + * + * @param self an Iterator + * @param condition a Closure used to determine unique items + * @return the modified Iterator + * @since 1.5.5 + */ + public static <T> Iterator<T> unique(Iterator<T> self, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure condition) { + Comparator<T> comparator = condition.getMaximumNumberOfParameters() == 1 + ? new OrderBy<T>(condition, true) + : new ClosureComparator<T>(condition); + return unique(self, comparator); + } + + /** + * A convenience method for making a collection unique using a Closure + * to determine duplicate (equal) items. + * <p> + * If the closure takes a single parameter, the + * argument passed will be each element, and the closure + * should return a value used for comparison (either using + * {@link java.lang.Comparable#compareTo(java.lang.Object)} or {@link java.lang.Object#equals(java.lang.Object)}). + * If the closure takes two parameters, two items from the collection + * will be passed as arguments, and the closure should return an + * int value (with 0 indicating the items are not unique). + * <pre class="groovyTestCase">assert [1,4] == [1,3,4,5].unique { it % 2 }</pre> + * <pre class="groovyTestCase">assert [2,3,4] == [2,3,3,4].unique { a, b -> a <=> b }</pre> + * + * @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 <T> Collection<T> unique(Collection<T> self, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure closure) { + return unique(self, true, closure); + } + + /** + * A convenience method for making a List unique using a Closure + * to determine duplicate (equal) items. + * <p> + * If the closure takes a single parameter, the + * argument passed will be each element, and the closure + * should return a value used for comparison (either using + * {@link java.lang.Comparable#compareTo(java.lang.Object)} or {@link java.lang.Object#equals(java.lang.Object)}). + * If the closure takes two parameters, two items from the List + * will be passed as arguments, and the closure should return an + * int value (with 0 indicating the items are not unique). + * <pre class="groovyTestCase">assert [1,4] == [1,3,4,5].unique { it % 2 }</pre> + * <pre class="groovyTestCase">assert [2,3,4] == [2,3,3,4].unique { a, b -> a <=> b }</pre> + * + * @param self a List + * @param closure a 1 or 2 arg Closure used to determine unique items + * @return self without any duplicates + * @see #unique(Collection, boolean, Closure) + * @since 2.4.0 + */ + public static <T> List<T> unique(List<T> self, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure closure) { + return (List<T>) unique((Collection<T>) self, true, closure); + } + + /** + * A convenience method for making a collection unique using a Closure to determine duplicate (equal) items. + * If mutate is true, it works on the receiver object and returns it. If mutate is false, a new collection is returned. + * <p> + * If the closure takes a single parameter, each element from the Collection will be passed to the closure. The closure + * should return a value used for comparison (either using {@link java.lang.Comparable#compareTo(java.lang.Object)} or + * {@link java.lang.Object#equals(java.lang.Object)}). If the closure takes two parameters, two items from the collection + * will be passed as arguments, and the closure should return an int value (with 0 indicating the items are not unique). + * <pre class="groovyTestCase"> + * def orig = [1, 3, 4, 5] + * def uniq = orig.unique(false) { it % 2 } + * assert orig == [1, 3, 4, 5] + * assert uniq == [1, 4] + * </pre> + * <pre class="groovyTestCase"> + * 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] + * </pre> + * + * @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 <T> Collection<T> unique(Collection<T> self, boolean mutate, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure closure) { + // use a comparator of one item or two + int params = closure.getMaximumNumberOfParameters(); + if (params == 1) { + self = unique(self, mutate, new OrderBy<T>(closure, true)); + } else { + self = unique(self, mutate, new ClosureComparator<T>(closure)); + } + return self; + } + + /** + * A convenience method for making a List unique using a Closure to determine duplicate (equal) items. + * If mutate is true, it works on the receiver object and returns it. If mutate is false, a new collection is returned. + * <p> + * If the closure takes a single parameter, each element from the List will be passed to the closure. The closure + * should return a value used for comparison (either using {@link java.lang.Comparable#compareTo(java.lang.Object)} or + * {@link java.lang.Object#equals(java.lang.Object)}). If the closure takes two parameters, two items from the collection + * will be passed as arguments, and the closure should return an int value (with 0 indicating the items are not unique). + * <pre class="groovyTestCase"> + * def orig = [1, 3, 4, 5] + * def uniq = orig.unique(false) { it % 2 } + * assert orig == [1, 3, 4, 5] + * assert uniq == [1, 4] + * </pre> + * <pre class="groovyTestCase"> + * 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] + * </pre> + * + * @param self a List + * @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 2.4.0 + */ + public static <T> List<T> unique(List<T> self, boolean mutate, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure closure) { + return (List<T>) unique((Collection<T>) self, mutate, closure); + } + + /** + * Returns an iterator equivalent to this iterator with all duplicated + * items removed by using the supplied comparator. The original iterator + * will be exhausted upon returning. + * + * @param self an Iterator + * @param comparator a Comparator + * @return the modified Iterator + * @since 1.5.5 + */ + public static <T> Iterator<T> unique(Iterator<T> self, Comparator<T> comparator) { + return uniqueItems(new IteratorIterableAdapter<T>(self), comparator).listIterator(); + } + + private static final class IteratorIterableAdapter<T> implements Iterable<T> { + private final Iterator<T> self; + + private IteratorIterableAdapter(Iterator<T> self) { + this.self = self; + } + + @Override + public Iterator<T> iterator() { + return self; + } + } + + /** + * Remove all duplicates from a given Collection. + * Works on the original object (and also returns it). + * The order of members in the Collection are compared by the given Comparator. + * For each duplicate, the first member which is returned + * by the given Collection's iterator is retained, but all other ones are removed. + * The given Collection's original order is preserved. + * <p> + * <pre class="groovyTestCase"> + * 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] ) + * </pre> + * + * @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 <T> Collection<T> unique(Collection<T> self, Comparator<T> comparator) { + return unique(self, true, comparator) ; + } + + /** + * Remove all duplicates from a given List. + * Works on the original object (and also returns it). + * The order of members in the List are compared by the given Comparator. + * For each duplicate, the first member which is returned + * by the given List's iterator is retained, but all other ones are removed. + * The given List's original order is preserved. + * <p> + * <pre class="groovyTestCase"> + * 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] ) + * </pre> + * + * @param self a List + * @param comparator a Comparator + * @return self the now modified List without duplicates + * @see #unique(java.util.Collection, boolean, java.util.Comparator) + * @since 2.4.0 + */ + public static <T> List<T> unique(List<T> self, Comparator<T> comparator) { + return (List<T>) unique((Collection<T>) self, true, comparator); + } + + /** + * Remove all duplicates from a given Collection. + * If mutate is true, it works on the original object (and also returns it). If mutate is false, a new collection is returned. + * The order of members in the Collection are compared by the given Comparator. + * For each duplicate, the first member which is returned + * by the given Collection's iterator is retained, but all other ones are removed. + * The given Collection's original order is preserved. + * <p> + * <pre class="groovyTestCase"> + * 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] ) + * </pre> + * + * @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 <T> Collection<T> unique(Collection<T> self, boolean mutate, Comparator<T> comparator) { + List<T> answer = uniqueItems(self, comparator); + if (mutate) { + self.clear(); + self.addAll(answer); + } + return mutate ? self : answer; + } + + private static <T> List<T> uniqueItems(Iterable<T> self, Comparator<T> comparator) { + List<T> answer = new ArrayList<T>(); + for (T t : self) { + boolean duplicated = false; + for (T t2 : answer) { + if (comparator.compare(t, t2) == 0) { + duplicated = true; + break; + } + } + if (!duplicated) + answer.add(t); + } + return answer; + } + + /** + * Remove all duplicates from a given List. + * If mutate is true, it works on the original object (and also returns it). If mutate is false, a new List is returned. + * The order of members in the List are compared by the given Comparator. + * For each duplicate, the first member which is returned + * by the given List's iterator is retained, but all other ones are removed. + * The given List's original order is preserved. + * <p> + * <pre class="groovyTestCase"> + * 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] ) + * </pre> + * + * @param self a List + * @param mutate false will always cause a new List to be created, true will mutate List in place + * @param comparator a Comparator + * @return self the List without duplicates + * @since 2.4.0 + */ + public static <T> List<T> unique(List<T> self, boolean mutate, Comparator<T> comparator) { + return (List<T>) unique((Collection<T>) self, mutate, comparator); + } + + /** + * Returns an iterator equivalent to this iterator but with all duplicated items + * removed where duplicate (equal) items are deduced by calling the supplied Closure condition. + * <p> + * If the supplied Closure takes a single parameter, the argument passed will be each element, + * and the closure should return a value used for comparison (either using + * {@link java.lang.Comparable#compareTo(java.lang.Object)} or {@link java.lang.Object#equals(java.lang.Object)}). + * If the closure takes two parameters, two items from the Iterator + * will be passed as arguments, and the closure should return an + * int value (with 0 indicating the items are not unique). + * <pre class="groovyTestCase"> + * def items = "Hello".toList() + [null, null] + "there".toList() + * def toLower = { it == null ? null : it.toLowerCase() } + * def noDups = items.iterator().toUnique(toLower).toList() + * assert noDups == ['H', 'e', 'l', 'o', null, 't', 'r'] + * </pre> + * <pre class="groovyTestCase">assert [1,4] == [1,3,4,5].toUnique { it % 2 }</pre> + * <pre class="groovyTestCase">assert [2,3,4] == [2,3,3,4].toUnique { a, b -> a <=> b }</pre> + * + * @param self an Iterator + * @param condition a Closure used to determine unique items + * @return an Iterator with no duplicate items + * @since 2.4.0 + */ + public static <T> Iterator<T> toUnique(Iterator<T> self, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure condition) { + return toUnique(self, condition.getMaximumNumberOfParameters() == 1 + ? new OrderBy<T>(condition, true) + : new ClosureComparator<T>(condition)); + } + + private static final class ToUniqueIterator<E> implements Iterator<E> { + private final Iterator<E> delegate; + private final Set<E> seen; + private boolean exhausted; + private E next; + + private ToUniqueIterator(Iterator<E> delegate, Comparator<E> comparator) { + this.delegate = delegate; + seen = new TreeSet<E>(comparator); + advance(); + } + + public boolean hasNext() { + return !exhausted; + } + + public E next() { + if (exhausted) throw new NoSuchElementException(); + E result = next; + advance(); + return result; + } + + public void remove() { + if (exhausted) throw new NoSuchElementException(); + delegate.remove(); + } + + private void advance() { + boolean foundNext = false; + while (!foundNext && !exhausted) { + exhausted = !delegate.hasNext(); + if (!exhausted) { + next = delegate.next(); + foundNext = seen.add(next); + } + } + } + } + + /** + * Returns an iterator equivalent to this iterator with all duplicated + * items removed by using the supplied comparator. + * + * @param self an Iterator + * @param comparator a Comparator used to determine unique (equal) items + * If {@code null}, the Comparable natural ordering of the elements will be used. + * @return an Iterator with no duplicate items + * @since 2.4.0 + */ + public static <T> Iterator<T> toUnique(Iterator<T> self, Comparator<T> comparator) { + return new ToUniqueIterator<T>(self, comparator); + } + + /** + * Returns an iterator equivalent to this iterator with all duplicated + * items removed by using the natural ordering of the items. + * + * @param self an Iterator + * @return an Iterator with no duplicate items + * @since 2.4.0 + */ + public static <T> Iterator<T> toUnique(Iterator<T> self) { + return toUnique(self, (Comparator<T>) null); + } + + /** + * Returns a Collection containing the items from the Iterable but with duplicates removed. + * The items in the Iterable are compared by the given Comparator. + * For each duplicate, the first member which is returned from the + * Iterable is retained, but all other ones are removed. + * <p> + * <pre class="groovyTestCase"> + * 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.toUnique(new PersonComparator()) + * assert list2 == [a, b, c] && list == [a, b, c, d] + * </pre> + * + * @param self an Iterable + * @param comparator a Comparator used to determine unique (equal) items + * If {@code null}, the Comparable natural ordering of the elements will be used. + * @return the Collection of non-duplicate items + * @since 2.4.0 + */ + public static <T> Collection<T> toUnique(Iterable<T> self, Comparator<T> comparator) { + Collection<T> result = createSimilarCollection((Collection<T>) self); + addAll(result, toUnique(self.iterator(), comparator)); + return result; + } + + /** + * Returns a List containing the items from the List but with duplicates removed. + * The items in the List are compared by the given Comparator. + * For each duplicate, the first member which is returned from the + * List is retained, but all other ones are removed. + * <p> + * <pre class="groovyTestCase"> + * 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.toUnique(new PersonComparator()) + * assert list2 == [a, b, c] && list == [a, b, c, d] + * </pre> + * + * @param self a List + * @param comparator a Comparator used to determine unique (equal) items + * If {@code null}, the Comparable natural ordering of the elements will be used. + * @return the List of non-duplicate items + * @since 2.4.0 + */ + public static <T> List<T> toUnique(List<T> self, Comparator<T> comparator) { + return (List<T>) toUnique((Iterable<T>) self, comparator); + } + + /** + * Returns a Collection containing the items from the Iterable but with duplicates removed + * using the natural ordering of the items to determine uniqueness. + * <p> + * <pre class="groovyTestCase"> + * String[] letters = ['c', 'a', 't', 's', 'a', 't', 'h', 'a', 't'] + * String[] expected = ['c', 'a', 't', 's', 'h'] + * assert letters.toUnique() == expected + * </pre> + * + * @param self an Iterable + * @return the Collection of non-duplicate items + * @since 2.4.0 + */ + public static <T> Collection<T> toUnique(Iterable<T> self) { + return toUnique(self, (Comparator<T>) null); + } + + /** + * Returns a List containing the items from the List but with duplicates removed + * using the natural ordering of the items to determine uniqueness. + * <p> + * <pre class="groovyTestCase"> + * def letters = ['c', 'a', 't', 's', 'a', 't', 'h', 'a', 't'] + * def expected = ['c', 'a', 't', 's', 'h'] + * assert letters.toUnique() == expected + * </pre> + * + * @param self a List + * @return the List of non-duplicate items + * @since 2.4.0 + */ + public static <T> List<T> toUnique(List<T> self) { + return toUnique(self, (Comparator<T>) null); + } + + /** + * Returns a Collection containing the items from the Iterable but with duplicates removed. + * The items in the Iterable are compared by the given Closure condition. + * For each duplicate, the first member which is returned from the + * Iterable is retained, but all other ones are removed. + * <p> + * If the closure takes a single parameter, each element from the Iterable will be passed to the closure. The closure + * should return a value used for comparison (either using {@link java.lang.Comparable#compareTo(java.lang.Object)} or + * {@link java.lang.Object#equals(java.lang.Object)}). If the closure takes two parameters, two items from the Iterable + * will be passed as arguments, and the closure should return an int value (with 0 indicating the items are not unique). + * <p> + * <pre class="groovyTestCase"> + * class Person { + * def fname, lname + * String toString() { + * return fname + " " + lname + * } + * } + * + * 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] + * def list2 = list.toUnique{ p1, p2 -> p1.lname != p2.lname ? p1.lname <=> p2.lname : p1.fname <=> p2.fname } + * assert( list2 == [a, b, c] && list == [a, b, c, d] ) + * def list3 = list.toUnique{ it.toString() } + * assert( list3 == [a, b, c] && list == [a, b, c, d] ) + * </pre> + * + * @param self an Iterable + * @param condition a Closure used to determine unique items + * @return a new Collection + * @see #toUnique(Iterable, Comparator) + * @since 2.4.0 + */ + public static <T> Collection<T> toUnique(Iterable<T> self, @ClosureParams(value = FromString.class, options = {"T", "T,T"}) Closure condition) { + Comparator<T> comparator = condition.getMaximumNumberOfParameters() == 1 + ? new OrderBy<T>(condition, true) + : new ClosureComparator<T>(condition); + return toUnique(self, comparator); + } + + /** + * Returns a List containing the items from the List but with duplicates removed. + * The items in the List are compared by the given Closure condition. + * For each duplicate, the first member which is returned from the + * Iterable is retained, but all other ones are removed. + * <p> + * If the closure takes a single parameter, each element from the Iterable will be passed to the closure. The closure + * should return a value used for comparison (either using {@link java.lang.Comparable#compareTo(java.lang.Object)} or + * {@link java.lang.Object#equals(java.lang.Object)}). If the closure takes two parameters, two items from the Iterable + * will be passed as arguments, and the closure should return an int value (with 0 indicating the items are not unique). + * <p> + * <pre class="groovyTestCase"> + * class Person { + * def fname, lname + * String toString() { + * return fname + " " + lname + * } + * } + * + * 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] + * def list2 = list.toUnique{ p1, p2 -> p1.lname != p2.lname ? p1.lname <=> p2.lname : p1.fname <=> p2.fname } + * assert( list2 == [a, b, c] && list == [a, b, c, d] ) + * def list3 = list.toUnique{ it.toString() } + * assert( list3 == [a, b, c] && list == [a, b, c, d] ) + * </pre> + * + * @param self a List + * @param condition a Closure used to determine unique items + * @return a new List + * @see #toUnique(Iterable, Comparator) + * @since 2.4.0 + */ + public static <T> List<T> toUnique(List<T> self, @ClosureParams(value = FromString.class, options = {"T", "T,T"}) Closure condition) { + return (List<T>) toUnique((Iterable<T>) self, condition); + } + + /** + * Returns a new Array containing the items from the original Array but with duplicates removed with the supplied + * comparator determining which items are unique. + * <p> + * <pre class="groovyTestCase"> + * String[] letters = ['c', 'a', 't', 's', 'A', 't', 'h', 'a', 'T'] + * String[] lower = ['c', 'a', 't', 's', 'h'] + * class LowerComparator implements Comparator { + * int compare(let1, let2) { let1.toLowerCase() <=> let2.toLowerCase() } + * } + * assert letters.toUnique(new LowerComparator()) == lower + * </pre> + * + * @param self an array + * @param comparator a Comparator used to determine unique (equal) items + * If {@code null}, the Comparable natural ordering of the elements will be used. + * @return the unique items from the array + */ + @SuppressWarnings("unchecked") + public static <T> T[] toUnique(T[] self, Comparator<T> comparator) { + Collection<T> items = toUnique(toList(self), comparator); + T[] result = createSimilarArray(self, items.size()); + return items.toArray(result); + } + + /** + * Returns a new Array containing the items from the original Array but with duplicates removed using the + * natural ordering of the items in the array. + * <p> + * <pre class="groovyTestCase"> + * String[] letters = ['c', 'a', 't', 's', 'a', 't', 'h', 'a', 't'] + * String[] expected = ['c', 'a', 't', 's', 'h'] + * def result = letters.toUnique() + * assert result == expected + * assert result.class.componentType == String + * </pre> + * + * @param self an array + * @return the unique items from the array + */ + @SuppressWarnings("unchecked") + public static <T> T[] toUnique(T[] self) { + return (T[]) toUnique(self, (Comparator) null); + } + + /** + * Returns a new Array containing the items from the original Array but with duplicates removed with the supplied + * comparator determining which items are unique. + * <p> + * <pre class="groovyTestCase"> + * String[] letters = ['c', 'a', 't', 's', 'A', 't', 'h', 'a', 'T'] + * String[] expected = ['c', 'a', 't', 's', 'h'] + * assert letters.toUnique{ p1, p2 -> p1.toLowerCase() <=> p2.toLowerCase() } == expected + * assert letters.toUnique{ it.toLowerCase() } == expected + * </pre> + * + * @param self an array + * @param condition a Closure used to determine unique items + * @return the unique items from the array + */ + @SuppressWarnings("unchecked") + public static <T> T[] toUnique(T[] self, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure condition) { + Comparator<T> comparator = condition.getMaximumNumberOfParameters() == 1 + ? new OrderBy<T>(condition, true) + : new ClosureComparator<T>(condition); + return toUnique(self, comparator); + } + + /** + * Iterates through an array passing each array entry to the given closure. + * <pre class="groovyTestCase"> + * String[] letters = ['a', 'b', 'c'] + * String result = '' + * letters.each{ result += it } + * assert result == 'abc' + * </pre> + * + * @param self the array over which we iterate + * @param closure the closure applied on each array entry + * @return the self array + * @since 2.5.0 + */ + public static <T> T[] each(T[] self, @ClosureParams(FirstParam.Component.class) Closure closure) { + for(T item : self){ + closure.call(item); + } + return self; + } + + /** + * Iterates through an aggregate type or data structure, + * passing each item to the given closure. Custom types may utilize this + * method by simply providing an "iterator()" method. The items returned + * from the resulting iterator will be passed to the closure. + * <pre class="groovyTestCase"> + * String result = '' + * ['a', 'b', 'c'].each{ result += it } + * assert result == 'abc' + * </pre> + * + * @param self the object over which we iterate + * @param closure the closure applied on each element found + * @return the self Object + * @since 1.0 + */ + public static <T> T each(T self, Closure closure) { + each(InvokerHelper.asIterator(self), closure); + return self; + } + + /** + * Iterates through an array, + * passing each array element and the element's index (a counter starting at + * zero) to the given closure. + * <pre class="groovyTestCase"> + * String[] letters = ['a', 'b', 'c'] + * String result = '' + * letters.eachWithIndex{ letter, index -> result += "$index:$letter" } + * assert result == '0:a1:b2:c' + * </pre> + * + * @param self an array + * @param closure a Closure to operate on each array entry + * @return the self array + * @since 2.5.0 + */ + public static <T> T[] eachWithIndex(T[] self, @ClosureParams(value=FromString.class, options="T,Integer") Closure closure) { + final Object[] args = new Object[2]; + int counter = 0; + for(T item : self) { + args[0] = item; + args[1] = counter++; + closure.call(args); + } + return self; + } + + /** + * Iterates through an aggregate type or data structure, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * <pre class="groovyTestCase"> + * String result = '' + * ['a', 'b', 'c'].eachWithIndex{ letter, index -> result += "$index:$letter" } + * assert result == '0:a1:b2:c' + * </pre> + * + * @param self an Object + * @param closure a Closure to operate on each item + * @return the self Object + * @since 1.0 + */ + public static <T> T eachWithIndex(T self, /*@ClosureParams(value=FromString.class, options="?,Integer")*/ Closure closure) { + final Object[] args = new Object[2]; + int counter = 0; + for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) { + args[0] = iter.next(); + args[1] = counter++; + closure.call(args); + } + return self; + } + + /** + * Iterates through an iterable type, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self an Iterable + * @param closure a Closure to operate on each item + * @return the self Iterable + * @since 2.3.0 + */ + public static <T> Iterable<T> eachWithIndex(Iterable<T> self, @ClosureParams(value=FromString.class, options="T,java.lang.Integer") Closure closure) { + eachWithIndex(self.iterator(), closure); + return self; + } + + /** + * Iterates through an iterator type, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self an Iterator + * @param closure a Closure to operate on each item + * @return the self Iterator (now exhausted) + * @since 2.3.0 + */ + public static <T> Iterator<T> eachWithIndex(Iterator<T> self, @ClosureParams(value=FromString.class, options="T,java.lang.Integer") Closure closure) { + final Object[] args = new Object[2]; + int counter = 0; + while (self.hasNext()) { + args[0] = self.next(); + args[1] = counter++; + closure.call(args); + } + return self; + } + + /** + * Iterates through a Collection, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self a Collection + * @param closure a Closure to operate on each item + * @return the self Collection + * @since 2.4.0 + */ + public static <T> Collection<T> eachWithIndex(Collection<T> self, @ClosureParams(value=FromString.class, options="T,java.lang.Integer") Closure closure) { + return (Collection<T>) eachWithIndex((Iterable<T>) self, closure); + } + + /** + * Iterates through a List, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self a List + * @param closure a Closure to operate on each item + * @return the self List + * @since 2.4.0 + */ + public static <T> List<T> eachWithIndex(List<T> self, @ClosureParams(value=FromString.class, options="T,java.lang.Integer") Closure closure) { + return (List<T>) eachWithIndex((Iterable<T>) self, closure); + } + + /** + * Iterates through a Set, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self a Set + * @param closure a Closure to operate on each item + * @return the self Set + * @since 2.4.0 + */ + public static <T> Set<T> eachWithIndex(Set<T> self, @ClosureParams(value=FromString.class, options="T,java.lang.Integer") Closure closure) { + return (Set<T>) eachWithIndex((Iterable<T>) self, closure); + } + + /** + * Iterates through a SortedSet, + * passing each item and the item's index (a counter starting at + * zero) to the given closure. + * + * @param self a SortedSet + * @param closure a Closure to operate on each item + * @return the self SortedSet + * @since 2.4.0 + */ + public static <T> Sort
<TRUNCATED>
