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