View Javadoc

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