View Javadoc

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