View Javadoc

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