View Javadoc

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