View Javadoc

    1   /*
    2    * $Id: DefaultGroovyMethods.java,v 1.125 2005/01/04 01:52:28 spullara Exp $
    3    *
    4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5    *
    6    * Redistribution and use of this software and associated documentation
    7    * ("Software"), with or without modification, are permitted provided that the
    8    * following conditions are met:
    9    *  1. Redistributions of source code must retain copyright statements and
    10   * notices. Redistributions must also contain a copy of this document.
    11   *  2. Redistributions in binary form must reproduce the above copyright
    12   * notice, this list of conditions and the following disclaimer in the
    13   * documentation and/or other materials provided with the distribution.
    14   *  3. The name "groovy" must not be used to endorse or promote products
    15   * derived from this Software without prior written permission of The Codehaus.
    16   * For written permission, please contact info@codehaus.org.
    17   *  4. Products derived from this Software may not be called "groovy" nor may
    18   * "groovy" appear in their names without prior written permission of The
    19   * Codehaus. "groovy" is a registered trademark of The Codehaus.
    20   *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
    21   *
    22   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    23   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    24   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    25   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    26   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    27   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    28   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    29   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    30   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    31   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    32   * DAMAGE.
    33   *
    34   */
    35  package org.codehaus.groovy.runtime;
    36
    37  import groovy.lang.*;
    38  import groovy.util.CharsetToolkit;
    39  import groovy.util.ClosureComparator;
    40  import groovy.util.OrderBy;
    41
    42  import java.io.*;
    43  import java.lang.reflect.Array;
    44  import java.lang.reflect.Field;
    45  import java.lang.reflect.Modifier;
    46  import java.net.MalformedURLException;
    47  import java.net.ServerSocket;
    48  import java.net.Socket;
    49  import java.net.URL;
    50  import java.security.AccessController;
    51  import java.security.PrivilegedAction;
    52  import java.util.*;
    53  import java.util.logging.Logger;
    54  import java.util.regex.Matcher;
    55  import java.util.regex.Pattern;
    56
    57  /***
    58   * This class defines all the new groovy methods which appear on normal JDK
    59   * classes inside the Groovy environment. Static methods are used with the
    60   * first parameter the destination class.
    61   *
    62   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    63   * @author Jeremy Rayner
    64   * @author Sam Pullara
    65   * @author Rod Cope
    66   * @author Guillaume Laforge
    67   * @author John Wilson
    68   * @version $Revision: 1.125 $
    69   */
    70  public class DefaultGroovyMethods {
    71
    72      private static Logger log = Logger.getLogger(DefaultGroovyMethods.class.getName());
    73
    74      private static final Integer ONE = new Integer(1);
    75      private static final char ZERO_CHAR = '\u0000';
    76
    77      /***
    78       * Allows the subscript operator to be used to lookup dynamic property values.
    79       * <code>bean[somePropertyNameExpression]</code>. The normal property notation
    80       * of groovy is neater and more concise but only works with compile time known
    81       * property names.
    82       *
    83       * @param self
    84       * @return
    85       */
    86      public static Object getAt(Object self, String property) {
    87          return InvokerHelper.getProperty(self, property);
    88      }
    89
    90      /***
    91       * Allows the subscript operator to be used to set dynamically named property values.
    92       * <code>bean[somePropertyNameExpression] = foo</code>. The normal property notation
    93       * of groovy is neater and more concise but only works with compile time known
    94       * property names.
    95       *
    96       * @param self
    97       */
    98      public static void putAt(Object self, String property, Object newValue) {
    99          InvokerHelper.setProperty(self, property, newValue);
    100     }
    101
    102     /***
    103      * Generates a detailed dump string of an object showing its class,
    104      * hashCode and fields
    105      */
    106     public static String dump(Object self) {
    107         if (self == null) {
    108             return "null";
    109         }
    110         StringBuffer buffer = new StringBuffer("<");
    111         Class klass = self.getClass();
    112         buffer.append(klass.getName());
    113         buffer.append("@");
    114         buffer.append(Integer.toHexString(self.hashCode()));
    115         boolean groovyObject = self instanceof GroovyObject;
    116
    117         /*jes this may be rewritten to use the new allProperties() stuff
    118          * but the original pulls out private variables, whereas allProperties()
    119          * does not. What's the real use of dump() here?
    120          */
    121         while (klass != null) {
    122             Field[] fields = klass.getDeclaredFields();
    123             for (int i = 0; i < fields.length; i++) {
    124                 final Field field = fields[i];
    125                 if ((field.getModifiers() & Modifier.STATIC) == 0) {
    126                     if (groovyObject && field.getName().equals("metaClass")) {
    127                         continue;
    128                     }
    129                     AccessController.doPrivileged(new PrivilegedAction() {
    130                         public Object run() {
    131                             field.setAccessible(true);
    132                             return null;
    133                         }
    134                     });
    135                     buffer.append(" ");
    136                     buffer.append(field.getName());
    137                     buffer.append("=");
    138                     try {
    139                         buffer.append(InvokerHelper.toString(field.get(self)));
    140                     } catch (Exception e) {
    141                         buffer.append(e);
    142                     }
    143                 }
    144             }
    145
    146             klass = klass.getSuperclass();
    147         }
    148
    149         /* here is a different implementation that uses allProperties(). I have left
    150          * it commented out because it returns a slightly different list of properties;
    151          * ie it does not return privates. I don't know what dump() really should be doing,
    152          * although IMO showing private fields is a no-no
    153          */
    154         /*
    155         List props = allProperties(self);
    156 for(Iterator itr = props.iterator(); itr.hasNext(); ) {
    157 PropertyValue pv = (PropertyValue) itr.next();
    158
    159             // the original skipped this, so I will too
    160             if(pv.getName().equals("metaClass")) continue;
    161             if(pv.getName().equals("class")) continue;
    162
    163             buffer.append(" ");
    164             buffer.append(pv.getName());
    165             buffer.append("=");
    166             try {
    167                 buffer.append(InvokerHelper.toString(pv.getValue()));
    168             }
    169             catch (Exception e) {
    170                 buffer.append(e);
    171             }
    172 }
    173         */
    174
    175         buffer.append(">");
    176         return buffer.toString();
    177     }
    178
    179     public static void eachPropertyName(Object self, Closure closure) {
    180         List props = allProperties(self);
    181         for (Iterator itr = props.iterator(); itr.hasNext();) {
    182             PropertyValue pv = (PropertyValue) itr.next();
    183             closure.call(pv.getName());
    184         }
    185     }
    186
    187     public static void eachProperty(Object self, Closure closure) {
    188         List props = allProperties(self);
    189         for (Iterator itr = props.iterator(); itr.hasNext();) {
    190             PropertyValue pv = (PropertyValue) itr.next();
    191             closure.call(pv);
    192         }
    193     }
    194
    195     public static List allProperties(Object self) {
    196         List props = new ArrayList();
    197         MetaClass metaClass = InvokerHelper.getMetaClass(self);
    198
    199         List mps;
    200
    201         if (self instanceof groovy.util.Expando) {
    202             mps = ((groovy.util.Expando) self).getProperties();
    203         } else {
    204             // get the MetaProperty list from the MetaClass
    205             mps = metaClass.getProperties();
    206         }
    207
    208         for (Iterator itr = mps.iterator(); itr.hasNext();) {
    209             MetaProperty mp = (MetaProperty) itr.next();
    210             PropertyValue pv = new PropertyValue(self, mp);
    211             props.add(pv);
    212         }
    213
    214         return props;
    215     }
    216
    217     /***
    218      * Scoped use method
    219      */
    220     public static void use(Object self, Class categoryClass, Closure closure) {
    221         GroovyCategorySupport.use(categoryClass, closure);
    222     }
    223
    224     /***
    225      * Scoped use method with list of categories
    226      */
    227     public static void use(Object self, List categoryClassList, Closure closure) {
    228         GroovyCategorySupport.use(categoryClassList, closure);
    229     }
    230
    231
    232     /***
    233      * Print to a console in interactive format
    234      */
    235     public static void print(Object self, Object value) {
    236         System.out.print(InvokerHelper.toString(value));
    237     }
    238
    239     /***
    240      * Print a linebreak to the standard out.
    241      */
    242     public static void println(Object self) {
    243         System.out.println();
    244     }
    245
    246     /***
    247      * Print to a console in interactive format along with a newline
    248      */
    249     public static void println(Object self, Object value) {
    250         System.out.println(InvokerHelper.toString(value));
    251     }
    252
    253     /***
    254      * @return a String that matches what would be typed into a terminal to
    255      *         create this object. e.g. [1, 'hello'].inspect() -> [1, "hello"]
    256      */
    257     public static String inspect(Object self) {
    258         return InvokerHelper.inspect(self);
    259     }
    260
    261     /***
    262      * Print to a console in interactive format
    263      */
    264     public static void print(Object self, PrintWriter out) {
    265         if (out == null) {
    266             out = new PrintWriter(System.out);
    267         }
    268         out.print(InvokerHelper.toString(self));
    269     }
    270
    271     /***
    272      * Print to a console in interactive format
    273      *
    274      * @param out the PrintWriter used for printing
    275      */
    276     public static void println(Object self, PrintWriter out) {
    277         if (out == null) {
    278             out = new PrintWriter(System.out);
    279         }
    280         InvokerHelper.invokeMethod(self, "print", out);
    281         out.println();
    282     }
    283
    284     /***
    285      * Provide a dynamic method invocation method which can be overloaded in
    286      * classes to implement dynamic proxies easily.
    287      */
    288     public static Object invokeMethod(Object object, String method, Object arguments) {
    289         return InvokerHelper.invokeMethod(object, method, arguments);
    290     }
    291
    292     // isCase methods
    293     //-------------------------------------------------------------------------
    294     public static boolean isCase(Object caseValue, Object switchValue) {
    295         return caseValue.equals(switchValue);
    296     }
    297
    298     public static boolean isCase(String caseValue, Object switchValue) {
    299         if (switchValue == null) {
    300             return caseValue == null;
    301         }
    302         return caseValue.equals(switchValue.toString());
    303     }
    304
    305     public static boolean isCase(Class caseValue, Object switchValue) {
    306         return caseValue.isInstance(switchValue);
    307     }
    308
    309     public static boolean isCase(Collection caseValue, Object switchValue) {
    310         return caseValue.contains(switchValue);
    311     }
    312
    313     public static boolean isCase(Pattern caseValue, Object switchValue) {
    314         Matcher matcher = caseValue.matcher(switchValue.toString());
    315         if (matcher.matches()) {
    316             RegexSupport.setLastMatcher(matcher);
    317             return true;
    318         } else {
    319             return false;
    320         }
    321     }
    322
    323     // Collection based methods
    324     //-------------------------------------------------------------------------
    325
    326     /***
    327      * Allows objects to be iterated through using a closure
    328      *
    329      * @param self    the object over which we iterate
    330      * @param closure the closure applied on each element found
    331      */
    332     public static void each(Object self, Closure closure) {
    333         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    334             closure.call(iter.next());
    335         }
    336     }
    337
    338     /***
    339      * Allows object to be iterated through a closure with a counter
    340      *
    341      * @param self    an Object
    342      * @param closure a Closure
    343      */
    344     public static void eachWithIndex(Object self, Closure closure) {
    345         int counter = 0;
    346         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    347             closure.call(new Object[]{iter.next(), new Integer(counter++)});
    348         }
    349     }
    350
    351     /***
    352      * Allows objects to be iterated through using a closure
    353      *
    354      * @param self    the collection over which we iterate
    355      * @param closure the closure applied on each element of the collection
    356      */
    357     public static void each(Collection self, Closure closure) {
    358         for (Iterator iter = self.iterator(); iter.hasNext();) {
    359             closure.call(iter.next());
    360         }
    361     }
    362
    363     /***
    364      * Allows a Map to be iterated through using a closure. If the
    365      * closure takes one parameter then it will be passed the Map.Entry
    366      * otherwise if the closure takes two parameters then it will be
    367      * passed the key and the value.
    368      *
    369      * @param self    the map over which we iterate
    370      * @param closure the closure applied on each entry of the map
    371      */
    372     public static void each(Map self, Closure closure) {
    373         if (closure.getParameterTypes().length == 2) {
    374             for (Iterator iter = self.entrySet().iterator(); iter.hasNext();) {
    375                 Map.Entry entry = (Map.Entry) iter.next();
    376                 closure.call(new Object[]{entry.getKey(), entry.getValue()});
    377             }
    378         } else {
    379             for (Iterator iter = self.entrySet().iterator(); iter.hasNext();) {
    380                 closure.call(iter.next());
    381             }
    382         }
    383     }
    384
    385     /***
    386      * Iterates over every element of a collection, and check whether a predicate is valid for all elements.
    387      *
    388      * @param self    the object over which we iterate
    389      * @param closure the closure predicate used for matching
    390      * @return true if every item in the collection matches the closure
    391      *         predicate
    392      */
    393     public static boolean every(Object self, Closure closure) {
    394         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    395             if (!InvokerHelper.asBool(closure.call(iter.next()))) {
    396                 return false;
    397             }
    398         }
    399         return true;
    400     }
    401
    402     /***
    403      * Iterates over every element of a collection, and check whether a predicate is valid for at least one element
    404      *
    405      * @param self    the object over which we iterate
    406      * @param closure the closure predicate used for matching
    407      * @return true if any item in the collection matches the closure predicate
    408      */
    409     public static boolean any(Object self, Closure closure) {
    410         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    411             if (InvokerHelper.asBool(closure.call(iter.next()))) {
    412                 return true;
    413             }
    414         }
    415         return false;
    416     }
    417
    418     /***
    419      * Iterates over every element of the collection and return each object that matches
    420      * the given filter - calling the isCase() method used by switch statements.
    421      * This method can be used with different kinds of filters like regular expresions, classes, ranges etc.
    422      *
    423      * @param self   the object over which we iterate
    424      * @param filter the filter to perform on the collection (using the isCase(object) method)
    425      * @return a list of objects which match the filter
    426      */
    427     public static List grep(Object self, Object filter) {
    428         List answer = new ArrayList();
    429         MetaClass metaClass = InvokerHelper.getMetaClass(filter);
    430         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    431             Object object = iter.next();
    432             if (InvokerHelper.asBool(metaClass.invokeMethod(filter, "isCase", object))) {
    433                 answer.add(object);
    434             }
    435         }
    436         return answer;
    437     }
    438
    439     /***
    440      * Counts the number of occurencies of the given value inside this collection
    441      *
    442      * @param self  the collection within which we count the number of occurencies
    443      * @param value the value
    444      * @return the number of occurrencies
    445      */
    446     public static int count(Collection self, Object value) {
    447         int answer = 0;
    448         for (Iterator iter = self.iterator(); iter.hasNext();) {
    449             if (InvokerHelper.compareEqual(iter.next(), value)) {
    450                 ++answer;
    451             }
    452         }
    453         return answer;
    454     }
    455
    456     /***
    457      * Convert a collection to a List.
    458      *
    459      * @param self a collection
    460      * @return a List
    461      */
    462     public static List toList(Collection self) {
    463         List answer = new ArrayList(self.size());
    464         answer.addAll(self);
    465         return answer;
    466     }
    467
    468     /***
    469      * Iterates through this object transforming each object into a new value using the closure
    470      * as a transformer, returning a list of transformed values.
    471      *
    472      * @param self    the values of the object to map
    473      * @param closure the closure used to map each element of the collection
    474      * @return a List of the mapped values
    475      */
    476     public static List collect(Object self, Closure closure) {
    477         return (List) collect(self, new ArrayList(), closure);
    478     }
    479
    480     /***
    481      * Iterates through this object transforming each object into a new value using the closure
    482      * as a transformer and adding it to the collection, returning the resulting collection.
    483      *
    484      * @param self       the values of the object to map
    485      * @param collection the Collection to which the mapped values are added
    486      * @param closure    the closure used to map each element of the collection
    487      * @return the resultant collection
    488      */
    489     public static Collection collect(Object self, Collection collection, Closure closure) {
    490         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    491             collection.add(closure.call(iter.next()));
    492         }
    493         return collection;
    494     }
    495
    496     /***
    497      * Iterates through this collection transforming each entry into a new value using the closure
    498      * as a transformer, returning a list of transformed values.
    499      *
    500      * @param self    a collection
    501      * @param closure the closure used for mapping
    502      * @return a List of the mapped values
    503      */
    504     public static List collect(Collection self, Closure closure) {
    505         return (List) collect(self, new ArrayList(self.size()), closure);
    506     }
    507
    508     /***
    509      * Iterates through this collection transforming each entry into a new value using the closure
    510      * as a transformer, returning a list of transformed values.
    511      *
    512      * @param self       a collection
    513      * @param collection the Collection to which the mapped values are added
    514      * @param closure    the closure used to map each element of the collection
    515      * @return the resultant collection
    516      */
    517     public static Collection collect(Collection self, Collection collection, Closure closure) {
    518         for (Iterator iter = self.iterator(); iter.hasNext();) {
    519             collection.add(closure.call(iter.next()));
    520             if (closure.getDirective() == Closure.DONE) {
    521                 break;
    522             }
    523         }
    524         return collection;
    525     }
    526
    527     /***
    528      * Iterates through this Map transforming each entry into a new value using the closure
    529      * as a transformer, returning a list of transformed values.
    530      *
    531      * @param self    a Map
    532      * @param closure the closure used for mapping
    533      * @return a List of the mapped values
    534      */
    535     public static Collection collect(Map self, Collection collection, Closure closure) {
    536         for (Iterator iter = self.entrySet().iterator(); iter.hasNext();) {
    537             collection.add(closure.call(iter.next()));
    538         }
    539         return collection;
    540     }
    541
    542     /***
    543      * Iterates through this Map transforming each entry into a new value using the closure
    544      * as a transformer, returning a list of transformed values.
    545      *
    546      * @param self       a Map
    547      * @param collection the Collection to which the mapped values are added
    548      * @param closure    the closure used to map each element of the collection
    549      * @return the resultant collection
    550      */
    551     public static List collect(Map self, Closure closure) {
    552         return (List) collect(self, new ArrayList(self.size()), closure);
    553     }
    554
    555     /***
    556      * Finds the first value matching the closure condition
    557      *
    558      * @param self    an Object with an iterator returning its values
    559      * @param closure a closure condition
    560      * @return the first Object found
    561      */
    562     public static Object find(Object self, Closure closure) {
    563         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    564             Object value = iter.next();
    565             if (InvokerHelper.asBool(closure.call(value))) {
    566                 return value;
    567             }
    568         }
    569         return null;
    570     }
    571
    572     /***
    573      * Finds the first value matching the closure condition
    574      *
    575      * @param self    a Collection
    576      * @param closure a closure condition
    577      * @return the first Object found
    578      */
    579     public static Object find(Collection self, Closure closure) {
    580         for (Iterator iter = self.iterator(); iter.hasNext();) {
    581             Object value = iter.next();
    582             if (InvokerHelper.asBool(closure.call(value))) {
    583                 return value;
    584             }
    585         }
    586         return null;
    587     }
    588
    589     /***
    590      * Finds the first value matching the closure condition
    591      *
    592      * @param self    a Map
    593      * @param closure a closure condition
    594      * @return the first Object found
    595      */
    596     public static Object find(Map self, Closure closure) {
    597         for (Iterator iter = self.entrySet().iterator(); iter.hasNext();) {
    598             Object value = iter.next();
    599             if (InvokerHelper.asBool(closure.call(value))) {
    600                 return value;
    601             }
    602         }
    603         return null;
    604     }
    605
    606     /***
    607      * Finds all values matching the closure condition
    608      *
    609      * @param self    an Object with an Iterator returning its values
    610      * @param closure a closure condition
    611      * @return a List of the values found
    612      */
    613     public static List findAll(Object self, Closure closure) {
    614         List answer = new ArrayList();
    615         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext();) {
    616             Object value = iter.next();
    617             if (InvokerHelper.asBool(closure.call(value))) {
    618                 answer.add(value);
    619             }
    620         }
    621         return answer;
    622     }
    623
    624     /***
    625      * Finds all values matching the closure condition
    626      *
    627      * @param self    a Collection
    628      * @param closure a closure condition
    629      * @return a List of the values found
    630      */
    631     public static List findAll(Collection self, Closure closure) {
    632         List answer = new ArrayList(self.size());
    633         for (Iterator iter = self.iterator(); iter.hasNext();) {
    634             Object value = iter.next();
    635             if (InvokerHelper.asBool(closure.call(value))) {
    636                 answer.add(value);
    637             }
    638         }
    639         return answer;
    640     }
    641
    642     /***
    643      * Finds all values matching the closure condition
    644      *
    645      * @param self    a Map
    646      * @param closure a closure condition applying on the keys
    647      * @return a List of keys
    648      */
    649     public static List findAll(Map self, Closure closure) {
    650         List answer = new ArrayList(self.size());
    651         for (Iterator iter = self.entrySet().iterator(); iter.hasNext();) {
    652             Object value = iter.next();
    653             if (InvokerHelper.asBool(closure.call(value))) {
    654                 answer.add(value);
    655             }
    656         }
    657         return answer;
    658     }
    659
    660     /***
    661      * Iterates through the given collection, passing in the initial value to
    662      * the closure along with the current iterated item then passing into the
    663      * next iteration the value of the previous closure.
    664      *
    665      * @param self    a Collection
    666      * @param value   a value
    667      * @param closure a closure
    668      * @return the last value of the last iteration
    669      */
    670     public static Object inject(Collection self, Object value, Closure closure) {
    671         Object[] params = new Object[2];
    672         for (Iterator iter = self.iterator(); iter.hasNext();) {
    673             Object item = iter.next();
    674             params[0] = value;
    675             params[1] = item;
    676             value = closure.call(params);
    677         }
    678         return value;
    679     }
    680
    681     /***
    682      * Concatenates all of the items of the collection together with the given String as a separator
    683      *
    684      * @param self      a Collection of objects
    685      * @param separator a String separator
    686      * @return the joined String
    687      */
    688     public static String join(Collection self, String separator) {
    689         StringBuffer buffer = new StringBuffer();
    690         boolean first = true;
    691         for (Iterator iter = self.iterator(); iter.hasNext();) {
    692             Object value = iter.next();
    693             if (first) {
    694                 first = false;
    695             } else {
    696                 buffer.append(separator);
    697             }
    698             buffer.append(InvokerHelper.toString(value));
    699         }
    700         return buffer.toString();
    701     }
    702
    703     /***
    704      * Concatenates all of the elements of the array together with the given String as a separator
    705      *
    706      * @param self      an array of Object
    707      * @param separator a String separator
    708      * @return the joined String
    709      */
    710     public static String join(Object[] self, String separator) {
    711         StringBuffer buffer = new StringBuffer();
    712         boolean first = true;
    713         for (int i = 0; i < self.length; i++) {
    714             String value = InvokerHelper.toString(self[i]);
    715             if (first) {
    716                 first = false;
    717             } else {
    718                 buffer.append(separator);
    719             }
    720             buffer.append(value);
    721         }
    722         return buffer.toString();
    723     }
    724
    725     /***
    726      * Selects the maximum value found in the collection
    727      *
    728      * @param self a Collection
    729      * @return the maximum value
    730      */
    731     public static Object max(Collection self) {
    732         Object answer = null;
    733         for (Iterator iter = self.iterator(); iter.hasNext();) {
    734             Object value = iter.next();
    735             if (value != null) {
    736                 if (answer == null || InvokerHelper.compareGreaterThan(value, answer)) {
    737                     answer = value;
    738                 }
    739             }
    740         }
    741         return answer;
    742     }
    743
    744     /***
    745      * Selects the maximum value found in the collection using the given comparator
    746      *
    747      * @param self       a Collection
    748      * @param comparator a Comparator
    749      * @return the maximum value
    750      */
    751     public static Object max(Collection self, Comparator comparator) {
    752         Object answer = null;
    753         for (Iterator iter = self.iterator(); iter.hasNext();) {
    754             Object value = iter.next();
    755             if (answer == null || comparator.compare(value, answer) > 0) {
    756                 answer = value;
    757             }
    758         }
    759         return answer;
    760     }
    761
    762     /***
    763      * Selects the minimum value found in the collection
    764      *
    765      * @param self a Collection
    766      * @return the minimum value
    767      */
    768     public static Object min(Collection self) {
    769         Object answer = null;
    770         for (Iterator iter = self.iterator(); iter.hasNext();) {
    771             Object value = iter.next();
    772             if (value != null) {
    773                 if (answer == null || InvokerHelper.compareLessThan(value, answer)) {
    774                     answer = value;
    775                 }
    776             }
    777         }
    778         return answer;
    779     }
    780
    781     /***
    782      * Selects the minimum value found in the collection using the given comparator
    783      *
    784      * @param self       a Collection
    785      * @param comparator a Comparator
    786      * @return the minimum value
    787      */
    788     public static Object min(Collection self, Comparator comparator) {
    789         Object answer = null;
    790         for (Iterator iter = self.iterator(); iter.hasNext();) {
    791             Object value = iter.next();
    792             if (answer == null || comparator.compare(value, answer) < 0) {
    793                 answer = value;
    794
    795             }
    796         }
    797         return answer;
    798     }
    799
    800     /***
    801      * Selects the minimum value found in the collection using the given closure as a comparator
    802      *
    803      * @param self    a Collection
    804      * @param closure a closure used as a comparator
    805      * @return the minimum value
    806      */
    807     public static Object min(Collection self, Closure closure) {
    808         return min(self, new ClosureComparator(closure));
    809     }
    810
    811     /***
    812      * Selects the maximum value found in the collection using the given closure as a comparator
    813      *
    814      * @param self    a Collection
    815      * @param closure a closure used as a comparator
    816      * @return the maximum value
    817      */
    818     public static Object max(Collection self, Closure closure) {
    819         return max(self, new ClosureComparator(closure));
    820     }
    821
    822     /***
    823      * Makes a String look like a Collection by adding support for the size() method
    824      *
    825      * @param text a String
    826      * @return the length of the String
    827      */
    828     public static int size(String text) {
    829         return text.length();
    830     }
    831
    832     /***
    833      * Makes an Array look like a Collection by adding support for the size() method
    834      *
    835      * @param self an Array of Object
    836      * @return the size of the Array
    837      */
    838     public static int size(Object[] self) {
    839         return self.length;
    840     }
    841
    842     /***
    843      * Support the subscript operator for String.
    844      *
    845      * @param text  a String
    846      * @param index the index of the Character to get
    847      * @return the Character at the given index
    848      */
    849     public static CharSequence getAt(CharSequence text, int index) {
    850         index = normaliseIndex(index, text.length());
    851         return text.subSequence(index, index + 1);
    852     }
    853
    854     /***
    855      * Support the subscript operator for String
    856      *
    857      * @param text a String
    858      * @return the Character object at the given index
    859      */
    860     public static String getAt(String text, int index) {
    861         index = normaliseIndex(index, text.length());
    862         return text.substring(index, index + 1);
    863     }
    864
    865     /***
    866      * Support the range subscript operator for CharSequence
    867      *
    868      * @param text  a CharSequence
    869      * @param range a Range
    870      * @return the subsequence CharSequence
    871      */
    872     public static CharSequence getAt(CharSequence text, Range range) {
    873         int from = normaliseIndex(InvokerHelper.asInt(range.getFrom()), text.length());
    874         int to = normaliseIndex(InvokerHelper.asInt(range.getTo()), text.length());
    875
    876         // if this is a backwards range, reverse the arguments to substring
    877         if (from > to) {
    878             int tmp = from;
    879             from = to;
    880             to = tmp;
    881         }
    882
    883         return text.subSequence(from, to + 1);
    884     }
    885
    886     /***
    887      * Support the range subscript operator for String
    888      *
    889      * @param text  a String
    890      * @param range a Range
    891      * @return a substring corresponding to the Range
    892      */
    893     public static String getAt(String text, Range range) {
    894         int from = normaliseIndex(InvokerHelper.asInt(range.getFrom()), text.length());
    895         int to = normaliseIndex(InvokerHelper.asInt(range.getTo()), text.length());
    896
    897         // if this is a backwards range, reverse the arguments to substring
    898         boolean reverse = range.isReverse();
    899         if (from > to) {
    900             int tmp = to;
    901             to = from;
    902             from = tmp;
    903             reverse = !reverse;
    904         }
    905
    906         String answer = text.substring(from, to + 1);
    907         if (reverse) {
    908             answer = reverse(answer);
    909         }
    910         return answer;
    911     }
    912
    913     /***
    914      * Creates a new string which is the reverse (backwards) of this string
    915      *
    916      * @param self a String
    917      * @return a new string with all the characters reversed.
    918      */
    919     public static String reverse(String self) {
    920         int size = self.length();
    921         StringBuffer buffer = new StringBuffer(size);
    922         for (int i = size - 1; i >= 0; i--) {
    923             buffer.append(self.charAt(i));
    924         }
    925         return buffer.toString();
    926     }
    927
    928     /***
    929      * Transforms a String representing a URL into a URL object.
    930      *
    931      * @param self the String representing a URL
    932      * @return a URL
    933      * @throws MalformedURLException is thrown if the URL is not well formed.
    934      */
    935     public static URL toURL(String self) throws MalformedURLException {
    936         return new URL(self);
    937     }
    938
    939     private static String getPadding(String padding, int length) {
    940         if (padding.length() < length) {
    941             return multiply(padding, new Integer(length / padding.length() + 1)).substring(0, length);
    942         } else {
    943             return padding.substring(0, length);
    944         }
    945     }
    946
    947     /***
    948      * Pad a String with the characters appended to the left
    949      *
    950      * @param numberOfChars the total number of characters
    951      * @param padding       the charaters used for padding
    952      * @return the String padded to the left
    953      */
    954     public static String padLeft(String self, Number numberOfChars, String padding) {
    955         int numChars = numberOfChars.intValue();
    956         if (numChars <= self.length()) {
    957             return self;
    958         } else {
    959             return getPadding(padding, numChars - self.length()) + self;
    960         }
    961     }
    962
    963     /***
    964      * Pad a String with the spaces appended to the left
    965      *
    966      * @param numberOfChars the total number of characters
    967      * @return the String padded to the left
    968      */
    969
    970     public static String padLeft(String self, Number numberOfChars) {
    971         return padLeft(self, numberOfChars, " ");
    972     }
    973
    974     /***
    975      * Pad a String with the characters appended to the right
    976      *
    977      * @param numberOfChars the total number of characters
    978      * @param padding       the charaters used for padding
    979      * @return the String padded to the right
    980      */
    981
    982     public static String padRight(String self, Number numberOfChars, String padding) {
    983         int numChars = numberOfChars.intValue();
    984         if (numChars <= self.length()) {
    985             return self;
    986         } else {
    987             return self + getPadding(padding, numChars - self.length());
    988         }
    989     }
    990
    991     /***
    992      * Pad a String with the spaces appended to the right
    993      *
    994      * @param numberOfChars the total number of characters
    995      * @return the String padded to the right
    996      */
    997
    998     public static String padRight(String self, Number numberOfChars) {
    999         return padRight(self, numberOfChars, " ");
    1000     }
    1001
    1002     /***
    1003      * Center a String and padd it with the characters appended around it
    1004      *
    1005      * @param numberOfChars the total number of characters
    1006      * @param padding       the charaters used for padding
    1007      * @return the String centered with padded character around
    1008      */
    1009     public static String center(String self, Number numberOfChars, String padding) {
    1010         int numChars = numberOfChars.intValue();
    1011         if (numChars <= self.length()) {
    1012             return self;
    1013         } else {
    1014             int charsToAdd = numChars - self.length();
    1015             String semiPad = charsToAdd % 2 == 1 ?
    1016                     getPadding(padding, charsToAdd / 2 + 1) :
    1017                     getPadding(padding, charsToAdd / 2);
    1018             if (charsToAdd % 2 == 0)
    1019                 return semiPad + self + semiPad;
    1020             else
    1021                 return semiPad.substring(0, charsToAdd / 2) + self + semiPad;
    1022         }
    1023     }
    1024
    1025     /***
    1026      * Center a String and padd it with spaces appended around it
    1027      *
    1028      * @param numberOfChars the total number of characters
    1029      * @return the String centered with padded character around
    1030      */
    1031     public static String center(String self, Number numberOfChars) {
    1032         return center(self, numberOfChars, " ");
    1033     }
    1034
    1035     /***
    1036      * Support the subscript operator for a regex Matcher
    1037      *
    1038      * @param matcher a Matcher
    1039      * @param idx     an index
    1040      * @return the group at the given index
    1041      */
    1042     public static String getAt(Matcher matcher, int idx) {
    1043         matcher.reset();
    1044         idx = normaliseIndex(idx, matcher.groupCount());
    1045
    1046         // are we using groups?
    1047         if (matcher.groupCount() > 0) {
    1048             // yes, so return the specified group
    1049             matcher.find();
    1050             return matcher.group(idx);
    1051         } else {
    1052             // not using groups, so return the nth
    1053             // occurrence of the pattern
    1054             for (int i = 0; i <= idx; i++) {
    1055                 matcher.find();
    1056             }
    1057             return matcher.group();
    1058         }
    1059     }
    1060
    1061     /***
    1062      * Support the range subscript operator for a List
    1063      *
    1064      * @param self  a List
    1065      * @param range a Range
    1066      * @return a range of a list from the range's from index up to but not including the ranges's to value
    1067      */
    1068     public static List getAt(List self, Range range) {
    1069         int size = self.size();
    1070         int from = normaliseIndex(InvokerHelper.asInt(range.getFrom()), size);
    1071         int to = normaliseIndex(InvokerHelper.asInt(range.getTo()), size);
    1072         boolean reverse = range.isReverse();
    1073         if (from > to) {
    1074             int tmp = to;
    1075             to = from;
    1076             from = tmp;
    1077             reverse = !reverse;
    1078         }
    1079         if (++to > size) {
    1080             to = size;
    1081         }
    1082         List answer = self.subList(from, to);
    1083         if (reverse) {
    1084             answer = reverse(answer);
    1085         }
    1086         return answer;
    1087     }
    1088
    1089     /***
    1090      * Allows a List to be used as the indices to be used on a List
    1091      *
    1092      * @param self    a List
    1093      * @param indices a Collection of indices
    1094      * @return a new list of the values at the given indices
    1095      */
    1096     public static List getAt(List self, Collection indices) {
    1097         List answer = new ArrayList(indices.size());
    1098         for (Iterator iter = indices.iterator(); iter.hasNext();) {
    1099             Object value = iter.next();
    1100             if (value instanceof Range) {
    1101                 answer.addAll(getAt(self, (Range) value));
    1102             } else if (value instanceof List) {
    1103                 answer.addAll(getAt(self, (List) value));
    1104             } else {
    1105                 int idx = InvokerHelper.asInt(value);
    1106                 answer.add(getAt(self, idx));
    1107             }
    1108         }
    1109         return answer;
    1110     }
    1111
    1112     /***
    1113      * Allows a List to be used as the indices to be used on a List
    1114      *
    1115      * @param self    an Array of Objects
    1116      * @param indices a Collection of indices
    1117      * @return a new list of the values at the given indices
    1118      */
    1119     public static List getAt(Object[] self, Collection indices) {
    1120         List answer = new ArrayList(indices.size());
    1121         for (Iterator iter = indices.iterator(); iter.hasNext();) {
    1122             Object value = iter.next();
    1123             if (value instanceof Range) {
    1124                 answer.addAll(getAt(self, (Range) value));
    1125             } else if (value instanceof Collection) {
    1126                 answer.addAll(getAt(self, (Collection) value));
    1127             } else {
    1128                 int idx = InvokerHelper.asInt(value);
    1129                 answer.add(getAt(self, idx));
    1130             }
    1131         }
    1132         return answer;
    1133     }
    1134
    1135     /***
    1136      * Allows a List to be used as the indices to be used on a CharSequence
    1137      *
    1138      * @param self    a CharSequence
    1139      * @param indices a Collection of indices
    1140      * @return a String of the values at the given indices
    1141      */
    1142     public static CharSequence getAt(CharSequence self, Collection indices) {
    1143         StringBuffer answer = new StringBuffer();
    1144         for (Iterator iter = indices.iterator(); iter.hasNext();) {
    1145             Object value = iter.next();
    1146             if (value instanceof Range) {
    1147                 answer.append(getAt(self, (Range) value));
    1148             } else if (value instanceof Collection) {
    1149                 answer.append(getAt(self, (Collection) value));
    1150             } else {
    1151                 int idx = InvokerHelper.asInt(value);
    1152                 answer.append(getAt(self, idx));
    1153             }
    1154         }
    1155         return answer.toString();
    1156     }
    1157
    1158     /***
    1159      * Allows a List to be used as the indices to be used on a String
    1160      *
    1161      * @param self    a String
    1162      * @param indices a Collection of indices
    1163      * @return a String of the values at the given indices
    1164      */
    1165     public static String getAt(String self, Collection indices) {
    1166         return (String) getAt((CharSequence) self, indices);
    1167     }
    1168
    1169     /***
    1170      * Allows a List to be used as the indices to be used on a Matcher
    1171      *
    1172      * @param self    a Matcher
    1173      * @param indices a Collection of indices
    1174      * @return a String of the values at the given indices
    1175      */
    1176     public static String getAt(Matcher self, Collection indices) {
    1177         StringBuffer answer = new StringBuffer();
    1178         for (Iterator iter = indices.iterator(); iter.hasNext();) {
    1179             Object value = iter.next();
    1180             if (value instanceof Range) {
    1181                 answer.append(getAt(self, (Range) value));
    1182             } else if (value instanceof Collection) {
    1183                 answer.append(getAt(self, (Collection) value));
    1184             } else {
    1185                 int idx = InvokerHelper.asInt(value);
    1186                 answer.append(getAt(self, idx));
    1187             }
    1188         }
    1189         return answer.toString();
    1190     }
    1191
    1192     /***
    1193      * Creates a sub-Map containing the given keys. This method is similar to
    1194      * List.subList() but uses keys rather than index ranges.
    1195      *
    1196      * @param map  a Map
    1197      * @param keys a Collection of keys
    1198      * @return a new Map containing the given keys
    1199      */
    1200     public static Map subMap(Map map, Collection keys) {
    1201         Map answer = new HashMap(keys.size());
    1202         for (Iterator iter = keys.iterator(); iter.hasNext();) {
    1203             Object key = iter.next();
    1204             answer.put(key, map.get(key));
    1205         }
    1206         return answer;
    1207     }
    1208
    1209     /***
    1210      * Looks up an item in a Map for the given key and returns the value - unless
    1211      * there is no entry for the given key in which case add the default value
    1212      * to the map and return that.
    1213      *
    1214      * @param map          a Map
    1215      * @param key          the key to lookup the value of
    1216      * @param defaultValue the value to return and add to the map for this key if
    1217      *                     there is no entry for the given key
    1218      * @return the value of the given key or the default value, added to the map if the
    1219      *         key did not exist
    1220      */
    1221     public static Object get(Map map, Object key, Object defaultValue) {
    1222         Object answer = map.get(key);
    1223         if (answer == null) {
    1224             answer = defaultValue;
    1225             map.put(key, answer);
    1226         }
    1227         return answer;
    1228     }
    1229
    1230     /***
    1231      * Support the range subscript operator for an Array
    1232      *
    1233      * @param array an Array of Objects
    1234      * @param range a Range
    1235      * @return a range of a list from the range's from index up to but not
    1236      *         including the ranges's to value
    1237      */
    1238     public static List getAt(Object[] array, Range range) {
    1239         List list = Arrays.asList(array);
    1240         return getAt(list, range);
    1241     }
    1242
    1243     /***
    1244      * Support the subscript operator for an Array
    1245      *
    1246      * @param array an Array of Objects
    1247      * @param idx   an index
    1248      * @return the value at the given index
    1249      */
    1250     public static Object getAt(Object[] array, int idx) {
    1251         return array[normaliseIndex(idx, array.length)];
    1252     }
    1253
    1254     /***
    1255      * Support the subscript operator for an Array
    1256      *
    1257      * @param array an Array of Objects
    1258      * @param idx   an index
    1259      * @param value an Object to put at the given index
    1260      */
    1261     public static void putAt(Object[] array, int idx, Object value) {
    1262         if (value instanceof Number) {
    1263             Class arrayComponentClass = array.getClass().getComponentType();
    1264
    1265             if (!arrayComponentClass.equals(value.getClass())) {
    1266                 Object newVal = InvokerHelper.asType(value, arrayComponentClass);
    1267                 array[normaliseIndex(idx, array.length)] = newVal;
    1268                 return;
    1269             }
    1270         }
    1271         array[normaliseIndex(idx, array.length)] = value;
    1272     }
    1273
    1274     /***
    1275      * Allows conversion of arrays into a mutable List
    1276      *
    1277      * @param array an Array of Objects
    1278      * @return the array as a List
    1279      */
    1280     public static List toList(Object[] array) {
    1281         int size = array.length;
    1282         List list = new ArrayList(size);
    1283         for (int i = 0; i < size; i++) {
    1284             list.add(array[i]);
    1285         }
    1286         return list;
    1287     }
    1288
    1289     /***
    1290      * Support the subscript operator for a List
    1291      *
    1292      * @param self a List
    1293      * @param idx  an index
    1294      * @return the value at the given index
    1295      */
    1296     public static Object getAt(List self, int idx) {
    1297         int size = self.size();
    1298         int i = normaliseIndex(idx, size);
    1299         if (i < size) {
    1300             return self.get(i);
    1301         } else {
    1302             return null;
    1303         }
    1304     }
    1305
    1306     /***
    1307      * A helper method to allow lists to work with subscript operators
    1308      *
    1309      * @param self  a List
    1310      * @param idx   an index
    1311      * @param value the value to put at the given index
    1312      */
    1313     public static void putAt(List self, int idx, Object value) {
    1314         int size = self.size();
    1315         idx = normaliseIndex(idx, size);
    1316         if (idx < size) {
    1317             self.set(idx, value);
    1318         } else {
    1319             while (size < idx) {
    1320                 self.add(size++, null);
    1321             }
    1322             self.add(idx, value);
    1323         }
    1324     }
    1325
    1326     /***
    1327      * Support the subscript operator for a List
    1328      *
    1329      * @param self a Map
    1330      * @param key  an Object as a key for the map
    1331      * @return the value corresponding to the given key
    1332      */
    1333     public static Object getAt(Map self, Object key) {
    1334         return self.get(key);
    1335     }
    1336
    1337     /***
    1338      * A helper method to allow lists to work with subscript operators
    1339      *
    1340      * @param self a Map
    1341      * @param key  an Object as a key for the map
    1342      * @return the value corresponding to the given key
    1343      */
    1344     public static Object putAt(Map self, Object key, Object value) {
    1345         return self.put(key, value);
    1346     }
    1347
    1348     /***
    1349      * This converts a possibly negative index to a real index into the array.
    1350      *
    1351      * @param i
    1352      * @param size
    1353      * @return
    1354      */
    1355     protected static int normaliseIndex(int i, int size) {
    1356         int temp = i;
    1357         if (i < 0) {
    1358             i += size;
    1359         }
    1360         if (i < 0) {
    1361             throw new ArrayIndexOutOfBoundsException("Negative array index [" + temp + "] too large for array size " + size);
    1362         }
    1363         return i;
    1364     }
    1365
    1366     /***
    1367      * Support the subscript operator for List
    1368      *
    1369      * @param coll     a Collection
    1370      * @param property a String
    1371      * @return a List
    1372      */
    1373     public static List getAt(Collection coll, String property) {
    1374         List answer = new ArrayList(coll.size());
    1375         for (Iterator iter = coll.iterator(); iter.hasNext();) {
    1376             Object item = iter.next();
    1377             Object value = InvokerHelper.getProperty(item, property);
    1378             if (value instanceof Collection) {
    1379                 answer.addAll((Collection) value);
    1380             } else {
    1381                 answer.add(value);
    1382             }
    1383         }
    1384         return answer;
    1385     }
    1386
    1387     /***
    1388      * A convenience method for creating an immutable map
    1389      *
    1390      * @param self a Map
    1391      * @return an immutable Map
    1392      */
    1393     public static Map asImmutable(Map self) {
    1394         return Collections.unmodifiableMap(self);
    1395     }
    1396
    1397     /***
    1398      * A convenience method for creating an immutable sorted map
    1399      *
    1400      * @param self a SortedMap
    1401      * @return an immutable SortedMap
    1402      */
    1403     public static SortedMap asImmutable(SortedMap self) {
    1404         return Collections.unmodifiableSortedMap(self);
    1405     }
    1406
    1407     /***
    1408      * A convenience method for creating an immutable list
    1409      *
    1410      * @param self a List
    1411      * @return an immutable List
    1412      */
    1413     public static List asImmutable(List self) {
    1414         return Collections.unmodifiableList(self);
    1415     }
    1416
    1417     /***
    1418      * A convenience method for creating an immutable list
    1419      *
    1420      * @param self a Set
    1421      * @return an immutable Set
    1422      */
    1423     public static Set asImmutable(Set self) {
    1424         return Collections.unmodifiableSet(self);
    1425     }
    1426
    1427     /***
    1428      * A convenience method for creating an immutable sorted set
    1429      *
    1430      * @param self a SortedSet
    1431      * @return an immutable SortedSet
    1432      */
    1433     public static SortedSet asImmutable(SortedSet self) {
    1434         return Collections.unmodifiableSortedSet(self);
    1435     }
    1436
    1437     /***
    1438      * A convenience method for creating an immutable Collection
    1439      *
    1440      * @param self a Collection
    1441      * @return an immutable Collection
    1442      */
    1443     public static Collection asImmutable(Collection self) {
    1444         return Collections.unmodifiableCollection(self);
    1445     }
    1446
    1447     /***
    1448      * A convenience method for creating a synchronized Map
    1449      *
    1450      * @param self a Map
    1451      * @return a synchronized Map
    1452      */
    1453     public static Map asSynchronized(Map self) {
    1454         return Collections.synchronizedMap(self);
    1455     }
    1456
    1457     /***
    1458      * A convenience method for creating a synchronized SortedMap
    1459      *
    1460      * @param self a SortedMap
    1461      * @return a synchronized SortedMap
    1462      */
    1463     public static SortedMap asSynchronized(SortedMap self) {
    1464         return Collections.synchronizedSortedMap(self);
    1465     }
    1466
    1467     /***
    1468      * A convenience method for creating a synchronized Collection
    1469      *
    1470      * @param self a Collection
    1471      * @return a synchronized Collection
    1472      */
    1473     public static Collection asSynchronized(Collection self) {
    1474         return Collections.synchronizedCollection(self);
    1475     }
    1476
    1477     /***
    1478      * A convenience method for creating a synchronized List
    1479      *
    1480      * @param self a List
    1481      * @return a synchronized List
    1482      */
    1483     public static List asSynchronized(List self) {
    1484         return Collections.synchronizedList(self);
    1485     }
    1486
    1487     /***
    1488      * A convenience method for creating a synchronized Set
    1489      *
    1490      * @param self a Set
    1491      * @return a synchronized Set
    1492      */
    1493     public static Set asSynchronized(Set self) {
    1494         return Collections.synchronizedSet(self);
    1495     }
    1496
    1497     /***
    1498      * A convenience method for creating a synchronized SortedSet
    1499      *
    1500      * @param self a SortedSet
    1501      * @return a synchronized SortedSet
    1502      */
    1503     public static SortedSet asSynchronized(SortedSet self) {
    1504         return Collections.synchronizedSortedSet(self);
    1505     }
    1506
    1507     /***
    1508      * Sorts the given collection into a sorted list
    1509      *
    1510      * @param self the collection to be sorted
    1511      * @return the sorted collection as a List
    1512      */
    1513     public static List sort(Collection self) {
    1514         List answer = asList(self);
    1515         Collections.sort(answer);
    1516         return answer;
    1517     }
    1518
    1519     /***
    1520      * Avoids doing unnecessary work when sorting an already sorted set
    1521      *
    1522      * @param self
    1523      * @return the sorted set
    1524      */
    1525     public static SortedSet sort(SortedSet self) {
    1526         return self;
    1527     }
    1528
    1529     /***
    1530      * A convenience method for sorting a List
    1531      *
    1532      * @param self a List to be sorted
    1533      * @return the sorted List
    1534      */
    1535     public static List sort(List self) {
    1536         Collections.sort(self);
    1537         return self;
    1538     }
    1539
    1540     /***
    1541      * Removes the last item from the List. Using add() and pop()
    1542      * is similar to push and pop on a Stack.
    1543      *
    1544      * @param self a List
    1545      * @return the item removed from the List
    1546      * @throws UnsupportedOperationException if the list is empty and you try to pop() it.
    1547      */
    1548     public static Object pop(List self) {
    1549         if (self.isEmpty()) {
    1550             throw new UnsupportedOperationException("Cannot pop() an empty List");
    1551         }
    1552         return self.remove(self.size() - 1);
    1553     }
    1554
    1555     /***
    1556      * A convenience method for sorting a List with a specific comparator
    1557      *
    1558      * @param self       a List
    1559      * @param comparator a Comparator used for the comparison
    1560      * @return a sorted List
    1561      */
    1562     public static List sort(List self, Comparator comparator) {
    1563         Collections.sort(self, comparator);
    1564         return self;
    1565     }
    1566
    1567     /***
    1568      * A convenience method for sorting a Collection with a specific comparator
    1569      *
    1570      * @param self       a collection to be sorted
    1571      * @param comparator a Comparator used for the comparison
    1572      * @return a newly created sorted List
    1573      */
    1574     public static List sort(Collection self, Comparator comparator) {
    1575         return sort(asList(self), comparator);
    1576     }
    1577
    1578     /***
    1579      * A convenience method for sorting a List using a closure as a comparator
    1580      *
    1581      * @param self    a List
    1582      * @param closure a Closure used as a comparator
    1583      * @return a sorted List
    1584      */
    1585     public static List sort(List self, Closure closure) {
    1586         // use a comparator of one item or two
    1587         Class[] params = closure.getParameterTypes();
    1588         if (params.length == 1) {
    1589             Collections.sort(self, new OrderBy(closure));
    1590         } else {
    1591             Collections.sort(self, new ClosureComparator(closure));
    1592         }
    1593         return self;
    1594     }
    1595
    1596     /***
    1597      * A convenience method for sorting a Collection using a closure as a comparator
    1598      *
    1599      * @param self    a Collection to be sorted
    1600      * @param closure a Closure used as a comparator
    1601      * @return a newly created sorted List
    1602      */
    1603     public static List sort(Collection self, Closure closure) {
    1604         return sort(asList(self), closure);
    1605     }
    1606
    1607     /***
    1608      * Converts the given collection into a List
    1609      *
    1610      * @param self a collection to be converted into a List
    1611      * @return a newly created List if this collection is not already a List
    1612      */
    1613     public static List asList(Collection self) {
    1614         if (self instanceof List) {
    1615             return (List) self;
    1616         } else {
    1617             return new ArrayList(self);
    1618         }
    1619     }
    1620
    1621     /***
    1622      * Reverses the list
    1623      *
    1624      * @param self a List
    1625      * @return a reversed List
    1626      */
    1627     public static List reverse(List self) {
    1628         int size = self.size();
    1629         List answer = new ArrayList(size);
    1630         ListIterator iter = self.listIterator(size);
    1631         while (iter.hasPrevious()) {
    1632             answer.add(iter.previous());
    1633         }
    1634         return answer;
    1635     }
    1636
    1637     /***
    1638      * Create a List as a union of both Collections
    1639      *
    1640      * @param left  the left Collection
    1641      * @param right the right Collection
    1642      * @return a List
    1643      */
    1644     public static List plus(Collection left, Collection right) {
    1645         List answer = new ArrayList(left.size() + right.size());
    1646         answer.addAll(left);
    1647         answer.addAll(right);
    1648         return answer;
    1649     }
    1650
    1651     /***
    1652      * Create a List as a union of a Collection and an Object
    1653      *
    1654      * @param left  a Collection
    1655      * @param right an object to append
    1656      * @return a List
    1657      */
    1658     public static List plus(Collection left, Object right) {
    1659         List answer = new ArrayList(left.size() + 1);
    1660         answer.addAll(left);
    1661         answer.add(right);
    1662         return answer;
    1663     }
    1664
    1665     /***
    1666      * Create a List composed of the same elements repeated a certain number of times.
    1667      *
    1668      * @param self   a Collection
    1669      * @param factor the number of times to append
    1670      * @return a List
    1671      */
    1672     public static List multiply(Collection self, Number factor) {
    1673         int size = factor.intValue();
    1674         List answer = new ArrayList(self.size() * size);
    1675         for (int i = 0; i < size; i++) {
    1676             answer.addAll(self);
    1677         }
    1678         return answer;
    1679     }
    1680
    1681     /***
    1682      * Create a List composed of the intersection of both collections
    1683      *
    1684      * @param left  a List
    1685      * @param right a Collection
    1686      * @return a List as an intersection of both collections
    1687      */
    1688     public static List intersect(List left, Collection right) {
    1689
    1690         if (left.size() == 0)
    1691             return new ArrayList();
    1692
    1693         boolean nlgnSort = sameType(new Collection[]{left, right});
    1694
    1695         ArrayList result = new ArrayList();
    1696         //creates the collection to look for values.
    1697         Collection pickFrom = nlgnSort ? (Collection) new TreeSet(left) : left;
    1698
    1699         for (Iterator iter = right.iterator(); iter.hasNext();) {
    1700             final Object o = iter.next();
    1701             if (pickFrom.contains(o))
    1702                 result.add(o);
    1703         }
    1704         return result;
    1705     }
    1706
    1707     /***
    1708      * Create a List composed of the elements of the first list minus the elements of the collection
    1709      *
    1710      * @param self     a List
    1711      * @param removeMe a Collection of elements to remove
    1712      * @return a List with the common elements removed
    1713      */
    1714     public static List minus(List self, Collection removeMe) {
    1715
    1716         if (self.size() == 0)
    1717             return new ArrayList();
    1718
    1719         boolean nlgnSort = sameType(new Collection[]{self, removeMe});
    1720
    1721         //we can't use the same tactic as for intersection
    1722         //since AbstractCollection only does a remove on the first
    1723         //element it encounter.
    1724
    1725         if (nlgnSort) {
    1726             //n*log(n) version
    1727             Set answer = new TreeSet(self);
    1728             answer.removeAll(removeMe);
    1729             return new ArrayList(answer);
    1730         } else {
    1731             //n*n version
    1732             List tmpAnswer = new LinkedList(self);
    1733             for (Iterator iter = tmpAnswer.iterator(); iter.hasNext();) {
    1734                 Object element = iter.next();
    1735                 //boolean removeElement = false;
    1736                 for (Iterator iterator = removeMe.iterator(); iterator.hasNext();) {
    1737                     if (element.equals(iterator.next())) {
    1738                         iter.remove();
    1739                     }
    1740                 }
    1741             }
    1742             //remove duplicates
    1743             //can't use treeset since the base classes are different
    1744             List answer = new LinkedList();
    1745             Object[] array = tmpAnswer.toArray(new Object[tmpAnswer.size()]);
    1746
    1747             for (int i = 0; i < array.length; i++) {
    1748                 if (array[i] != null) {
    1749                     for (int j = i + 1; j < array.length; j++) {
    1750                         if (array[i].equals(array[j])) {
    1751                             array[j] = null;
    1752                         }
    1753                     }
    1754                     answer.add(array[i]);
    1755                 }
    1756             }
    1757             return new ArrayList(answer);
    1758         }
    1759     }
    1760
    1761     /***
    1762      * Flatten a list
    1763      *
    1764      * @param self a List
    1765      * @return a flattened List
    1766      */
    1767     public static List flatten(List self) {
    1768         return new ArrayList(flatten(self, new LinkedList()));
    1769     }
    1770
    1771     /***
    1772      * Iterate over each element of the list in the reverse order.
    1773      *
    1774      * @param self    a List
    1775      * @param closure a closure
    1776      */
    1777     public static void reverseEach(List self, Closure closure) {
    1778         List reversed = reverse(self);
    1779         for (Iterator iter = reversed.iterator(); iter.hasNext();) {
    1780             closure.call(iter.next());
    1781         }
    1782     }
    1783
    1784     private static List flatten(Collection elements, List addTo) {
    1785         Iterator iter = elements.iterator();
    1786         while (iter.hasNext()) {
    1787             Object element = iter.next();
    1788             if (element instanceof Collection) {
    1789                 flatten((Collection) element, addTo);
    1790             } else if (element instanceof Map) {
    1791                 flatten(((Map) element).values(), addTo);
    1792             } else {
    1793                 addTo.add(element);
    1794             }
    1795         }
    1796         return addTo;
    1797     }
    1798
    1799     /***
    1800      * Overloads the left shift operator to provide an easy way to append objects to a list
    1801      *
    1802      * @param self  a Collection
    1803      * @param value an Object to be added to the collection.
    1804      * @return a Collection with an Object added to it.
    1805      */
    1806     public static Collection leftShift(Collection self, Object value) {
    1807         self.add(value);
    1808         return self;
    1809     }
    1810
    1811     /***
    1812      * Overloads the left shift operator to provide an easy way to append multiple
    1813      * objects as string representations to a String
    1814      *
    1815      * @param self  a String
    1816      * @param value an Obect
    1817      * @return a StringWriter
    1818      */
    1819     public static StringWriter leftShift(String self, Object value) {
    1820         StringWriter answer = createStringWriter(self);
    1821         try {
    1822             leftShift(answer, value);
    1823         } catch (IOException e) {
    1824             throw new StringWriterIOException(e);
    1825         }
    1826         return answer;
    1827     }
    1828
    1829     protected static StringWriter createStringWriter(String self) {
    1830         StringWriter answer = new StringWriter();
    1831         answer.write(self);
    1832         return answer;
    1833     }
    1834
    1835     protected static StringBufferWriter createStringBufferWriter(StringBuffer self) {
    1836         return new StringBufferWriter(self);
    1837     }
    1838
    1839     /***
    1840      * Overloads the left shift operator to provide an easy way to append multiple
    1841      * objects as string representations to a StringBuffer
    1842      *
    1843      * @param self  a StringBuffer
    1844      * @param value a value to append
    1845      * @return a StringWriter
    1846      */
    1847     public static Writer leftShift(StringBuffer self, Object value) {
    1848         StringBufferWriter answer = createStringBufferWriter(self);
    1849         try {
    1850             leftShift(answer, value);
    1851         } catch (IOException e) {
    1852             throw new StringWriterIOException(e);
    1853         }
    1854         return answer;
    1855     }
    1856
    1857     /***
    1858      * Overloads the left shift operator to provide an append mechanism to add things to a writer
    1859      *
    1860      * @param self  a Writer
    1861      * @param value a value to append
    1862      * @return a StringWriter
    1863      */
    1864     public static Writer leftShift(Writer self, Object value) throws IOException {
    1865         InvokerHelper.write(self, value);
    1866         return self;
    1867     }
    1868
    1869     /***
    1870      * Implementation of the left shift operator for integral types.  Non integral
    1871      * Number types throw UnsupportedOperationException.
    1872      */
    1873     public static Number leftShift(Number left, Number right) {
    1874         return NumberMath.leftShift(left, right);
    1875     }
    1876
    1877     /***
    1878      * Implementation of the right shift operator for integral types.  Non integral
    1879      * Number types throw UnsupportedOperationException.
    1880      */
    1881     public static Number rightShift(Number left, Number right) {
    1882         return NumberMath.rightShift(left, right);
    1883     }
    1884
    1885     /***
    1886      * Implementation of the right shift (unsigned) operator for integral types.  Non integral
    1887      * Number types throw UnsupportedOperationException.
    1888      */
    1889     public static Number rightShiftUnsigned(Number left, Number right) {
    1890         return NumberMath.rightShiftUnsigned(left, right);
    1891     }
    1892
    1893     /***
    1894      * A helper method so that dynamic dispatch of the writer.write(object) method
    1895      * will always use the more efficient Writable.writeTo(writer) mechanism if the
    1896      * object implements the Writable interface.
    1897      *
    1898      * @param self     a Writer
    1899      * @param writable an object implementing the Writable interface
    1900      */
    1901     public static void write(Writer self, Writable writable) throws IOException {
    1902         writable.writeTo(self);
    1903     }
    1904
    1905     /***
    1906      * Overloads the left shift operator to provide an append mechanism to add things to a stream
    1907      *
    1908      * @param self  an OutputStream
    1909      * @param value a value to append
    1910      * @return a Writer
    1911      */
    1912     public static Writer leftShift(OutputStream self, Object value) throws IOException {
    1913         OutputStreamWriter writer = new FlushingStreamWriter(self);
    1914         leftShift(writer, value);
    1915         return writer;
    1916     }
    1917
    1918     /***
    1919      * Overloads the left shift operator to provide an append mechanism to add bytes to a stream
    1920      *
    1921      * @param self  an OutputStream
    1922      * @param value a value to append
    1923      * @return an OutputStream
    1924      */
    1925     public static OutputStream leftShift(OutputStream self, byte[] value) throws IOException {
    1926         self.write(value);
    1927         self.flush();
    1928         return self;
    1929     }
    1930
    1931     private static boolean sameType(Collection[] cols) {
    1932         List all = new LinkedList();
    1933         for (int i = 0; i < cols.length; i++) {
    1934             all.addAll(cols[i]);
    1935         }
    1936         if (all.size() == 0)
    1937             return true;
    1938
    1939         Object first = all.get(0);
    1940
    1941         //trying to determine the base class of the collections
    1942         //special case for Numbers
    1943         Class baseClass;
    1944         if (first instanceof Number) {
    1945             baseClass = Number.class;
    1946         } else {
    1947             baseClass = first.getClass();
    1948         }
    1949
    1950         for (int i = 0; i < cols.length; i++) {
    1951             for (Iterator iter = cols[i].iterator(); iter.hasNext();) {
    1952                 if (!baseClass.isInstance(iter.next())) {
    1953                     return false;
    1954                 }
    1955             }
    1956         }
    1957         return true;
    1958     }
    1959
    1960     // Primitive type array methods
    1961     //-------------------------------------------------------------------------
    1962
    1963     public static Object getAt(byte[] array, int idx) {
    1964         return primitiveArrayGet(array, idx);
    1965     }
    1966
    1967     public static Object getAt(char[] array, int idx) {
    1968         return primitiveArrayGet(array, idx);
    1969     }
    1970
    1971     public static Object getAt(short[] array, int idx) {
    1972         return primitiveArrayGet(array, idx);
    1973     }
    1974
    1975     public static Object getAt(int[] array, int idx) {
    1976         return primitiveArrayGet(array, idx);
    1977     }
    1978
    1979     public static Object getAt(long[] array, int idx) {
    1980         return primitiveArrayGet(array, idx);
    1981     }
    1982
    1983     public static Object getAt(float[] array, int idx) {
    1984         return primitiveArrayGet(array, idx);
    1985     }
    1986
    1987     public static Object getAt(double[] array, int idx) {
    1988         return primitiveArrayGet(array, idx);
    1989     }
    1990
    1991     public static Object getAt(byte[] array, Range range) {
    1992         return primitiveArrayGet(array, range);
    1993     }
    1994
    1995     public static Object getAt(char[] array, Range range) {
    1996         return primitiveArrayGet(array, range);
    1997     }
    1998
    1999     public static Object getAt(short[] array, Range range) {
    2000         return primitiveArrayGet(array, range);
    2001     }
    2002
    2003     public static Object getAt(int[] array, Range range) {
    2004         return primitiveArrayGet(array, range);
    2005     }
    2006
    2007     public static Object getAt(long[] array, Range range) {
    2008         return primitiveArrayGet(array, range);
    2009     }
    2010
    2011     public static Object getAt(float[] array, Range range) {
    2012         return primitiveArrayGet(array, range);
    2013     }
    2014
    2015     public static Object getAt(double[] array, Range range) {
    2016         return primitiveArrayGet(array, range);
    2017     }
    2018
    2019     public static Object getAt(byte[] array, Collection indices) {
    2020         return primitiveArrayGet(array, indices);
    2021     }
    2022
    2023     public static Object getAt(char[] array, Collection indices) {
    2024         return primitiveArrayGet(array, indices);
    2025     }
    2026
    2027     public static Object getAt(short[] array, Collection indices) {
    2028         return primitiveArrayGet(array, indices);
    2029     }
    2030
    2031     public static Object getAt(int[] array, Collection indices) {
    2032         return primitiveArrayGet(array, indices);
    2033     }
    2034
    2035     public static Object getAt(long[] array, Collection indices) {
    2036         return primitiveArrayGet(array, indices);
    2037     }
    2038
    2039     public static Object getAt(float[] array, Collection indices) {
    2040         return primitiveArrayGet(array, indices);
    2041     }
    2042
    2043     public static Object getAt(double[] array, Collection indices) {
    2044         return primitiveArrayGet(array, indices);
    2045     }
    2046
    2047     public static void putAt(byte[] array, int idx, Object newValue) {
    2048         primitiveArrayPut(array, idx, newValue);
    2049     }
    2050
    2051     public static void putAt(char[] array, int idx, Object newValue) {
    2052         primitiveArrayPut(array, idx, newValue);
    2053     }
    2054
    2055     public static void putAt(short[] array, int idx, Object newValue) {
    2056         primitiveArrayPut(array, idx, newValue);
    2057     }
    2058
    2059     public static void putAt(int[] array, int idx, Object newValue) {
    2060         primitiveArrayPut(array, idx, newValue);
    2061     }
    2062
    2063     public static void putAt(long[] array, int idx, Object newValue) {
    2064         primitiveArrayPut(array, idx, newValue);
    2065     }
    2066
    2067     public static void putAt(float[] array, int idx, Object newValue) {
    2068         primitiveArrayPut(array, idx, newValue);
    2069     }
    2070
    2071     public static void putAt(double[] array, int idx, Object newValue) {
    2072         primitiveArrayPut(array, idx, newValue);
    2073     }
    2074
    2075     public static int size(byte[] array) {
    2076         return Array.getLength(array);
    2077     }
    2078
    2079     public static int size(char[] array) {
    2080         return Array.getLength(array);
    2081     }
    2082
    2083     public static int size(short[] array) {
    2084         return Array.getLength(array);
    2085     }
    2086
    2087     public static int size(int[] array) {
    2088         return Array.getLength(array);
    2089     }
    2090
    2091     public static int size(long[] array) {
    2092         return Array.getLength(array);
    2093     }
    2094
    2095     public static int size(float[] array) {
    2096         return Array.getLength(array);
    2097     }
    2098
    2099     public static int size(double[] array) {
    2100         return Array.getLength(array);
    2101     }
    2102
    2103     public static List toList(byte[] array) {
    2104         return InvokerHelper.primitiveArrayToList(array);
    2105     }
    2106
    2107     public static List toList(char[] array) {
    2108         return InvokerHelper.primitiveArrayToList(array);
    2109     }
    2110
    2111     public static List toList(short[] array) {
    2112         return InvokerHelper.primitiveArrayToList(array);
    2113     }
    2114
    2115     public static List toList(int[] array) {
    2116         return InvokerHelper.primitiveArrayToList(array);
    2117     }
    2118
    2119     public static List toList(long[] array) {
    2120         return InvokerHelper.primitiveArrayToList(array);
    2121     }
    2122
    2123     public static List toList(float[] array) {
    2124         return InvokerHelper.primitiveArrayToList(array);
    2125     }
    2126
    2127     public static List toList(double[] array) {
    2128         return InvokerHelper.primitiveArrayToList(array);
    2129     }
    2130
    2131     private static final char[] tTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray();
    2132
    2133     public static Writable encodeBase64(final Byte[] data) {
    2134         return encodeBase64(InvokerHelper.convertToByteArray(data));
    2135     }
    2136
    2137     /***
    2138      * Produce a Writable object which writes the base64 encoding of the byte array
    2139      * Calling toString() on the result rerurns the encoding as a String
    2140      *
    2141      * @param data byte array to be encoded
    2142      * @return object which will write the base64 encoding of the byte array
    2143      */
    2144     public static Writable encodeBase64(final byte[] data) {
    2145         return new Writable() {
    2146             public Writer writeTo(final Writer writer) throws IOException {
    2147                 int charCount = 0;
    2148                 final int dLimit = (data.length / 3) * 3;
    2149
    2150                 for (int dIndex = 0; dIndex != dLimit; dIndex += 3) {
    2151                     int d = ((data[dIndex] & 0XFF) << 16) | ((data[dIndex + 1] & 0XFF) << 8) | (data[dIndex + 2] & 0XFF);
    2152
    2153                     writer.write(tTable[d >> 18]);
    2154                     writer.write(tTable[(d >> 12) & 0X3F]);
    2155                     writer.write(tTable[(d >> 6) & 0X3F]);
    2156                     writer.write(tTable[d & 0X3F]);
    2157
    2158                     if (++charCount == 18) {
    2159                         writer.write('\n');
    2160                         charCount = 0;
    2161                     }
    2162                 }
    2163
    2164                 if (dLimit != data.length) {
    2165                     int d = (data[dLimit] & 0XFF) << 16;
    2166
    2167                     if (dLimit + 1 != data.length) {
    2168                         d |= (data[dLimit + 1] & 0XFF) << 8;
    2169                     }
    2170
    2171                     writer.write(tTable[d >> 18]);
    2172                     writer.write(tTable[(d >> 12) & 0X3F]);
    2173                     writer.write((dLimit + 1 < data.length) ? tTable[(d >> 6) & 0X3F] : '=');
    2174                     writer.write('=');
    2175                 }
    2176
    2177                 return writer;
    2178             }
    2179
    2180             public String toString() {
    2181                 StringWriter buffer = new StringWriter();
    2182
    2183                 try {
    2184                     writeTo(buffer);
    2185                 } catch (IOException e) {
    2186                     throw new RuntimeException(e); // TODO: change this exception type
    2187                 }
    2188
    2189                 return buffer.toString();
    2190             }
    2191         };
    2192     }
    2193
    2194     private static final byte[] translateTable = (
    2195             //
    2196             "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042"
    2197             //                    \t    \n                \r
    2198             + "\u0042\u0042\u0041\u0041\u0042\u0042\u0041\u0042"
    2199             //
    2200             + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042"
    2201             //
    2202             + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042"
    2203             //        sp    !     "     #     $     %     &     '
    2204             + "\u0041\u0042\u0042\u0042\u0042\u0042\u0042\u0042"
    2205             //         (    )     *     +     ,     -     .     /
    2206             + "\u0042\u0042\u0042\u003E\u0042\u0042\u0042\u003F"
    2207             //         0    1     2     3     4     5     6     7
    2208             + "\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B"
    2209             //         8    9     :     ;     <     =     >     ?
    2210             + "\u003C\u003D\u0042\u0042\u0042\u0040\u0042\u0042"
    2211             //         @    A     B     C     D     E     F     G
    2212             + "\u0042\u0000\u0001\u0002\u0003\u0004\u0005\u0006"
    2213             //         H    I   J K   L     M   N   O
    2214             + "\u0007\u0008\t\n\u000B\u000C\r\u000E"
    2215             //         P    Q     R     S     T     U     V    W
    2216             + "\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016"
    2217             //         X    Y     Z     [     \     ]     ^    _
    2218             + "\u0017\u0018\u0019\u0042\u0042\u0042\u0042\u0042"
    2219             //         '    a     b     c     d     e     f     g
    2220             + "\u0042\u001A\u001B\u001C\u001D\u001E\u001F\u0020"
    2221             //        h   i   j     k     l     m     n     o    p
    2222             + "\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028"
    2223             //        p     q     r     s     t     u     v     w
    2224             + "\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030"
    2225             //        x     y     z
    2226             + "\u0031\u0032\u0033").getBytes();
    2227
    2228     /***
    2229      * Decode the Sting from base64 into a byte array
    2230      *
    2231      * @param value the string to be decoded
    2232      * @return the decoded bytes as an array
    2233      */
    2234     public static byte[] decodeBase64(final String value) {
    2235         int byteShift = 4;
    2236         int tmp = 0;
    2237         boolean done = false;
    2238         final StringBuffer buffer = new StringBuffer();
    2239
    2240         for (int i = 0; i != value.length(); i++) {
    2241             final char c = value.charAt(i);
    2242             final int sixBit = (c < 123) ? translateTable[c] : 66;
    2243
    2244             if (sixBit < 64) {
    2245                 if (done) throw new RuntimeException("= character not at end of base64 value"); // TODO: change this exception type
    2246
    2247                 tmp = (tmp << 6) | sixBit;
    2248
    2249                 if (byteShift-- != 4) {
    2250                     buffer.append((char) ((tmp >> (byteShift * 2)) & 0XFF));
    2251                 }
    2252
    2253             } else if (sixBit == 64) {
    2254
    2255                 byteShift--;
    2256                 done = true;
    2257
    2258             } else if (sixBit == 66) {
    2259                 // RFC 2045 says that I'm allowed to take the presence of
    2260                 // these characters as evedence of data corruption
    2261                 // So I will
    2262                 throw new RuntimeException("bad character in base64 value"); // TODO: change this exception type
    2263             }
    2264
    2265             if (byteShift == 0) byteShift = 4;
    2266         }
    2267
    2268         try {
    2269             return buffer.toString().getBytes("ISO-8859-1");
    2270         } catch (UnsupportedEncodingException e) {
    2271             throw new RuntimeException("Base 64 decode produced byte values > 255"); // TODO: change this exception type
    2272         }
    2273     }
    2274
    2275     /***
    2276      * Implements the getAt(int) method for primitve type arrays
    2277      */
    2278     protected static Object primitiveArrayGet(Object array, int idx) {
    2279         return Array.get(array, normaliseIndex(idx, Array.getLength(array)));
    2280     }
    2281
    2282     /***
    2283      * Implements the getAt(Range) method for primitve type arrays
    2284      */
    2285     protected static List primitiveArrayGet(Object array, Range range) {
    2286         List answer = new ArrayList();
    2287         for (Iterator iter = range.iterator(); iter.hasNext();) {
    2288             int idx = InvokerHelper.asInt(iter.next());
    2289             answer.add(primitiveArrayGet(array, idx));
    2290         }
    2291         return answer;
    2292     }
    2293
    2294     /***
    2295      * Implements the getAt(Collection) method for primitve type arrays
    2296      */
    2297     protected static List primitiveArrayGet(Object self, Collection indices) {
    2298         List answer = new ArrayList();
    2299         for (Iterator iter = indices.iterator(); iter.hasNext();) {
    2300             Object value = iter.next();
    2301             if (value instanceof Range) {
    2302                 answer.addAll(primitiveArrayGet(self, (Range) value));
    2303             } else if (value instanceof List) {
    2304                 answer.addAll(primitiveArrayGet(self, (List) value));
    2305             } else {
    2306                 int idx = InvokerHelper.asInt(value);
    2307                 answer.add(primitiveArrayGet(self, idx));
    2308             }
    2309         }
    2310         return answer;
    2311     }
    2312
    2313     /***
    2314      * Implements the set(int idx) method for primitve type arrays
    2315      */
    2316     protected static void primitiveArrayPut(Object array, int idx, Object newValue) {
    2317         Array.set(array, normaliseIndex(idx, Array.getLength(array)), newValue);
    2318     }
    2319
    2320     // String methods
    2321     //-------------------------------------------------------------------------
    2322
    2323     /***
    2324      * Converts the given string into a Character object
    2325      * using the first character in the string
    2326      *
    2327      * @param self a String
    2328      * @return the first Character
    2329      */
    2330     public static Character toCharacter(String self) {
    2331         /*** @todo use cache? */
    2332         return new Character(self.charAt(0));
    2333     }
    2334
    2335     /***
    2336      * Tokenize a String
    2337      *
    2338      * @param self  a String
    2339      * @param token the delimiter
    2340      * @return a List of tokens
    2341      */
    2342     public static List tokenize(String self, String token) {
    2343         return InvokerHelper.asList(new StringTokenizer(self, token));
    2344     }
    2345
    2346     /***
    2347      * Tokenize a String (with a whitespace as delimiter)
    2348      *
    2349      * @param self a String
    2350      * @return a List of tokens
    2351      */
    2352     public static List tokenize(String self) {
    2353         return InvokerHelper.asList(new StringTokenizer(self));
    2354     }
    2355
    2356     /***
    2357      * Appends a String
    2358      *
    2359      * @param left  a String
    2360      * @param value a String
    2361      * @return a String
    2362      */
    2363     public static String plus(String left, Object value) {
    2364         //return left + value;
    2365         return left + toString(value);
    2366     }
    2367
    2368     /***
    2369      * Appends a String
    2370      *
    2371      * @param value a Number
    2372      * @param right a String
    2373      * @return a String
    2374      */
    2375     public static String plus(Number value, String right) {
    2376         return toString(value) + right;
    2377     }
    2378
    2379     /***
    2380      * Appends a String
    2381      *
    2382      * @param left  a StringBuffer
    2383      * @param value a String
    2384      * @return a String
    2385      */
    2386     public static String plus(StringBuffer left, String value) {
    2387         return left + value;
    2388     }
    2389
    2390
    2391     /***
    2392      * Remove a part of a String
    2393      *
    2394      * @param left  a String
    2395      * @param value a String part to remove
    2396      * @return a String minus the part to be removed
    2397      */
    2398     public static String minus(String left, Object value) {
    2399         String text = toString(value);
    2400         return left.replaceFirst(text, "");
    2401     }
    2402
    2403     /***
    2404      * Provide an implementation of contains() like Collection to make Strings more polymorphic
    2405      * This method is not required on JDK 1.5 onwards
    2406      *
    2407      * @param self a String
    2408      * @param text a String to look for
    2409      * @return true if this string contains the given text
    2410      */
    2411     public static boolean contains(String self, String text) {
    2412         int idx = self.indexOf(text);
    2413         return idx >= 0;
    2414     }
    2415
    2416     /***
    2417      * Count the number of occurencies of a substring
    2418      *
    2419      * @param self a String
    2420      * @param text a substring
    2421      * @return the number of occurrencies of the given string inside this String
    2422      */
    2423     public static int count(String self, String text) {
    2424         int answer = 0;
    2425         for (int idx = 0; true; idx++) {
    2426             idx = self.indexOf(text, idx);
    2427             if (idx >= 0) {
    2428                 ++answer;
    2429             } else {
    2430                 break;
    2431             }
    2432         }
    2433         return answer;
    2434     }
    2435
    2436     /***
    2437      * Increments the last digit in the given string, resetting
    2438      * it and moving onto the next digit if increasing the digit
    2439      * no longer becomes a letter or digit.
    2440      *
    2441      * @param self a String
    2442      * @return a String with an incremented digit at the end
    2443      */
    2444     public static String next(String self) {
    2445         StringBuffer buffer = new StringBuffer(self);
    2446         char firstCh = firstCharacter();
    2447         for (int idx = buffer.length() - 1; idx >= 0; idx--) {
    2448             char ch = next(buffer.charAt(idx));
    2449             if (ch != ZERO_CHAR) {
    2450                 buffer.setCharAt(idx, ch);
    2451                 break;
    2452             } else {
    2453                 // lets find the first char
    2454                 if (idx == 0) {
    2455                     buffer.append("1");
    2456                 } else {
    2457                     buffer.setCharAt(idx, firstCh);
    2458                 }
    2459             }
    2460         }
    2461         return buffer.toString();
    2462     }
    2463
    2464     /***
    2465      * Decrements the last digit in the given string, resetting
    2466      * it and moving onto the next digit if increasing the digit
    2467      * no longer becomes a letter or digit.
    2468      *
    2469      * @param self a String
    2470      * @return a String with a decremented digit at the end
    2471      */
    2472     public static String previous(String self) {
    2473         StringBuffer buffer = new StringBuffer(self);
    2474         char lastCh = lastCharacter();
    2475         for (int idx = buffer.length() - 1; idx >= 0; idx--) {
    2476             char ch = previous(buffer.charAt(idx));
    2477             if (ch != ZERO_CHAR) {
    2478                 buffer.setCharAt(idx, ch);
    2479                 break;
    2480             } else {
    2481                 if (idx == 0) {
    2482                     return null;
    2483                 } else {
    2484                     // lets find the first char
    2485                     buffer.setCharAt(idx, lastCh);
    2486                 }
    2487             }
    2488         }
    2489         return buffer.toString();
    2490     }
    2491
    2492     /***
    2493      * Executes the given string as a command line process. For more control
    2494      * over the process mechanism in JDK 1.5 you can use java.lang.ProcessBuilder.
    2495      *
    2496      * @param self a command line String
    2497      * @return the Process which has just started for this command line string
    2498      */
    2499     public static Process execute(String self) throws IOException {
    2500         return Runtime.getRuntime().exec(self);
    2501     }
    2502
    2503     private static char next(char ch) {
    2504         if (Character.isLetterOrDigit(++ch)) {
    2505             return ch;
    2506         } else {
    2507             return ZERO_CHAR;
    2508         }
    2509     }
    2510
    2511     private static char previous(char ch) {
    2512         if (Character.isLetterOrDigit(--ch)) {
    2513             return ch;
    2514         } else {
    2515             return ZERO_CHAR;
    2516         }
    2517     }
    2518
    2519     /***
    2520      * @return the first character used when a letter rolls over when incrementing
    2521      */
    2522     private static char firstCharacter() {
    2523         char ch = ZERO_CHAR;
    2524         while (!Character.isLetterOrDigit(ch)) {
    2525             ch++;
    2526         }
    2527         return ch;
    2528     }
    2529
    2530     /***
    2531      * @return the last character used when a letter rolls over when decrementing
    2532      */
    2533     private static char lastCharacter() {
    2534         char ch = firstCharacter();
    2535         while (Character.isLetterOrDigit(++ch)) ;
    2536         return --ch;
    2537     }
    2538
    2539     /***
    2540      * Repeat a String a certain number of times
    2541      *
    2542      * @param self   a String to be repeated
    2543      * @param factor the number of times the String should be repeated
    2544      * @return a String composed of a repeatition
    2545      * @throws IllegalArgumentException if the number of repeatition is &lt; 0
    2546      */
    2547     public static String multiply(String self, Number factor) {
    2548         int size = factor.intValue();
    2549         if (size == 0)
    2550             return "";
    2551         else if (size < 0) {
    2552             throw new IllegalArgumentException("multiply() should be called with a number of 0 or greater not: " + size);
    2553         }
    2554         StringBuffer answer = new StringBuffer(self);
    2555         for (int i = 1; i < size; i++) {
    2556             answer.append(self);
    2557         }
    2558         return answer.toString();
    2559     }
    2560
    2561     protected static String toString(Object value) {
    2562         return (value == null) ? "null" : value.toString();
    2563     }
    2564
    2565     // Number based methods
    2566     //-------------------------------------------------------------------------
    2567
    2568     /***
    2569      * Increment a Character by one
    2570      *
    2571      * @param self a Character
    2572      * @return an incremented Number
    2573      */
    2574     public static Number next(Character self) {
    2575         return plus(self, ONE);
    2576     }
    2577
    2578     /***
    2579      * Increment a Number by one
    2580      *
    2581      * @param self a Number
    2582      * @return an incremented Number
    2583      */
    2584     public static Number next(Number self) {
    2585         return plus(self, ONE);
    2586     }
    2587
    2588     /***
    2589      * Decrement a Character by one
    2590      *
    2591      * @param self a Character
    2592      * @return a decremented Number
    2593      */
    2594     public static Number previous(Character self) {
    2595         return minus(self, ONE);
    2596     }
    2597
    2598     /***
    2599      * Decrement a Number by one
    2600      *
    2601      * @param self a Number
    2602      * @return a decremented Number
    2603      */
    2604     public static Number previous(Number self) {
    2605         return minus(self, ONE);
    2606     }
    2607
    2608     /***
    2609      * Add a Character and a Number
    2610      *
    2611      * @param left  a Character
    2612      * @param right a Number
    2613      * @return the addition of the Character and the Number
    2614      */
    2615     public static Number plus(Character left, Number right) {
    2616         return plus(new Integer(left.charValue()), right);
    2617     }
    2618
    2619     /***
    2620      * Add a Number and a Character
    2621      *
    2622      * @param left  a Number
    2623      * @param right a Character
    2624      * @return the addition of the Character and the Number
    2625      */
    2626     public static Number plus(Number left, Character right) {
    2627         return plus(left, new Integer(right.charValue()));
    2628     }
    2629
    2630     /***
    2631      * Add two Characters
    2632      *
    2633      * @param left  a Character
    2634      * @param right a Character
    2635      * @return the addition of both Characters
    2636      */
    2637     public static Number plus(Character left, Character right) {
    2638         return plus(new Integer(left.charValue()), right);
    2639     }
    2640
    2641     /***
    2642      * Add two Numbers
    2643      *
    2644      * @param left  a Number
    2645      * @param right another Number to add
    2646      * @return the addition of both Numbers
    2647      */
    2648     public static Number plus(Number left, Number right) {
    2649         return NumberMath.add(left, right);
    2650     }
    2651
    2652     /***
    2653      * Compare a Character and a Number
    2654      *
    2655      * @param left  a Character
    2656      * @param right a Number
    2657      * @return the result of the comparison
    2658      */
    2659     public static int compareTo(Character left, Number right) {
    2660         return compareTo(new Integer(left.charValue()), right);
    2661     }
    2662
    2663     /***
    2664      * Compare a Number and a Character
    2665      *
    2666      * @param left  a Number
    2667      * @param right a Character
    2668      * @return the result of the comparison
    2669      */
    2670     public static int compareTo(Number left, Character right) {
    2671         return compareTo(left, new Integer(right.charValue()));
    2672     }
    2673
    2674     /***
    2675      * Compare two Characters
    2676      *
    2677      * @param left  a Character
    2678      * @param right a Character
    2679      * @return the result of the comparison
    2680      */
    2681     public static int compareTo(Character left, Character right) {
    2682         return compareTo(new Integer(left.charValue()), right);
    2683     }
    2684
    2685     /***
    2686      * Compare two Numbers
    2687      *
    2688      * @param left  a Number
    2689      * @param right another Number to compare to
    2690      * @return the comparision of both numbers
    2691      */
    2692     public static int compareTo(Number left, Number right) {
    2693         /*** @todo maybe a double dispatch thing to handle new large numbers? */
    2694         return NumberMath.compareTo(left, right);
    2695     }
    2696
    2697     /***
    2698      * Subtract a Number from a Character
    2699      *
    2700      * @param left  a Character
    2701      * @param right a Number
    2702      * @return the addition of the Character and the Number
    2703      */
    2704     public static Number minus(Character left, Number right) {
    2705         return minus(new Integer(left.charValue()), right);
    2706     }
    2707
    2708     /***
    2709      * Subtract a Character from a Number
    2710      *
    2711      * @param left  a Number
    2712      * @param right a Character
    2713      * @return the addition of the Character and the Number
    2714      */
    2715     public static Number minus(Number left, Character right) {
    2716         return minus(left, new Integer(right.charValue()));
    2717     }
    2718
    2719     /***
    2720      * Subtraction two Characters
    2721      *
    2722      * @param left  a Character
    2723      * @param right a Character
    2724      * @return the addition of both Characters
    2725      */
    2726     public static Number minus(Character left, Character right) {
    2727         return minus(new Integer(left.charValue()), right);
    2728     }
    2729
    2730     /***
    2731      * Substraction of two Numbers
    2732      *
    2733      * @param left  a Number
    2734      * @param right another Number to substract to the first one
    2735      * @return the substraction
    2736      */
    2737     public static Number minus(Number left, Number right) {
    2738         return NumberMath.subtract(left, right);
    2739     }
    2740
    2741     /***
    2742      * Multiply a Character by a Number
    2743      *
    2744      * @param left  a Character
    2745      * @param right a Number
    2746      * @return the multiplication of both
    2747      */
    2748     public static Number multiply(Character left, Number right) {
    2749         return multiply(new Integer(left.charValue()), right);
    2750     }
    2751
    2752     /***
    2753      * Multiply a Number by a Character
    2754      *
    2755      * @param left  a Number
    2756      * @param right a Character
    2757      * @return the multiplication of both
    2758      */
    2759     public static Number multiply(Number left, Character right) {
    2760         return multiply(left, new Integer(right.charValue()));
    2761     }
    2762
    2763     /***
    2764      * Multiply two Characters
    2765      *
    2766      * @param left  a Character
    2767      * @param right another Character
    2768      * @return the multiplication of both
    2769      */
    2770     public static Number multiply(Character left, Character right) {
    2771         return multiply(new Integer(left.charValue()), right);
    2772     }
    2773
    2774     /***
    2775      * Multiply two Numbers
    2776      *
    2777      * @param left  a Number
    2778      * @param right another Number
    2779      * @return the multiplication of both
    2780      */
    2781     //Note:  This method is NOT called if left AND right are both BigIntegers or BigDecimals because
    2782     //those classes implement a method with a better exact match.
    2783     public static Number multiply(Number left, Number right) {
    2784         return NumberMath.multiply(left, right);
    2785     }
    2786
    2787     /***
    2788      * Power of a Number to a certain exponent
    2789      *
    2790      * @param self     a Number
    2791      * @param exponent a Number exponent
    2792      * @return a Number to the power of a certain exponent
    2793      */
    2794     public static Number power(Number self, Number exponent) {
    2795         double answer = Math.pow(self.doubleValue(), exponent.doubleValue());
    2796         if (NumberMath.isFloatingPoint(self) || NumberMath.isFloatingPoint(exponent) || answer < 1) {
    2797             return new Double(answer);
    2798         } else if (NumberMath.isLong(self) || NumberMath.isLong(exponent) || answer > Integer.MAX_VALUE) {
    2799             return new Long((long) answer);
    2800         } else {
    2801             return new Integer((int) answer);
    2802         }
    2803     }
    2804
    2805     /***
    2806      * Divide a Character by a Number
    2807      *
    2808      * @param left  a Character
    2809      * @param right a Number
    2810      * @return the multiplication of both
    2811      */
    2812     public static Number div(Character left, Number right) {
    2813         return div(new Integer(left.charValue()), right);
    2814     }
    2815
    2816     /***
    2817      * Divide a Number by a Character
    2818      *
    2819      * @param left  a Number
    2820      * @param right a Character
    2821      * @return the multiplication of both
    2822      */
    2823     public static Number div(Number left, Character right) {
    2824         return div(left, new Integer(right.charValue()));
    2825     }
    2826
    2827     /***
    2828      * Divide two Characters
    2829      *
    2830      * @param left  a Character
    2831      * @param right another Character
    2832      * @return the multiplication of both
    2833      */
    2834     public static Number div(Character left, Character right) {
    2835         return div(new Integer(left.charValue()), right);
    2836     }
    2837
    2838     /***
    2839      * Divide two Numbers
    2840      *
    2841      * @param left  a Number
    2842      * @param right another Number
    2843      * @return a Number resulting of the divide operation
    2844      */
    2845     //Method name changed from 'divide' to avoid collision with BigInteger method that has
    2846     //different semantics.  We want a BigDecimal result rather than a BigInteger.
    2847     public static Number div(Number left, Number right) {
    2848         return NumberMath.divide(left, right);
    2849     }
    2850
    2851     /***
    2852      * Integer Divide a Character by a Number
    2853      *
    2854      * @param left  a Character
    2855      * @param right a Number
    2856      * @return the integer division of both
    2857      */
    2858     public static Number intdiv(Character left, Number right) {
    2859         return intdiv(new Integer(left.charValue()), right);
    2860     }
    2861
    2862     /***
    2863      * Integer Divide a Number by a Character
    2864      *
    2865      * @param left  a Number
    2866      * @param right a Character
    2867      * @return the integer division of both
    2868      */
    2869     public static Number intdiv(Number left, Character right) {
    2870         return intdiv(left, new Integer(right.charValue()));
    2871     }
    2872
    2873     /***
    2874      * Integer Divide two Characters
    2875      *
    2876      * @param left  a Character
    2877      * @param right another Character
    2878      * @return the integer division of both
    2879      */
    2880     public static Number intdiv(Character left, Character right) {
    2881         return intdiv(new Integer(left.charValue()), right);
    2882     }
    2883
    2884     /***
    2885      * Integer Divide two Numbers
    2886      *
    2887      * @param left  a Number
    2888      * @param right another Number
    2889      * @return a Number (an Integer) resulting of the integer division operation
    2890      */
    2891     public static Number intdiv(Number left, Number right) {
    2892         return NumberMath.intdiv(left, right);
    2893     }
    2894
    2895     /***
    2896      * Bitwise OR together two numbers
    2897      *
    2898      * @param left  a Number
    2899      * @param right another Number to bitwise OR
    2900      * @return the bitwise OR of both Numbers
    2901      */
    2902     public static Number or(Number left, Number right) {
    2903         return NumberMath.or(left, right);
    2904     }
    2905
    2906     /***
    2907      * Bitwise AND together two Numbers
    2908      *
    2909      * @param left  a Number
    2910      * @param right another Number to bitwse AND
    2911      * @return the bitwise AND of both Numbers
    2912      */
    2913     public static Number and(Number left, Number right) {
    2914         return NumberMath.and(left, right);
    2915     }
    2916
    2917     /***
    2918      * Performs a division modulus operation
    2919      *
    2920      * @param left  a Number
    2921      * @param right another Number to mod
    2922      * @return the modulus result
    2923      */
    2924     public static Number mod(Number left, Number right) {
    2925         return NumberMath.mod(left, right);
    2926     }
    2927
    2928     /***
    2929      * Negates the number
    2930      *
    2931      * @param left a Number
    2932      * @return the negation of the number
    2933      */
    2934     public static Number negate(Number left) {
    2935         return NumberMath.negate(left);
    2936     }
    2937
    2938
    2939     /***
    2940      * Iterates a number of times
    2941      *
    2942      * @param self    a Number
    2943      * @param closure the closure to call a number of times
    2944      */
    2945     public static void times(Number self, Closure closure) {
    2946         for (int i = 0, size = self.intValue(); i < size; i++) {
    2947             closure.call(new Integer(i));
    2948             if (closure.getDirective() == Closure.DONE) {
    2949                 break;
    2950             }
    2951         }
    2952     }
    2953
    2954     /***
    2955      * Iterates from this number up to the given number
    2956      *
    2957      * @param self    a Number
    2958      * @param to      another Number to go up to
    2959      * @param closure the closure to call
    2960      */
    2961     public static void upto(Number self, Number to, Closure closure) {
    2962         for (int i = self.intValue(), size = to.intValue(); i <= size; i++) {
    2963             closure.call(new Integer(i));
    2964         }
    2965     }
    2966
    2967     /***
    2968      * Iterates from this number up to the given number using a step increment
    2969      *
    2970      * @param self       a Number to start with
    2971      * @param to         a Number to go up to
    2972      * @param stepNumber a Number representing the step increment
    2973      * @param closure    the closure to call
    2974      */
    2975     public static void step(Number self, Number to, Number stepNumber, Closure closure) {
    2976         for (int i = self.intValue(), size = to.intValue(), step = stepNumber.intValue(); i < size; i += step) {
    2977             closure.call(new Integer(i));
    2978         }
    2979     }
    2980
    2981     /***
    2982      * Get the absolute value
    2983      *
    2984      * @param number a Number
    2985      * @return the absolute value of that Number
    2986      */
    2987     //Note:  This method is NOT called if number is a BigInteger or BigDecimal because
    2988     //those classes implement a method with a better exact match.
    2989     public static int abs(Number number) {
    2990         return Math.abs(number.intValue());
    2991     }
    2992
    2993     /***
    2994      * Get the absolute value
    2995      *
    2996      * @param number a Long
    2997      * @return the absolute value of that Long
    2998      */
    2999     public static long abs(Long number) {
    3000         return Math.abs(number.longValue());
    3001     }
    3002
    3003     /***
    3004      * Get the absolute value
    3005      *
    3006      * @param number a Float
    3007      * @return the absolute value of that Float
    3008      */
    3009     public static float abs(Float number) {
    3010         return Math.abs(number.floatValue());
    3011     }
    3012
    3013     /***
    3014      * Get the absolute value
    3015      *
    3016      * @param number a Double
    3017      * @return the absolute value of that Double
    3018      */
    3019     public static double abs(Double number) {
    3020         return Math.abs(number.doubleValue());
    3021     }
    3022
    3023     /***
    3024      * Get the absolute value
    3025      *
    3026      * @param number a Float
    3027      * @return the absolute value of that Float
    3028      */
    3029     public static int round(Float number) {
    3030         return Math.round(number.floatValue());
    3031     }
    3032
    3033     /***
    3034      * Round the value
    3035      *
    3036      * @param number a Double
    3037      * @return the absolute value of that Double
    3038      */
    3039     public static long round(Double number) {
    3040         return Math.round(number.doubleValue());
    3041     }
    3042
    3043     /***
    3044      * Parse a String into an Integer
    3045      *
    3046      * @param self a String
    3047      * @return an Integer
    3048      */
    3049     public static Integer toInteger(String self) {
    3050         return Integer.valueOf(self);
    3051     }
    3052
    3053     /***
    3054      * Parse a String into a Long
    3055      *
    3056      * @param self a String
    3057      * @return a Long
    3058      */
    3059     public static Long toLong(String self) {
    3060         return Long.valueOf(self);
    3061     }
    3062
    3063     /***
    3064      * Parse a String into a Float
    3065      *
    3066      * @param self a String
    3067      * @return a Float
    3068      */
    3069     public static Float toFloat(String self) {
    3070         return Float.valueOf(self);
    3071     }
    3072
    3073     /***
    3074      * Parse a String into a Double
    3075      *
    3076      * @param self a String
    3077      * @return a Double
    3078      */
    3079     public static Double toDouble(String self) {
    3080         return Double.valueOf(self);
    3081     }
    3082
    3083     /***
    3084      * Transform a Number into an Integer
    3085      *
    3086      * @param self a Number
    3087      * @return an Integer
    3088      */
    3089     public static Integer toInteger(Number self) {
    3090         return new Integer(self.intValue());
    3091     }
    3092
    3093     // Date methods
    3094     //-------------------------------------------------------------------------
    3095
    3096     /***
    3097      * Increments a Date by a day
    3098      *
    3099      * @param self a Date
    3100      * @return the next days date
    3101      */
    3102     public static Date next(Date self) {
    3103         return plus(self, 1);
    3104     }
    3105
    3106     /***
    3107      * Decrement a Date by a day
    3108      *
    3109      * @param self a Date
    3110      * @return the previous days date
    3111      */
    3112     public static Date previous(Date self) {
    3113         return minus(self, 1);
    3114     }
    3115
    3116     /***
    3117      * Adds a number of days to this date and returns the new date
    3118      *
    3119      * @param self a Date
    3120      * @param days the number of days to increase
    3121      * @return the new date
    3122      */
    3123     public static Date plus(Date self, int days) {
    3124         Calendar calendar = (Calendar) Calendar.getInstance().clone();
    3125         calendar.setTime(self);
    3126         calendar.add(Calendar.DAY_OF_YEAR, days);
    3127         return calendar.getTime();
    3128     }
    3129
    3130     /***
    3131      * Subtracts a number of days from this date and returns the new date
    3132      *
    3133      * @param self a Date
    3134      * @return the new date
    3135      */
    3136     public static Date minus(Date self, int days) {
    3137         return plus(self, -days);
    3138     }
    3139
    3140     // File and stream based methods
    3141     //-------------------------------------------------------------------------
    3142
    3143     /***
    3144      * Iterates through the given file line by line
    3145      *
    3146      * @param self    a File
    3147      * @param closure a closure
    3148      * @throws IOException
    3149      */
    3150     public static void eachLine(File self, Closure closure) throws IOException {
    3151         eachLine(newReader(self), closure);
    3152     }
    3153
    3154     /***
    3155      * Iterates through the given reader line by line
    3156      *
    3157      * @param self    a Reader
    3158      * @param closure a closure
    3159      * @throws IOException
    3160      */
    3161     public static void eachLine(Reader self, Closure closure) throws IOException {
    3162         BufferedReader br = null;
    3163
    3164         if (self instanceof BufferedReader)
    3165             br = (BufferedReader) self;
    3166         else
    3167             br = new BufferedReader(self);
    3168
    3169         try {
    3170             while (true) {
    3171                 String line = br.readLine();
    3172                 if (line == null) {
    3173                     break;
    3174                 } else {
    3175                     closure.call(line);
    3176                 }
    3177             }
    3178             br.close();
    3179         } catch (IOException e) {
    3180             if (self != null) {
    3181                 try {
    3182                     br.close();
    3183                 } catch (Exception e2) {
    3184                     // ignore as we're already throwing
    3185                 }
    3186                 throw e;
    3187             }
    3188         }
    3189     }
    3190
    3191     /***
    3192      * Iterates through the given file line by line, splitting on the seperator
    3193      *
    3194      * @param self    a File
    3195      * @param sep     a String separator
    3196      * @param closure a closure
    3197      * @throws IOException
    3198      */
    3199     public static void splitEachLine(File self, String sep, Closure closure) throws IOException {
    3200         splitEachLine(newReader(self), sep, closure);
    3201     }
    3202
    3203     /***
    3204      * Iterates through the given reader line by line, splitting on the seperator
    3205      *
    3206      * @param self    a Reader
    3207      * @param sep     a String separator
    3208      * @param closure a closure
    3209      * @throws IOException
    3210      */
    3211     public static void splitEachLine(Reader self, String sep, Closure closure) throws IOException {
    3212         BufferedReader br = null;
    3213
    3214         if (self instanceof BufferedReader)
    3215             br = (BufferedReader) self;
    3216         else
    3217             br = new BufferedReader(self);
    3218
    3219         List args = new ArrayList();
    3220
    3221         try {
    3222             while (true) {
    3223                 String line = br.readLine();
    3224                 if (line == null) {
    3225                     break;
    3226                 } else {
    3227                     List vals = Arrays.asList(line.split(sep));
    3228                     args.clear();
    3229                     args.add(vals);
    3230                     closure.call(args);
    3231                 }
    3232             }
    3233             br.close();
    3234         } catch (IOException e) {
    3235             if (self != null) {
    3236                 try {
    3237                     br.close();
    3238                 } catch (Exception e2) {
    3239                     // ignore as we're already throwing
    3240                 }
    3241                 throw e;
    3242             }
    3243         }
    3244     }
    3245
    3246     /***
    3247      * Read a single, whole line from the given Reader
    3248      *
    3249      * @param self a Reader
    3250      * @return a line
    3251      * @throws IOException
    3252      */
    3253     public static String readLine(Reader self) throws IOException {
    3254         BufferedReader br = null;
    3255
    3256         if (self instanceof BufferedReader) {
    3257             br = (BufferedReader) self;
    3258         } else {
    3259             br = new BufferedReader(self);
    3260         }
    3261         return br.readLine();
    3262     }
    3263
    3264     /***
    3265      * Read a single, whole line from the given InputStream
    3266      *
    3267      * @param stream an InputStream
    3268      * @return a line
    3269      * @throws IOException
    3270      */
    3271     public static String readLine(InputStream stream) throws IOException {
    3272         return readLine(new InputStreamReader(stream));
    3273     }
    3274
    3275     /***
    3276      * Reads the file into a list of Strings for each line
    3277      *
    3278      * @param file a File
    3279      * @return a List of lines
    3280      * @throws IOException
    3281      */
    3282     public static List readLines(File file) throws IOException {
    3283         IteratorClosureAdapter closure = new IteratorClosureAdapter(file);
    3284         eachLine(file, closure);
    3285         return closure.asList();
    3286     }
    3287
    3288     /***
    3289      * Reads the content of the File opened with the specified encoding and returns it as a String
    3290      *
    3291      * @param file    the file whose content we want to read
    3292      * @param charset the charset used to read the content of the file
    3293      * @return a String containing the content of the file
    3294      * @throws IOException
    3295      */
    3296     public static String getText(File file, String charset) throws IOException {
    3297         BufferedReader reader = newReader(file, charset);
    3298         return getText(reader);
    3299     }
    3300
    3301     /***
    3302      * Reads the content of the File and returns it as a String
    3303      *
    3304      * @param file the file whose content we want to read
    3305      * @return a String containing the content of the file
    3306      * @throws IOException
    3307      */
    3308     public static String getText(File file) throws IOException {
    3309         BufferedReader reader = newReader(file);
    3310         return getText(reader);
    3311     }
    3312
    3313     /***
    3314      * Reads the content of this URL and returns it as a String
    3315      *
    3316      * @param url URL to read content from
    3317      * @return the text from that URL
    3318      * @throws IOException
    3319      */
    3320     public static String getText(URL url) throws IOException {
    3321         return getText(url, CharsetToolkit.getDefaultSystemCharset().toString());
    3322     }
    3323
    3324     /***
    3325      * Reads the content of this URL and returns it as a String
    3326      *
    3327      * @param url     URL to read content from
    3328      * @param charset opens the stream with a specified charset
    3329      * @return the text from that URL
    3330      * @throws IOException
    3331      */
    3332     public static String getText(URL url, String charset) throws IOException {
    3333         BufferedReader reader = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream(), charset));
    3334         return getText(reader);
    3335     }
    3336
    3337     /***
    3338      * Reads the content of this InputStream and returns it as a String
    3339      *
    3340      * @param is an input stream
    3341      * @return the text from that URL
    3342      * @throws IOException
    3343      */
    3344     public static String getText(InputStream is) throws IOException {
    3345         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    3346         return getText(reader);
    3347     }
    3348
    3349     /***
    3350      * Reads the content of this InputStream with a specified charset and returns it as a String
    3351      *
    3352      * @param is      an input stream
    3353      * @param charset opens the stream with a specified charset
    3354      * @return the text from that URL
    3355      * @throws IOException
    3356      */
    3357     public static String getText(InputStream is, String charset) throws IOException {
    3358         BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
    3359         return getText(reader);
    3360     }
    3361
    3362     /***
    3363      * Reads the content of the Reader and returns it as a String
    3364      *
    3365      * @param reader a Reader whose content we want to read
    3366      * @return a String containing the content of the buffered reader
    3367      * @throws IOException
    3368      */
    3369     public static String getText(Reader reader) throws IOException {
    3370         BufferedReader bufferedReader = new BufferedReader(reader);
    3371         return getText(bufferedReader);
    3372     }
    3373
    3374     /***
    3375      * Reads the content of the BufferedReader and returns it as a String
    3376      *
    3377      * @param reader a BufferedReader whose content we want to read
    3378      * @return a String containing the content of the buffered reader
    3379      * @throws IOException
    3380      */
    3381     public static String getText(BufferedReader reader) throws IOException {
    3382         StringBuffer answer = new StringBuffer();
    3383         // reading the content of the file within a char buffer allow to keep the correct line endings
    3384         char[] charBuffer = new char[4096];
    3385         int nbCharRead = 0;
    3386         while ((nbCharRead = reader.read(charBuffer)) != -1) {
    3387             // appends buffer
    3388             answer.append(charBuffer, 0, nbCharRead);
    3389         }
    3390         reader.close();
    3391         return answer.toString();
    3392     }
    3393
    3394     /***
    3395      * Write the text and append a new line (depending on the platform line-ending)
    3396      *
    3397      * @param writer a BufferedWriter
    3398      * @param line   the line to write
    3399      * @throws IOException
    3400      */
    3401     public static void writeLine(BufferedWriter writer, String line) throws IOException {
    3402         writer.write(line);
    3403         writer.newLine();
    3404     }
    3405
    3406     /***
    3407      * Write the text to the File.
    3408      *
    3409      * @param file a File
    3410      * @param text the text to write to the File
    3411      * @throws IOException
    3412      */
    3413     public static void write(File file, String text) throws IOException {
    3414         BufferedWriter writer = newWriter(file);
    3415         writer.write(text);
    3416         writer.close();
    3417     }
    3418
    3419     /***
    3420      * Write the text to the File with a specified encoding.
    3421      *
    3422      * @param file    a File
    3423      * @param text    the text to write to the File
    3424      * @param charset the charset used
    3425      * @throws IOException
    3426      */
    3427     public static void write(File file, String text, String charset) throws IOException {
    3428         BufferedWriter writer = newWriter(file, charset);
    3429         writer.write(text);
    3430         writer.close();
    3431     }
    3432
    3433     /***
    3434      * Append the text at the end of the File
    3435      *
    3436      * @param file a File
    3437      * @param text the text to append at the end of the File
    3438      * @throws IOException
    3439      */
    3440     public static void append(File file, String text) throws IOException {
    3441         BufferedWriter writer = newWriter(file, true);
    3442         writer.write(text);
    3443         writer.close();
    3444     }
    3445
    3446     /***
    3447      * Append the text at the end of the File with a specified encoding
    3448      *
    3449      * @param file    a File
    3450      * @param text    the text to append at the end of the File
    3451      * @param charset the charset used
    3452      * @throws IOException
    3453      */
    3454     public static void append(File file, String text, String charset) throws IOException {
    3455         BufferedWriter writer = newWriter(file, charset, true);
    3456         writer.write(text);
    3457         writer.close();
    3458     }
    3459
    3460     /***
    3461      * Reads the reader into a list of Strings for each line
    3462      *
    3463      * @param reader a Reader
    3464      * @return a List of lines
    3465      * @throws IOException
    3466      */
    3467     public static List readLines(Reader reader) throws IOException {
    3468         IteratorClosureAdapter closure = new IteratorClosureAdapter(reader);
    3469         eachLine(reader, closure);
    3470         return closure.asList();
    3471     }
    3472
    3473     /***
    3474      * Invokes the closure for each file in the given directory
    3475      *
    3476      * @param self    a File
    3477      * @param closure a closure
    3478      */
    3479     public static void eachFile(File self, Closure closure) {
    3480         File[] files = self.listFiles();
    3481         for (int i = 0; i < files.length; i++) {
    3482             closure.call(files[i]);
    3483         }
    3484     }
    3485
    3486     /***
    3487      * Invokes the closure for each file in the given directory and recursively.
    3488      * It is a depth-first exploration, directories are included in the search.
    3489      *
    3490      * @param self    a File
    3491      * @param closure a closure
    3492      */
    3493     public static void eachFileRecurse(File self, Closure closure) {
    3494         File[] files = self.listFiles();
    3495         for (int i = 0; i < files.length; i++) {
    3496             if (files[i].isDirectory()) {
    3497                 closure.call(files[i]);
    3498                 eachFileRecurse(files[i], closure);
    3499             } else {
    3500                 closure.call(files[i]);
    3501             }
    3502         }
    3503     }
    3504
    3505     /***
    3506      * Helper method to create a buffered reader for a file
    3507      *
    3508      * @param file a File
    3509      * @return a BufferedReader
    3510      * @throws IOException
    3511      */
    3512     public static BufferedReader newReader(File file) throws IOException {
    3513         CharsetToolkit toolkit = new CharsetToolkit(file);
    3514         return toolkit.getReader();
    3515     }
    3516
    3517     /***
    3518      * Helper method to create a buffered reader for a file, with a specified charset
    3519      *
    3520      * @param file    a File
    3521      * @param charset the charset with which we want to write in the File
    3522      * @return a BufferedReader
    3523      * @throws FileNotFoundException        if the File was not found
    3524      * @throws UnsupportedEncodingException if the encoding specified is not supported
    3525      */
    3526     public static BufferedReader newReader(File file, String charset)
    3527             throws FileNotFoundException, UnsupportedEncodingException {
    3528         return new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
    3529     }
    3530
    3531     /***
    3532      * Provides a reader for an arbitrary input stream
    3533      *
    3534      * @param self an input stream
    3535      * @return a reader
    3536      */
    3537     public static BufferedReader newReader(final InputStream self) {
    3538         return new BufferedReader(new InputStreamReader(self));
    3539     }
    3540
    3541     /***
    3542      * Helper method to create a new BufferedReader for a file and then
    3543      * passes it into the closure and ensures its closed again afterwords
    3544      *
    3545      * @param file
    3546      * @throws FileNotFoundException
    3547      */
    3548     public static void withReader(File file, Closure closure) throws IOException {
    3549         withReader(newReader(file), closure);
    3550     }
    3551
    3552     /***
    3553      * Helper method to create a buffered output stream for a file
    3554      *
    3555      * @param file
    3556      * @return
    3557      * @throws FileNotFoundException
    3558      */
    3559     public static BufferedOutputStream newOutputStream(File file) throws IOException {
    3560         return new BufferedOutputStream(new FileOutputStream(file));
    3561     }
    3562
    3563     /***
    3564      * Helper method to create a new OutputStream for a file and then
    3565      * passes it into the closure and ensures its closed again afterwords
    3566      *
    3567      * @param file a File
    3568      * @throws FileNotFoundException
    3569      */
    3570     public static void withOutputStream(File file, Closure closure) throws IOException {
    3571         withStream(newOutputStream(file), closure);
    3572     }
    3573
    3574     /***
    3575      * Helper method to create a new InputStream for a file and then
    3576      * passes it into the closure and ensures its closed again afterwords
    3577      *
    3578      * @param file a File
    3579      * @throws FileNotFoundException
    3580      */
    3581     public static void withInputStream(File file, Closure closure) throws IOException {
    3582         withStream(newInputStream(file), closure);
    3583     }
    3584
    3585     /***
    3586      * Helper method to create a buffered writer for a file
    3587      *
    3588      * @param file a File
    3589      * @return a BufferedWriter
    3590      * @throws FileNotFoundException
    3591      */
    3592     public static BufferedWriter newWriter(File file) throws IOException {
    3593         return new BufferedWriter(new FileWriter(file));
    3594     }
    3595
    3596     /***
    3597      * Helper method to create a buffered writer for a file in append mode
    3598      *
    3599      * @param file   a File
    3600      * @param append true if in append mode
    3601      * @return a BufferedWriter
    3602      * @throws FileNotFoundException
    3603      */
    3604     public static BufferedWriter newWriter(File file, boolean append) throws IOException {
    3605         return new BufferedWriter(new FileWriter(file, append));
    3606     }
    3607
    3608     /***
    3609      * Helper method to create a buffered writer for a file
    3610      *
    3611      * @param file    a File
    3612      * @param charset the name of the encoding used to write in this file
    3613      * @param append  true if in append mode
    3614      * @return a BufferedWriter
    3615      * @throws FileNotFoundException
    3616      */
    3617     public static BufferedWriter newWriter(File file, String charset, boolean append) throws IOException {
    3618         if (append) {
    3619             return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append), charset));
    3620         } else {
    3621             // first write the Byte Order Mark for Unicode encodings
    3622             FileOutputStream stream = new FileOutputStream(file);
    3623             if ("UTF-16BE".equals(charset)) {
    3624                 writeUtf16Bom(stream, true);
    3625             } else if ("UTF-16LE".equals(charset)) {
    3626                 writeUtf16Bom(stream, false);
    3627             }
    3628             return new BufferedWriter(new OutputStreamWriter(stream, charset));
    3629         }
    3630     }
    3631
    3632     /***
    3633      * Helper method to create a buffered writer for a file
    3634      *
    3635      * @param file    a File
    3636      * @param charset the name of the encoding used to write in this file
    3637      * @return a BufferedWriter
    3638      * @throws FileNotFoundException
    3639      */
    3640     public static BufferedWriter newWriter(File file, String charset) throws IOException {
    3641         return newWriter(file, charset, false);
    3642     }
    3643
    3644     /***
    3645      * Write a Byte Order Mark at the begining of the file
    3646      *
    3647      * @param stream    the FileOuputStream to write the BOM to
    3648      * @param bigEndian true if UTF 16 Big Endian or false if Low Endian
    3649      * @throws IOException
    3650      */
    3651     private static void writeUtf16Bom(FileOutputStream stream, boolean bigEndian) throws IOException {
    3652         if (bigEndian) {
    3653             stream.write(-2);
    3654             stream.write(-1);
    3655         } else {
    3656             stream.write(-1);
    3657             stream.write(-2);
    3658         }
    3659     }
    3660
    3661     /***
    3662      * Helper method to create a new BufferedWriter for a file and then
    3663      * passes it into the closure and ensures it is closed again afterwords
    3664      *
    3665      * @param file    a File
    3666      * @param closure a closure
    3667      * @throws FileNotFoundException
    3668      */
    3669     public static void withWriter(File file, Closure closure) throws IOException {
    3670         withWriter(newWriter(file), closure);
    3671     }
    3672
    3673     /***
    3674      * Helper method to create a new BufferedWriter for a file in a specified encoding
    3675      * and then passes it into the closure and ensures it is closed again afterwords
    3676      *
    3677      * @param file    a File
    3678      * @param charset the charset used
    3679      * @param closure a closure
    3680      * @throws FileNotFoundException
    3681      */
    3682     public static void withWriter(File file, String charset, Closure closure) throws IOException {
    3683         withWriter(newWriter(file, charset), closure);
    3684     }
    3685
    3686     /***
    3687      * Helper method to create a new BufferedWriter for a file in a specified encoding
    3688      * in append mode and then passes it into the closure and ensures it is closed again afterwords
    3689      *
    3690      * @param file    a File
    3691      * @param charset the charset used
    3692      * @param closure a closure
    3693      * @throws FileNotFoundException
    3694      */
    3695     public static void withWriterAppend(File file, String charset, Closure closure) throws IOException {
    3696         withWriter(newWriter(file, charset, true), closure);
    3697     }
    3698
    3699     /***
    3700      * Helper method to create a new PrintWriter for a file
    3701      *
    3702      * @param file a File
    3703      * @throws FileNotFoundException
    3704      */
    3705     public static PrintWriter newPrintWriter(File file) throws IOException {
    3706         return new PrintWriter(newWriter(file));
    3707     }
    3708
    3709     /***
    3710      * Helper method to create a new PrintWriter for a file with a specified charset
    3711      *
    3712      * @param file    a File
    3713      * @param charset the charset
    3714      * @return a PrintWriter
    3715      * @throws FileNotFoundException
    3716      */
    3717     public static PrintWriter newPrintWriter(File file, String charset) throws IOException {
    3718         return new PrintWriter(newWriter(file, charset));
    3719     }
    3720
    3721     /***
    3722      * Helper method to create a new PrintWriter for a file and then
    3723      * passes it into the closure and ensures its closed again afterwords
    3724      *
    3725      * @param file a File
    3726      * @throws FileNotFoundException
    3727      */
    3728     public static void withPrintWriter(File file, Closure closure) throws IOException {
    3729         withWriter(newPrintWriter(file), closure);
    3730     }
    3731
    3732     /***
    3733      * Allows a writer to be used, calling the closure with the writer
    3734      * and then ensuring that the writer is closed down again irrespective
    3735      * of whether exceptions occur or the
    3736      *
    3737      * @param writer  the writer which is used and then closed
    3738      * @param closure the closure that the writer is passed into
    3739      * @throws IOException
    3740      */
    3741     public static void withWriter(Writer writer, Closure closure) throws IOException {
    3742         try {
    3743             closure.call(writer);
    3744
    3745             // lets try close the writer & throw the exception if it fails
    3746             // but not try to reclose it in the finally block
    3747             Writer temp = writer;
    3748             writer = null;
    3749             temp.close();
    3750         } finally {
    3751             if (writer != null) {
    3752                 try {
    3753                     writer.close();
    3754                 } catch (IOException e) {
    3755                     log.warning("Caught exception closing writer: " + e);
    3756                 }
    3757             }
    3758         }
    3759     }
    3760
    3761     /***
    3762      * Allows a Reader to be used, calling the closure with the writer
    3763      * and then ensuring that the writer is closed down again irrespective
    3764      * of whether exceptions occur or the
    3765      *
    3766      * @param writer  the writer which is used and then closed
    3767      * @param closure the closure that the writer is passed into
    3768      * @throws IOException
    3769      */
    3770     public static void withReader(Reader writer, Closure closure) throws IOException {
    3771         try {
    3772             closure.call(writer);
    3773
    3774             // lets try close the writer & throw the exception if it fails
    3775             // but not try to reclose it in the finally block
    3776             Reader temp = writer;
    3777             writer = null;
    3778             temp.close();
    3779         } finally {
    3780             if (writer != null) {
    3781                 try {
    3782                     writer.close();
    3783                 } catch (IOException e) {
    3784                     log.warning("Caught exception closing writer: " + e);
    3785                 }
    3786             }
    3787         }
    3788     }
    3789
    3790     /***
    3791      * Allows a InputStream to be used, calling the closure with the stream
    3792      * and then ensuring that the stream is closed down again irrespective
    3793      * of whether exceptions occur or the
    3794      *
    3795      * @param stream  the stream which is used and then closed
    3796      * @param closure the closure that the stream is passed into
    3797      * @throws IOException
    3798      */
    3799     public static void withStream(InputStream stream, Closure closure) throws IOException {
    3800         try {
    3801             closure.call(stream);
    3802
    3803             // lets try close the stream & throw the exception if it fails
    3804             // but not try to reclose it in the finally block
    3805             InputStream temp = stream;
    3806             stream = null;
    3807             temp.close();
    3808         } finally {
    3809             if (stream != null) {
    3810                 try {
    3811                     stream.close();
    3812                 } catch (IOException e) {
    3813                     log.warning("Caught exception closing stream: " + e);
    3814                 }
    3815             }
    3816         }
    3817     }
    3818
    3819     /***
    3820      * Reads the stream into a list of Strings for each line
    3821      *
    3822      * @param stream a stream
    3823      * @return a List of lines
    3824      * @throws IOException
    3825      */
    3826     public static List readLines(InputStream stream) throws IOException {
    3827         return readLines(new BufferedReader(new InputStreamReader(stream)));
    3828     }
    3829
    3830     /***
    3831      * Iterates through the given stream line by line
    3832      *
    3833      * @param stream  a stream
    3834      * @param closure a closure
    3835      * @throws IOException
    3836      */
    3837     public static void eachLine(InputStream stream, Closure closure) throws IOException {
    3838         eachLine(new InputStreamReader(stream), closure);
    3839     }
    3840
    3841     /***
    3842      * Iterates through the lines read from the URL's associated input stream
    3843      *
    3844      * @param url     a URL to open and read
    3845      * @param closure a closure to apply on each line
    3846      * @throws IOException
    3847      */
    3848     public static void eachLine(URL url, Closure closure) throws IOException {
    3849         eachLine(url.openConnection().getInputStream(), closure);
    3850     }
    3851
    3852     /***
    3853      * Helper method to create a new BufferedReader for a URL and then
    3854      * passes it into the closure and ensures its closed again afterwords
    3855      *
    3856      * @param url a URL
    3857      * @throws FileNotFoundException
    3858      */
    3859     public static void withReader(URL url, Closure closure) throws IOException {
    3860         withReader(url.openConnection().getInputStream(), closure);
    3861     }
    3862
    3863     /***
    3864      * Helper method to create a new BufferedReader for a stream and then
    3865      * passes it into the closure and ensures its closed again afterwords
    3866      *
    3867      * @param in a stream
    3868      * @throws FileNotFoundException
    3869      */
    3870     public static void withReader(InputStream in, Closure closure) throws IOException {
    3871         withReader(new InputStreamReader(in), closure);
    3872     }
    3873
    3874     /***
    3875      * Allows an output stream to be used, calling the closure with the output stream
    3876      * and then ensuring that the output stream is closed down again irrespective
    3877      * of whether exceptions occur
    3878      *
    3879      * @param stream  the stream which is used and then closed
    3880      * @param closure the closure that the writer is passed into
    3881      * @throws IOException
    3882      */
    3883     public static void withWriter(OutputStream stream, Closure closure) throws IOException {
    3884         withWriter(new OutputStreamWriter(stream), closure);
    3885     }
    3886
    3887     /***
    3888      * Allows an output stream to be used, calling the closure with the output stream
    3889      * and then ensuring that the output stream is closed down again irrespective
    3890      * of whether exceptions occur.
    3891      *
    3892      * @param stream  the stream which is used and then closed
    3893      * @param charset the charset used
    3894      * @param closure the closure that the writer is passed into
    3895      * @throws IOException
    3896      */
    3897     public static void withWriter(OutputStream stream, String charset, Closure closure) throws IOException {
    3898         withWriter(new OutputStreamWriter(stream, charset), closure);
    3899     }
    3900
    3901     /***
    3902      * Allows a OutputStream to be used, calling the closure with the stream
    3903      * and then ensuring that the stream is closed down again irrespective
    3904      * of whether exceptions occur.
    3905      *
    3906      * @param stream  the stream which is used and then closed
    3907      * @param closure the closure that the stream is passed into
    3908      * @throws IOException
    3909      */
    3910     public static void withStream(OutputStream stream, Closure closure) throws IOException {
    3911         try {
    3912             closure.call(stream);
    3913
    3914             // lets try close the stream & throw the exception if it fails
    3915             // but not try to reclose it in the finally block
    3916             OutputStream temp = stream;
    3917             stream = null;
    3918             temp.close();
    3919         } finally {
    3920             if (stream != null) {
    3921                 try {
    3922                     stream.close();
    3923                 } catch (IOException e) {
    3924                     log.warning("Caught exception closing stream: " + e);
    3925                 }
    3926             }
    3927         }
    3928     }
    3929
    3930     /***
    3931      * Helper method to create a buffered input stream for a file
    3932      *
    3933      * @param file a File
    3934      * @return a BufferedInputStream of the file
    3935      * @throws FileNotFoundException
    3936      */
    3937     public static BufferedInputStream newInputStream(File file) throws FileNotFoundException {
    3938         return new BufferedInputStream(new FileInputStream(file));
    3939     }
    3940
    3941     /***
    3942      * Traverse through each byte of the specified File
    3943      *
    3944      * @param self    a File
    3945      * @param closure a closure
    3946      */
    3947     public static void eachByte(File self, Closure closure) throws IOException {
    3948         BufferedInputStream is = newInputStream(self);
    3949         eachByte(is, closure);
    3950     }
    3951
    3952     /***
    3953      * Traverse through each byte of the specified stream
    3954      *
    3955      * @param is      stream to iterate over
    3956      * @param closure closure to apply to each byte
    3957      * @throws IOException
    3958      */
    3959     public static void eachByte(InputStream is, Closure closure) throws IOException {
    3960         try {
    3961             while (true) {
    3962                 int b = is.read();
    3963                 if (b == -1) {
    3964                     break;
    3965                 } else {
    3966                     closure.call(new Byte((byte) b));
    3967                 }
    3968             }
    3969             is.close();
    3970         } catch (IOException e) {
    3971             if (is != null) {
    3972                 try {
    3973                     is.close();
    3974                 } catch (Exception e2) {
    3975                     // ignore as we're already throwing
    3976                 }
    3977                 throw e;
    3978             }
    3979         }
    3980     }
    3981
    3982     /***
    3983      * Traverse through each byte of the specified URL
    3984      *
    3985      * @param url     url to iterate over
    3986      * @param closure closure to apply to each byte
    3987      * @throws IOException
    3988      */
    3989     public static void eachByte(URL url, Closure closure) throws IOException {
    3990         InputStream is = url.openConnection().getInputStream();
    3991         eachByte(is, closure);
    3992     }
    3993
    3994     /***
    3995      * Transforms the characters from a reader with a Closure and write them to a writer
    3996      *
    3997      * @param reader
    3998      * @param writer
    3999      * @param closure
    4000      */
    4001     public static void transformChar(Reader reader, Writer writer, Closure closure) {
    4002         int c;
    4003         try {
    4004             char[] chars = new char[1];
    4005             while ((c = reader.read()) != -1) {
    4006                 chars[0] = (char) c;
    4007                 writer.write((String) closure.call(new String(chars)));
    4008             }
    4009         } catch (IOException e) {
    4010         }
    4011     }
    4012
    4013     /***
    4014      * Transforms the lines from a reader with a Closure and write them to a writer
    4015      *
    4016      * @param reader
    4017      * @param writer
    4018      * @param closure
    4019      */
    4020     public static void transformLine(Reader reader, Writer writer, Closure closure) throws IOException {
    4021         BufferedReader br = new BufferedReader(reader);
    4022         BufferedWriter bw = new BufferedWriter(writer);
    4023         String line;
    4024         while ((line = br.readLine()) != null) {
    4025             Object o = closure.call(line);
    4026             if (o != null) {
    4027                 bw.write(o.toString());
    4028                 bw.newLine();
    4029             }
    4030         }
    4031     }
    4032
    4033     /***
    4034      * Filter the lines from a reader and write them on the writer, according to a closure
    4035      * which returns true or false.
    4036      *
    4037      * @param reader  a reader
    4038      * @param writer  a writer
    4039      * @param closure the closure which returns booleans
    4040      * @throws IOException
    4041      */
    4042     public static void filterLine(Reader reader, Writer writer, Closure closure) throws IOException {
    4043         BufferedReader br = new BufferedReader(reader);
    4044         BufferedWriter bw = new BufferedWriter(writer);
    4045         String line;
    4046         while ((line = br.readLine()) != null) {
    4047             if (InvokerHelper.asBool(closure.call(line))) {
    4048                 bw.write(line);
    4049                 bw.newLine();
    4050             }
    4051         }
    4052         bw.flush();
    4053     }
    4054
    4055     /***
    4056      * Filters the lines of a File and creates a Writeable in return to stream the filtered lines
    4057      *
    4058      * @param self    a File
    4059      * @param closure a closure which returns a boolean indicating to filter the line or not
    4060      * @return a Writable closure
    4061      * @throws IOException if <code>self</code> is not readable
    4062      */
    4063     public static Writable filterLine(final File self, final Closure closure) throws IOException {
    4064         return filterLine(newReader(self), closure);
    4065     }
    4066
    4067     /***
    4068      * Filter the lines from a File and write them on a writer, according to a closure
    4069      * which returns true or false
    4070      *
    4071      * @param self    a File
    4072      * @param writer  a writer
    4073      * @param closure a closure which returns a boolean value and takes a line as input
    4074      * @throws IOException if <code>self</code> is not readable
    4075      */
    4076     public static void filterLine(final File self, final Writer writer, final Closure closure) throws IOException {
    4077         filterLine(newReader(self), writer, closure);
    4078     }
    4079
    4080     /***
    4081      * Filter the lines of a Reader and create a Writable in return to stream the filtered lines
    4082      *
    4083      * @param reader  a reader
    4084      * @param closure a closure returning a boolean indicating to filter or not a line
    4085      * @return a Writable closure
    4086      */
    4087     public static Writable filterLine(Reader reader, final Closure closure) {
    4088         final BufferedReader br = new BufferedReader(reader);
    4089         return new Writable() {
    4090             public Writer writeTo(Writer out) throws IOException {
    4091                 BufferedWriter bw = new BufferedWriter(out);
    4092                 String line;
    4093                 while ((line = br.readLine()) != null) {
    4094                     if (InvokerHelper.asBool(closure.call(line))) {
    4095                         bw.write(line);
    4096                         bw.newLine();
    4097                     }
    4098                 }
    4099                 bw.flush();
    4100                 return out;
    4101             }
    4102
    4103             public String toString() {
    4104                 StringWriter buffer = new StringWriter();
    4105                 try {
    4106                     writeTo(buffer);
    4107                 } catch (IOException e) {
    4108                     throw new RuntimeException(e); // TODO: change this exception type
    4109                 }
    4110                 return buffer.toString();
    4111             }
    4112         };
    4113     }
    4114
    4115     /***
    4116      * Filter lines from an input stream using a closure predicate
    4117      *
    4118      * @param self      an input stream
    4119      * @param predicate a closure which returns boolean and takes a line
    4120      * @return a filtered writer
    4121      */
    4122     public static Writable filterLine(final InputStream self, final Closure predicate) {
    4123         return filterLine(newReader(self), predicate);
    4124     }
    4125
    4126     /***
    4127      * Filters lines from an input stream, writing to a writer, using a closure which
    4128      * returns boolean and takes a line.
    4129      *
    4130      * @param self      an InputStream
    4131      * @param writer    a writer to write output to
    4132      * @param predicate a closure which returns a boolean and takes a line as input
    4133      */
    4134     public static void filterLine(final InputStream self, final Writer writer, final Closure predicate)
    4135             throws IOException {
    4136         filterLine(newReader(self), writer, predicate);
    4137     }
    4138
    4139     /***
    4140      * Reads the content of the file into an array of byte
    4141      *
    4142      * @param file a File
    4143      * @return a List of Bytes
    4144      */
    4145     public static byte[] readBytes(File file) throws IOException {
    4146         byte[] bytes = new byte[(int) file.length()];
    4147         FileInputStream fileInputStream = new FileInputStream(file);
    4148         DataInputStream dis = new DataInputStream(fileInputStream);
    4149         dis.readFully(bytes);
    4150         dis.close();
    4151         return bytes;
    4152     }
    4153
    4154
    4155
    4156     // ================================
    4157     // Socket and ServerSocket methods
    4158
    4159     /***
    4160      * Allows an InputStream and an OutputStream from a Socket to be used,
    4161      * calling the closure with the streams and then ensuring that the streams are closed down again
    4162      * irrespective of whether exceptions occur.
    4163      *
    4164      * @param socket  a Socket
    4165      * @param closure a Closure
    4166      * @throws IOException
    4167      */
    4168     public static void withStreams(Socket socket, Closure closure) throws IOException {
    4169         InputStream input = socket.getInputStream();
    4170         OutputStream output = socket.getOutputStream();
    4171         try {
    4172             closure.call(new Object[]{input, output});
    4173         } finally {
    4174             try {
    4175                 input.close();
    4176             } catch (IOException e) {
    4177                 // noop
    4178             }
    4179             try {
    4180                 output.close();
    4181             } catch (IOException e) {
    4182                 // noop
    4183             }
    4184         }
    4185     }
    4186
    4187     /***
    4188      * Overloads the left shift operator to provide an append mechanism
    4189      * to add things to the output stream of a socket
    4190      *
    4191      * @param self  a Socket
    4192      * @param value a value to append
    4193      * @return a Writer
    4194      */
    4195     public static Writer leftShift(Socket self, Object value) throws IOException {
    4196         return leftShift(self.getOutputStream(), value);
    4197     }
    4198
    4199     /***
    4200      * Overloads the left shift operator to provide an append mechanism
    4201      * to add bytes to the output stream of a socket
    4202      *
    4203      * @param self  a Socket
    4204      * @param value a value to append
    4205      * @return an OutputStream
    4206      */
    4207     public static OutputStream leftShift(Socket self, byte[] value) throws IOException {
    4208         return leftShift(self.getOutputStream(), value);
    4209     }
    4210
    4211     /***
    4212      * Allow to pass a Closure to the accept methods of ServerSocket
    4213      *
    4214      * @param serverSocket a ServerSocket
    4215      * @param closure      a Closure
    4216      * @return a Socket
    4217      * @throws IOException
    4218      */
    4219     public static Socket accept(ServerSocket serverSocket, final Closure closure) throws IOException {
    4220         final Socket socket = serverSocket.accept();
    4221         new Thread(new Runnable() {
    4222             public void run() {
    4223                 try {
    4224                     closure.call(socket);
    4225                 } finally {
    4226                     try {
    4227                         socket.close();
    4228                     } catch (IOException e) {
    4229                         // noop
    4230                     }
    4231                 }
    4232             }
    4233         }).start();
    4234         return socket;
    4235     }
    4236
    4237
    4238     /***
    4239      * @param file a File
    4240      * @return a File which wraps the input file and which implements Writable
    4241      */
    4242     public static File asWritable(File file) {
    4243         return new WritableFile(file);
    4244     }
    4245
    4246     /***
    4247      * @param file     a File
    4248      * @param encoding the encoding to be used when reading the file's contents
    4249      * @return File which wraps the input file and which implements Writable
    4250      */
    4251     public static File asWritable(File file, String encoding) {
    4252         return new WritableFile(file, encoding);
    4253     }
    4254
    4255     /***
    4256      * Converts the given String into a List of strings of one character
    4257      *
    4258      * @param self a String
    4259      * @return a List of characters (a 1-character String)
    4260      */
    4261     public static List toList(String self) {
    4262         int size = self.length();
    4263         List answer = new ArrayList(size);
    4264         for (int i = 0; i < size; i++) {
    4265             answer.add(self.substring(i, i + 1));
    4266         }
    4267         return answer;
    4268     }
    4269
    4270     // Process methods
    4271     //-------------------------------------------------------------------------
    4272
    4273     /***
    4274      * An alias method so that a process appears similar to System.out, System.in, System.err;
    4275      * you can use process.in, process.out, process.err in a similar way
    4276      *
    4277      * @return an InputStream
    4278      */
    4279     public static InputStream getIn(Process self) {
    4280         return self.getInputStream();
    4281     }
    4282
    4283     /***
    4284      * Read the text of the output stream of the Process.
    4285      *
    4286      * @param self a Process
    4287      * @return the text of the output
    4288      * @throws IOException
    4289      */
    4290     public static String getText(Process self) throws IOException {
    4291         return getText(new BufferedReader(new InputStreamReader(self.getInputStream())));
    4292     }
    4293
    4294     /***
    4295      * An alias method so that a process appears similar to System.out, System.in, System.err;
    4296      * you can use process.in, process.out, process.err in a similar way
    4297      *
    4298      * @return an InputStream
    4299      */
    4300     public static InputStream getErr(Process self) {
    4301         return self.getErrorStream();
    4302     }
    4303
    4304     /***
    4305      * An alias method so that a process appears similar to System.out, System.in, System.err;
    4306      * you can use process.in, process.out, process.err in a similar way
    4307      *
    4308      * @return an OutputStream
    4309      */
    4310     public static OutputStream getOut(Process self) {
    4311         return self.getOutputStream();
    4312     }
    4313
    4314     /***
    4315      * Overloads the left shift operator to provide an append mechanism
    4316      * to pipe into a Process
    4317      *
    4318      * @param self  a Process
    4319      * @param value a value to append
    4320      * @return a Writer
    4321      */
    4322     public static Writer leftShift(Process self, Object value) throws IOException {
    4323         return leftShift(self.getOutputStream(), value);
    4324     }
    4325
    4326     /***
    4327      * Overloads the left shift operator to provide an append mechanism
    4328      * to pipe into a Process
    4329      *
    4330      * @param self  a Process
    4331      * @param value a value to append
    4332      * @return an OutputStream
    4333      */
    4334     public static OutputStream leftShift(Process self, byte[] value) throws IOException {
    4335         return leftShift(self.getOutputStream(), value);
    4336     }
    4337
    4338     /***
    4339      * Wait for the process to finish during a certain amount of time, otherwise stops the process.
    4340      *
    4341      * @param self           a Process
    4342      * @param numberOfMillis the number of milliseconds to wait before stopping the process
    4343      */
    4344     public static void waitForOrKill(Process self, long numberOfMillis) {
    4345         ProcessRunner runnable = new ProcessRunner(self);
    4346         Thread thread = new Thread(runnable);
    4347         thread.start();
    4348         runnable.waitForOrKill(numberOfMillis);
    4349     }
    4350
    4351     /***
    4352      * process each regex matched substring of a string object. The object
    4353      * passed to the closure is a matcher with rich information of the last
    4354      * successful match
    4355      *
    4356      * @param str     the target string
    4357      * @param regex   a Regex string
    4358      * @param closure a closure
    4359      * @author bing ran
    4360      */
    4361     public static void eachMatch(String str, String regex, Closure closure) {
    4362         Pattern p = Pattern.compile(regex);
    4363         Matcher m = p.matcher(str);
    4364         while (m.find()) {
    4365             int count = m.groupCount();
    4366             ArrayList groups = new ArrayList();
    4367             for (int i = 0; i <= count; i++) {
    4368                 groups.add(m.group(i));
    4369             }
    4370             closure.call(groups);
    4371         }
    4372     }
    4373
    4374     public static void each(Matcher matcher, Closure closure) {
    4375         Matcher m = matcher;
    4376         while (m.find()) {
    4377             int count = m.groupCount();
    4378             ArrayList groups = new ArrayList();
    4379             for (int i = 0; i <= count; i++) {
    4380                 groups.add(m.group(i));
    4381             }
    4382             closure.call(groups);
    4383         }
    4384     }
    4385
    4386     /***
    4387      * Iterates over every element of the collection and return the index of the first object
    4388      * that matches the condition specified in the closure
    4389      *
    4390      * @param self    the iteration object over which we iterate
    4391      * @param closure the filter to perform a match on the collection
    4392      * @return an integer that is the index of the first macthed object.
    4393      */
    4394     public static int findIndexOf(Object self, Closure closure) {
    4395         int i = 0;
    4396         for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); i++) {
    4397             Object value = iter.next();
    4398             if (InvokerHelper.asBool(closure.call(value))) {
    4399                 break;
    4400             }
    4401         }
    4402         return i;
    4403     }
    4404
    4405     /***
    4406      * A Runnable which waits for a process to complete together with a notification scheme
    4407      * allowing another thread to wait a maximum number of seconds for the process to complete
    4408      * before killing it.
    4409      */
    4410     protected static class ProcessRunner implements Runnable {
    4411         Process process;
    4412         private boolean finished;
    4413
    4414         public ProcessRunner(Process process) {
    4415             this.process = process;
    4416         }
    4417
    4418         public void run() {
    4419             try {
    4420                 process.waitFor();
    4421             } catch (InterruptedException e) {
    4422             }
    4423             synchronized (this) {
    4424                 notifyAll();
    4425                 finished = true;
    4426             }
    4427         }
    4428
    4429         public synchronized void waitForOrKill(long millis) {
    4430             if (!finished) {
    4431                 try {
    4432                     wait(millis);
    4433                 } catch (InterruptedException e) {
    4434                 }
    4435                 if (!finished) {
    4436                     process.destroy();
    4437                 }
    4438             }
    4439         }
    4440     }
    4441 }