View Javadoc

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