001    /*
002     $Id: Invoker.java,v 1.87 2005/11/13 16:42:13 blackdrag 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
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.runtime;
047    
048    import groovy.lang.Closure;
049    import groovy.lang.GroovyObject;
050    import groovy.lang.GroovyRuntimeException;
051    import groovy.lang.MetaClass;
052    import groovy.lang.MetaClassRegistry;
053    import groovy.lang.MissingMethodException;
054    import groovy.lang.Range;
055    import groovy.lang.SpreadList;
056    import groovy.lang.Tuple;
057    import groovy.lang.GroovyInterceptable;
058    import org.apache.xml.serialize.OutputFormat;
059    import org.apache.xml.serialize.XMLSerializer;
060    import org.w3c.dom.Element;
061    import org.w3c.dom.Node;
062    import org.w3c.dom.NodeList;
063    
064    import java.io.File;
065    import java.io.IOException;
066    import java.io.StringWriter;
067    import java.lang.reflect.Array;
068    import java.lang.reflect.Method;
069    import java.math.BigDecimal;
070    import java.security.AccessController;
071    import java.security.PrivilegedAction;
072    import java.util.ArrayList;
073    import java.util.Arrays;
074    import java.util.Collection;
075    import java.util.Collections;
076    import java.util.Enumeration;
077    import java.util.Iterator;
078    import java.util.List;
079    import java.util.Map;
080    import java.util.NoSuchElementException;
081    import java.util.regex.Matcher;
082    import java.util.regex.Pattern;
083    
084    /**
085     * A helper class to invoke methods or extract properties on arbitrary Java objects dynamically
086     *
087     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
088     * @version $Revision: 1.87 $
089     */
090    public class Invoker {
091    
092        protected static final Object[] EMPTY_ARGUMENTS = {
093        };
094        protected static final Class[] EMPTY_TYPES = {
095        };
096    
097        public MetaClassRegistry getMetaRegistry() {
098            return metaRegistry;
099        }
100    
101        private MetaClassRegistry metaRegistry = new MetaClassRegistry();
102    
103        public MetaClass getMetaClass(Object object) {
104            return metaRegistry.getMetaClass(object.getClass());
105        }
106    
107        /**
108         * Invokes the given method on the object.
109         *
110         * @param object
111         * @param methodName
112         * @param arguments
113         * @return
114         */
115        public Object invokeMethod(Object object, String methodName, Object arguments) {
116            /*
117            System
118                .out
119                .println(
120                    "Invoker - Invoking method on object: "
121                        + object
122                        + " method: "
123                        + methodName
124                        + " arguments: "
125                        + InvokerHelper.toString(arguments));
126                        */
127    
128            if (object == null) {
129                throw new NullPointerException("Cannot invoke method " + methodName + "() on null object");
130            }
131            
132            // if the object is a Class, call a static method from that class
133            if (object instanceof Class) {
134                Class theClass = (Class) object;
135                MetaClass metaClass = metaRegistry.getMetaClass(theClass);
136                return metaClass.invokeStaticMethod(object, methodName, asArray(arguments));
137            }
138            else // it's an instance
139            {
140                // if it's not an object implementing GroovyObject (thus not builder, nor a closure)
141                if (!(object instanceof GroovyObject)) {
142                    Class theClass = object.getClass();
143                    MetaClass metaClass = metaRegistry.getMetaClass(theClass);
144                    return metaClass.invokeMethod(object, methodName, asArray(arguments));
145                }
146                // it's an object implementing GroovyObject
147                else {
148                    GroovyObject groovy = (GroovyObject) object;
149                    try {
150                        // if it's a pure interceptable object (even intercepting toString(), clone(), ...)
151                        if (groovy instanceof GroovyInterceptable) {
152                            return groovy.invokeMethod(methodName, asArray(arguments));
153                        }
154                        //else if there's a statically typed method or a GDK method
155                        else {
156                            return groovy.getMetaClass().invokeMethod(object, methodName, asArray(arguments));
157                        }
158                    } catch (MissingMethodException e) {
159                        if (e.getMethod().equals(methodName) && object.getClass() == e.getType()) {
160                            // in case there's nothing else, invoke the object's own invokeMethod()
161                            return groovy.invokeMethod(methodName, asArray(arguments));
162                        } else {
163                            throw e;
164                        }
165                    }
166                }
167            }
168        }
169    
170        public Object invokeSuperMethod(Object object, String methodName, Object arguments) {
171            if (object == null) {
172                throw new NullPointerException("Cannot invoke method " + methodName + "() on null object");
173            }
174    
175            Class theClass = object.getClass();
176    
177            MetaClass metaClass = metaRegistry.getMetaClass(theClass.getSuperclass());
178            return metaClass.invokeMethod(object, methodName, asArray(arguments));
179        }
180    
181        public Object invokeStaticMethod(String type, String method, Object arguments) {
182            MetaClass metaClass = metaRegistry.getMetaClass(loadClass(type));
183            List argumentList = asList(arguments);
184            return metaClass.invokeStaticMethod(null, method, asArray(arguments));
185        }
186    
187        public Object invokeConstructorAt(Class at, Class type, Object arguments) {
188            MetaClass metaClass = metaRegistry.getMetaClass(type);
189            return metaClass.invokeConstructorAt(at, asArray(arguments));
190        }
191    
192        public Object invokeConstructorAt(Class at, String type, Object arguments) {
193            return invokeConstructorAt(at, loadClass(type), arguments);
194        }
195    
196        public Object invokeConstructorOf(Class type, Object arguments) {
197            MetaClass metaClass = metaRegistry.getMetaClass(type);
198            return metaClass.invokeConstructor(asArray(arguments));
199        }
200    
201        public Object invokeConstructorOf(String type, Object arguments) {
202            return invokeConstructorOf(loadClass(type), arguments);
203        }
204    
205        /**
206         * Converts the given object into an array; if its an array then just
207         * cast otherwise wrap it in an array
208         */
209        public Object[] asArray(Object arguments) {
210            if (arguments == null) {
211                return EMPTY_ARGUMENTS;
212            }
213            else if ((arguments instanceof Object[]) && ((Object[]) arguments).length == 0) {
214                return (Object[]) arguments;
215            }
216            else if (arguments instanceof Tuple) {
217                Tuple tuple = (Tuple) arguments;
218                Object[] objects = tuple.toArray();
219                ArrayList array = new ArrayList();
220                for (int i = 0; i < objects.length; i++) {
221                    if (objects[i] instanceof SpreadList) {
222                        SpreadList slist = (SpreadList) objects[i];
223                        for (int j = 0; j < slist.size(); j++) {
224                            array.add(slist.get(j));
225                        }
226                    }
227                    else {
228                        array.add(objects[i]);
229                    }
230                }
231                return array.toArray();
232            }
233            else if (arguments instanceof Object[]) {
234                Object[] objects = (Object[]) arguments;
235                ArrayList array = new ArrayList();
236                for (int i = 0; i < objects.length; i++) {
237                    if (objects[i] instanceof SpreadList) {
238                        SpreadList slist = (SpreadList) objects[i];
239                        for (int j = 0; j < slist.size(); j++) {
240                            array.add(slist.get(j));
241                        }
242                    }
243                    else {
244                        array.add(objects[i]);
245                    }
246                }
247                return array.toArray();
248            }
249            else if (arguments instanceof SpreadList) {
250                ArrayList array = new ArrayList();
251                SpreadList slist = (SpreadList) arguments;
252                for (int j = 0; j < slist.size(); j++) {
253                    array.add(slist.get(j));
254                }
255                return array.toArray();
256            }
257            else {
258                return new Object[]{arguments};
259            }
260        }
261    
262        public List asList(Object value) {
263            if (value == null) {
264                return Collections.EMPTY_LIST;
265            }
266            else if (value instanceof List) {
267                return (List) value;
268            }
269            else if (value.getClass().isArray()) {
270                return Arrays.asList((Object[]) value);
271            }
272            else if (value instanceof Enumeration) {
273                List answer = new ArrayList();
274                for (Enumeration e = (Enumeration) value; e.hasMoreElements();) {
275                    answer.add(e.nextElement());
276                }
277                return answer;
278            }
279            else {
280                // lets assume its a collection of 1
281                return Collections.singletonList(value);
282            }
283        }
284    
285        /**
286         * Converts the value parameter into a <code>Collection</code>.
287         *
288         * @param value value to convert
289         * @return a Collection
290         */
291        public Collection asCollection(Object value) {
292            if (value == null) {
293                return Collections.EMPTY_LIST;
294            }
295            else if (value instanceof Collection) {
296                return (Collection) value;
297            }
298            else if (value instanceof Map) {
299                Map map = (Map) value;
300                return map.entrySet();
301            }
302            else if (value.getClass().isArray()) {
303                if (value.getClass().getComponentType().isPrimitive()) {
304                    return InvokerHelper.primitiveArrayToList(value);
305                }
306                return Arrays.asList((Object[]) value);
307            }
308            else if (value instanceof MethodClosure) {
309                MethodClosure method = (MethodClosure) value;
310                IteratorClosureAdapter adapter = new IteratorClosureAdapter(method.getDelegate());
311                method.call(adapter);
312                return adapter.asList();
313            }
314            else if (value instanceof String) {
315                return DefaultGroovyMethods.toList((String) value);
316            }
317            else if (value instanceof File) {
318                try {
319                    return DefaultGroovyMethods.readLines((File) value);
320                }
321                catch (IOException e) {
322                    throw new GroovyRuntimeException("Error reading file: " + value, e);
323                }
324            }
325            else {
326                // lets assume its a collection of 1
327                return Collections.singletonList(value);
328            }
329        }
330    
331        public Iterator asIterator(Object value) {
332            if (value == null) {
333                return Collections.EMPTY_LIST.iterator();
334            }
335            if (value instanceof Iterator) {
336                return (Iterator) value;
337            }
338            if (value instanceof NodeList) {
339                final NodeList nodeList = (NodeList) value;
340                return new Iterator() {
341                    private int current = 0;
342    
343                    public boolean hasNext() {
344                        return current < nodeList.getLength();
345                    }
346    
347                    public Object next() {
348                        Node node = nodeList.item(current++);
349                        return node;
350                    }
351    
352                    public void remove() {
353                        throw new UnsupportedOperationException("Cannot remove() from an Enumeration");
354                    }
355                };
356            }
357            else if (value instanceof Enumeration) {
358                final Enumeration enumeration = (Enumeration) value;
359                return new Iterator() {
360                    private Object last;
361    
362                    public boolean hasNext() {
363                        return enumeration.hasMoreElements();
364                    }
365    
366                    public Object next() {
367                        last = enumeration.nextElement();
368                        return last;
369                    }
370    
371                    public void remove() {
372                        throw new UnsupportedOperationException("Cannot remove() from an Enumeration");
373                    }
374                };
375            }
376            else if (value instanceof Matcher) {
377                final Matcher matcher = (Matcher) value;
378                return new Iterator() {
379                    private boolean found = false;
380                    private boolean done = false;
381    
382                    public boolean hasNext() {
383                        if (done) {
384                            return false;
385                        }
386                        if (!found) {
387                            found = matcher.find();
388                            if (!found) {
389                                done = true;
390                            }
391                        }
392                        return found;
393                    }
394    
395                    public Object next() {
396                        if (!found) {
397                            if (!hasNext()) {
398                                throw new NoSuchElementException();
399                            }
400                        }
401                        found = false;
402                        return matcher.group();
403                    }
404    
405                    public void remove() {
406                        throw new UnsupportedOperationException();
407                    }
408                };
409            }
410            else {
411                try {
412                    // lets try see if there's an iterator() method
413                    final Method method = value.getClass().getMethod("iterator", EMPTY_TYPES);
414    
415                    if (method != null) {
416                        AccessController.doPrivileged(new PrivilegedAction() {
417                            public Object run() {
418                                method.setAccessible(true);
419                                return null;
420                            }
421                        });
422    
423                        return (Iterator) method.invoke(value, EMPTY_ARGUMENTS);
424                    }
425                }
426                catch (Exception e) {
427                    //  ignore
428                }
429            }
430            return asCollection(value).iterator();
431        }
432    
433        /**
434         * @return true if the two objects are null or the objects are equal
435         */
436        public boolean objectsEqual(Object left, Object right) {
437            if (left == right) {
438                return true;
439            }
440            if (left != null) {
441                if (right == null) {
442                    return false;
443                }
444                if (left instanceof Comparable) {
445                    return compareTo(left, right) == 0;
446                }
447                else if (left instanceof List && right instanceof List) {
448                    return DefaultGroovyMethods.equals((List) left, (List) right);
449                }
450                else {
451                    return left.equals(right);
452                }
453            }
454            return false;
455        }
456    
457        public String inspect(Object self) {
458            return format(self, true);
459        }
460    
461        /**
462         * Compares the two objects handling nulls gracefully and performing numeric type coercion if required
463         */
464        public int compareTo(Object left, Object right) {
465            //System.out.println("Comparing: " + left + " to: " + right);
466            if (left == right) {
467                return 0;
468            }
469            if (left == null) {
470                return -1;
471            }
472            else if (right == null) {
473                return 1;
474            }
475            if (left instanceof Comparable) {
476                if (left instanceof Number) {
477                    if (isValidCharacterString(right)) {
478                        return asCharacter((Number) left).compareTo(asCharacter((String) right));
479                    }
480                    return DefaultGroovyMethods.compareTo((Number) left, asNumber(right));
481                }
482                else if (left instanceof Character) {
483                    if (isValidCharacterString(right)) {
484                        return ((Character) left).compareTo(asCharacter((String) right));
485                    }
486                    else if (right instanceof Number) {
487                        return ((Character) left).compareTo(asCharacter((Number) right));
488                    }
489                }
490                else if (right instanceof Number) {
491                    if (isValidCharacterString(left)) {
492                        return asCharacter((String) left).compareTo(asCharacter((Number) right));
493                    }
494                    return DefaultGroovyMethods.compareTo(asNumber(left), (Number) right);
495                }
496                else if (left instanceof String && right instanceof Character) {
497                    return ((String) left).compareTo(right.toString());
498                }
499                Comparable comparable = (Comparable) left;
500                return comparable.compareTo(right);
501            }
502    
503            if (left.getClass().isArray()) {
504                Collection leftList = asCollection(left);
505                if (right.getClass().isArray()) {
506                    right = asCollection(right);
507                }
508                return ((Comparable) leftList).compareTo(right);
509            }
510            /** todo we might wanna do some type conversion here */
511            throw new GroovyRuntimeException("Cannot compare values: " + left + " and " + right);
512        }
513    
514        /**
515         * A helper method to provide some better toString() behaviour such as turning arrays
516         * into tuples
517         */
518        public String toString(Object arguments) {
519            if (arguments instanceof Object[])
520                return toArrayString((Object[]) arguments);
521            else if (arguments instanceof Map)
522                return toMapString((Map) arguments);
523            else if (arguments instanceof Collection)
524                return format(arguments, true);
525            else
526                return format(arguments, false);
527        }
528    
529        /**
530         * A helper method to format the arguments types as a comma-separated list
531         */
532        public String toTypeString(Object[] arguments) {
533            if (arguments == null) {
534                return "null";
535            }
536            StringBuffer argBuf = new StringBuffer();
537            for (int i = 0; i < arguments.length; i++) {
538                if (i > 0) {
539                    argBuf.append(", ");
540                }
541                argBuf.append(arguments[i] != null ? arguments[i].getClass().getName() : "null");
542            }
543            return argBuf.toString();
544        }
545    
546        /**
547         * A helper method to return the string representation of a map with bracket boundaries "[" and "]".
548         */
549        public String toMapString(Map arg) {
550            if (arg == null) {
551                return "null";
552            }
553            if (arg.isEmpty()) {
554                return "[:]";
555            }
556            String sbdry = "[";
557            String ebdry = "]";
558            StringBuffer buffer = new StringBuffer(sbdry);
559            boolean first = true;
560            for (Iterator iter = arg.entrySet().iterator(); iter.hasNext();) {
561                if (first)
562                    first = false;
563                else
564                    buffer.append(", ");
565                Map.Entry entry = (Map.Entry) iter.next();
566                buffer.append(format(entry.getKey(), true));
567                buffer.append(":");
568                buffer.append(format(entry.getValue(), true));
569            }
570            buffer.append(ebdry);
571            return buffer.toString();
572        }
573    
574        /**
575         * A helper method to return the string representation of a list with bracket boundaries "[" and "]".
576         */
577        public String toListString(Collection arg) {
578            if (arg == null) {
579                return "null";
580            }
581            if (arg.isEmpty()) {
582                return "[]";
583            }
584            String sbdry = "[";
585            String ebdry = "]";
586            StringBuffer buffer = new StringBuffer(sbdry);
587            boolean first = true;
588            for (Iterator iter = arg.iterator(); iter.hasNext();) {
589                if (first)
590                    first = false;
591                else
592                    buffer.append(", ");
593                Object elem = iter.next();
594                buffer.append(format(elem, true));
595            }
596            buffer.append(ebdry);
597            return buffer.toString();
598        }
599    
600        /**
601         * A helper method to return the string representation of an arrray of objects
602         * with brace boundaries "{" and "}".
603         */
604        public String toArrayString(Object[] arguments) {
605            if (arguments == null) {
606                return "null";
607            }
608            String sbdry = "{";
609            String ebdry = "}";
610            StringBuffer argBuf = new StringBuffer(sbdry);
611            for (int i = 0; i < arguments.length; i++) {
612                if (i > 0) {
613                    argBuf.append(", ");
614                }
615                argBuf.append(format(arguments[i], true));
616            }
617            argBuf.append(ebdry);
618            return argBuf.toString();
619        }
620    
621        protected String format(Object arguments, boolean verbose) {
622            if (arguments == null) {
623                return "null";
624            }
625            else if (arguments.getClass().isArray()) {
626                return format(asCollection(arguments), verbose);
627            }
628            else if (arguments instanceof Range) {
629                Range range = (Range) arguments;
630                if (verbose) {
631                    return range.inspect();
632                }
633                else {
634                    return range.toString();
635                }
636            }
637            else if (arguments instanceof List) {
638                List list = (List) arguments;
639                StringBuffer buffer = new StringBuffer("[");
640                boolean first = true;
641                for (Iterator iter = list.iterator(); iter.hasNext();) {
642                    if (first) {
643                        first = false;
644                    }
645                    else {
646                        buffer.append(", ");
647                    }
648                    buffer.append(format(iter.next(), verbose));
649                }
650                buffer.append("]");
651                return buffer.toString();
652            }
653            else if (arguments instanceof Map) {
654                Map map = (Map) arguments;
655                if (map.isEmpty()) {
656                    return "[:]";
657                }
658                StringBuffer buffer = new StringBuffer("[");
659                boolean first = true;
660                for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
661                    if (first) {
662                        first = false;
663                    }
664                    else {
665                        buffer.append(", ");
666                    }
667                    Map.Entry entry = (Map.Entry) iter.next();
668                    buffer.append(format(entry.getKey(), verbose));
669                    buffer.append(":");
670                    buffer.append(format(entry.getValue(), verbose));
671                }
672                buffer.append("]");
673                return buffer.toString();
674            }
675            else if (arguments instanceof Element) {
676                Element node = (Element) arguments;
677                OutputFormat format = new OutputFormat(node.getOwnerDocument());
678                format.setOmitXMLDeclaration(true);
679                format.setIndenting(true);
680                format.setLineWidth(0);
681                format.setPreserveSpace(true);
682                StringWriter sw = new StringWriter();
683                XMLSerializer serializer = new XMLSerializer(sw, format);
684                try {
685                    serializer.asDOMSerializer();
686                    serializer.serialize(node);
687                }
688                catch (IOException e) {
689                }
690                return sw.toString();
691            }
692            else if (arguments instanceof String) {
693                if (verbose) {
694                    String arg = ((String)arguments).replaceAll("\\n", "\\\\n");    // line feed
695                    arg = arg.replaceAll("\\r", "\\\\r");      // carriage return
696                    arg = arg.replaceAll("\\t", "\\\\t");      // tab
697                    arg = arg.replaceAll("\\f", "\\\\f");      // form feed
698                    arg = arg.replaceAll("\\\"", "\\\\\"");    // double quotation amrk
699                    arg = arg.replaceAll("\\\\", "\\\\");      // back slash
700                    return "\"" + arg + "\"";
701                }
702                else {
703                    return (String) arguments;
704                }
705            }
706            else {
707                return arguments.toString();
708            }
709        }
710    
711        /**
712         * Looks up the given property of the given object
713         */
714        public Object getProperty(Object object, String property) {
715            if (object == null) {
716                throw new NullPointerException("Cannot get property: " + property + " on null object");
717            }
718            else if (object instanceof GroovyObject) {
719                GroovyObject pogo = (GroovyObject) object;
720                return pogo.getProperty(property);
721            }
722            else if (object instanceof Map) {
723                Map map = (Map) object;
724                return map.get(property);
725            }
726            else {
727                return metaRegistry.getMetaClass(object.getClass()).getProperty(object, property);
728            }
729        }
730        
731        /**
732         * Sets the property on the given object
733         */
734        public void setProperty(Object object, String property, Object newValue) {
735            if (object == null) {
736                throw new GroovyRuntimeException("Cannot set property on null object");
737            }
738            else if (object instanceof GroovyObject) {
739                GroovyObject pogo = (GroovyObject) object;
740                pogo.setProperty(property, newValue);
741            }
742            else if (object instanceof Map) {
743                Map map = (Map) object;
744                map.put(property, newValue);
745            }
746            else {
747                if (object instanceof Class)
748                    metaRegistry.getMetaClass((Class) object).setProperty((Class) object, property, newValue);
749                else
750                    metaRegistry.getMetaClass(object.getClass()).setProperty(object, property, newValue);
751            }
752        }
753    
754        /**
755         * Looks up the given attribute (field) on the given object
756         */
757        public Object getAttribute(Object object, String attribute) {
758            if (object == null) {
759                throw new NullPointerException("Cannot get attribute: " + attribute + " on null object");
760    
761                /**
762                 } else if (object instanceof GroovyObject) {
763                 GroovyObject pogo = (GroovyObject) object;
764                 return pogo.getAttribute(attribute);
765                 } else if (object instanceof Map) {
766                 Map map = (Map) object;
767                 return map.get(attribute);
768                 */
769            }
770            else {
771                if (object instanceof Class) {
772                    return metaRegistry.getMetaClass((Class) object).getAttribute(object, attribute);
773                } else if (object instanceof GroovyObject) {
774                    return ((GroovyObject)object).getMetaClass().getAttribute(object, attribute);
775                } else {
776                    return metaRegistry.getMetaClass(object.getClass()).getAttribute(object, attribute);
777                }
778            }
779        }
780    
781        /**
782         * Sets the given attribute (field) on the given object
783         */
784        public void setAttribute(Object object, String attribute, Object newValue) {
785            if (object == null) {
786                throw new GroovyRuntimeException("Cannot set attribute on null object");
787                /*
788            } else if (object instanceof GroovyObject) {
789                GroovyObject pogo = (GroovyObject) object;
790                pogo.setProperty(attribute, newValue);
791            } else if (object instanceof Map) {
792                Map map = (Map) object;
793                map.put(attribute, newValue);
794                */
795            }
796            else {
797                if (object instanceof Class) {
798                    metaRegistry.getMetaClass((Class) object).setAttribute(object, attribute, newValue);
799                } else if (object instanceof GroovyObject) {
800                    ((GroovyObject)object).getMetaClass().setAttribute(object, attribute, newValue);
801                } else {
802                    metaRegistry.getMetaClass(object.getClass()).setAttribute(object, attribute, newValue);
803                }
804            }
805        }
806    
807        /**
808         * Returns the method pointer for the given object name
809         */
810        public Closure getMethodPointer(Object object, String methodName) {
811            if (object == null) {
812                throw new NullPointerException("Cannot access method pointer for '" + methodName + "' on null object");
813            }
814            return MetaClassHelper.getMethodPointer(object, methodName);
815        }
816    
817    
818        /**
819         * Attempts to load the given class via name using the current class loader
820         * for this code or the thread context class loader
821         */
822        protected Class loadClass(String type) {
823            try {
824                return getClass().getClassLoader().loadClass(type);
825            }
826            catch (ClassNotFoundException e) {
827                try {
828                    return Thread.currentThread().getContextClassLoader().loadClass(type);
829                }
830                catch (ClassNotFoundException e2) {
831                    try {
832                        return Class.forName(type);
833                    }
834                    catch (ClassNotFoundException e3) {
835                    }
836                }
837                throw new GroovyRuntimeException("Could not load type: " + type, e);
838            }
839        }
840    
841        /**
842         * Find the right hand regex within the left hand string and return a matcher.
843         *
844         * @param left  string to compare
845         * @param right regular expression to compare the string to
846         * @return
847         */
848        public Matcher objectFindRegex(Object left, Object right) {
849            String stringToCompare;
850            if (left instanceof String) {
851                stringToCompare = (String) left;
852            }
853            else {
854                stringToCompare = toString(left);
855            }
856            String regexToCompareTo;
857            if (right instanceof String) {
858                regexToCompareTo = (String) right;
859            }
860            else if (right instanceof Pattern) {
861                Pattern pattern = (Pattern) right;
862                return pattern.matcher(stringToCompare);
863            }
864            else {
865                regexToCompareTo = toString(right);
866            }
867            Matcher matcher = Pattern.compile(regexToCompareTo).matcher(stringToCompare);
868            return matcher;
869        }
870    
871        /**
872         * Find the right hand regex within the left hand string and return a matcher.
873         *
874         * @param left  string to compare
875         * @param right regular expression to compare the string to
876         * @return
877         */
878        public boolean objectMatchRegex(Object left, Object right) {
879            Pattern pattern;
880            if (right instanceof Pattern) {
881                pattern = (Pattern) right;
882            }
883            else {
884                pattern = Pattern.compile(toString(right));
885            }
886            String stringToCompare = toString(left);
887            Matcher matcher = pattern.matcher(stringToCompare);
888            RegexSupport.setLastMatcher(matcher);
889            return matcher.matches();
890        }
891    
892        /**
893         * Compile a regular expression from a string.
894         *
895         * @param regex
896         * @return
897         */
898        public Pattern regexPattern(Object regex) {
899            return Pattern.compile(regex.toString());
900        }
901    
902        public Object asType(Object object, Class type) {
903            if (object == null) {
904                return null;
905            }
906            // TODO we should move these methods to groovy method, like g$asType() so that
907            // we can use operator overloading to customize on a per-type basis
908            if (type.isArray()) {
909                return asArray(object, type);
910    
911            }
912            if (type.isInstance(object)) {
913                return object;
914            }
915            if (type.isAssignableFrom(Collection.class)) {
916                if (object.getClass().isArray()) {
917                    // lets call the collections constructor
918                    // passing in the list wrapper
919                    Collection answer = null;
920                    try {
921                        answer = (Collection) type.newInstance();
922                    }
923                    catch (Exception e) {
924                        throw new ClassCastException("Could not instantiate instance of: " + type.getName() + ". Reason: " + e);
925                    }
926    
927                    // we cannot just wrap in a List as we support primitive type arrays
928                    int length = Array.getLength(object);
929                    for (int i = 0; i < length; i++) {
930                        Object element = Array.get(object, i);
931                        answer.add(element);
932                    }
933                    return answer;
934                }
935            }
936            if (type.equals(String.class)) {
937                return object.toString();
938            }
939            if (type.equals(Character.class)) {
940                if (object instanceof Number) {
941                    return asCharacter((Number) object);
942                }
943                else {
944                    String text = object.toString();
945                    if (text.length() == 1) {
946                        return new Character(text.charAt(0));
947                    }
948                    else {
949                        throw new ClassCastException("Cannot cast: " + text + " to a Character");
950                    }
951                }
952            }
953            if (Number.class.isAssignableFrom(type)) {
954                if (object instanceof Character) {
955                    return new Integer(((Character) object).charValue());
956                }
957                else if (object instanceof String) {
958                    String c = (String) object;
959                    if (c.length() == 1) {
960                        return new Integer(c.charAt(0));
961                    }
962                    else {
963                        throw new ClassCastException("Cannot cast: '" + c + "' to an Integer");
964                    }
965                }
966            }
967            if (object instanceof Number) {
968                Number n = (Number) object;
969                if (type.isPrimitive()) {
970                    if (type == byte.class) {
971                        return new Byte(n.byteValue());
972                    }
973                    if (type == char.class) {
974                        return new Character((char) n.intValue());
975                    }
976                    if (type == short.class) {
977                        return new Short(n.shortValue());
978                    }
979                    if (type == int.class) {
980                        return new Integer(n.intValue());
981                    }
982                    if (type == long.class) {
983                        return new Long(n.longValue());
984                    }
985                    if (type == float.class) {
986                        return new Float(n.floatValue());
987                    }
988                    if (type == double.class) {
989                        Double answer = new Double(n.doubleValue());
990                        //throw a runtime exception if conversion would be out-of-range for the type.
991                        if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY
992                                || answer.doubleValue() == Double.POSITIVE_INFINITY)) {
993                            throw new GroovyRuntimeException("Automatic coercion of " + n.getClass().getName()
994                                    + " value " + n + " to double failed.  Value is out of range.");
995                        }
996                        return answer;
997                    }
998                }
999                else {
1000                    if (Number.class.isAssignableFrom(type)) {
1001                        if (type == Byte.class) {
1002                            return new Byte(n.byteValue());
1003                        }
1004                        if (type == Character.class) {
1005                            return new Character((char) n.intValue());
1006                        }
1007                        if (type == Short.class) {
1008                            return new Short(n.shortValue());
1009                        }
1010                        if (type == Integer.class) {
1011                            return new Integer(n.intValue());
1012                        }
1013                        if (type == Long.class) {
1014                            return new Long(n.longValue());
1015                        }
1016                        if (type == Float.class) {
1017                            return new Float(n.floatValue());
1018                        }
1019                        if (type == Double.class) {
1020                            Double answer = new Double(n.doubleValue());
1021                            //throw a runtime exception if conversion would be out-of-range for the type.
1022                            if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY
1023                                    || answer.doubleValue() == Double.POSITIVE_INFINITY)) {
1024                                throw new GroovyRuntimeException("Automatic coercion of " + n.getClass().getName()
1025                                        + " value " + n + " to double failed.  Value is out of range.");
1026                            }
1027                            return answer;
1028                        }
1029    
1030                    }
1031                }
1032            }
1033            if (type == Boolean.class) {
1034                return asBool(object) ? Boolean.TRUE : Boolean.FALSE;
1035            }
1036            Object[] args = null;
1037            if (object instanceof Collection) {
1038                Collection list = (Collection) object;
1039                args = list.toArray();
1040            }
1041            else if (object instanceof Object[]) {
1042                args = (Object[]) object;
1043            }
1044            if (args != null) {
1045                // lets try invoke the constructor with the list as arguments
1046                // such as for creating a Dimension, Point, Color etc.
1047                try {
1048                    return invokeConstructorOf(type, args);
1049                }
1050                catch (Exception e) {
1051                    // lets ignore exception and return the original object
1052                    // as the caller has more context to be able to throw a more
1053                    // meaningful exception
1054                }
1055    
1056            }
1057            return object;
1058        }
1059    
1060        public Object asArray(Object object, Class type) {
1061            Collection list = asCollection(object);
1062            int size = list.size();
1063            Class elementType = type.getComponentType();
1064            Object array = Array.newInstance(elementType, size);
1065            int idx = 0;
1066    
1067            if (boolean.class.equals(elementType)) {
1068                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1069                    Object element = iter.next();
1070                    Array.setBoolean(array, idx, asBool(element));
1071                }
1072            }
1073            else if (byte.class.equals(elementType)) {
1074                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1075                    Object element = iter.next();
1076                    Array.setByte(array, idx, asByte(element));
1077                }
1078            }
1079            else if (char.class.equals(elementType)) {
1080                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1081                    Object element = iter.next();
1082                    Array.setChar(array, idx, asChar(element));
1083                }
1084            }
1085            else if (double.class.equals(elementType)) {
1086                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1087                    Object element = iter.next();
1088                    Array.setDouble(array, idx, asDouble(element));
1089                }
1090            }
1091            else if (float.class.equals(elementType)) {
1092                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1093                    Object element = iter.next();
1094                    Array.setFloat(array, idx, asFloat(element));
1095                }
1096            }
1097            else if (int.class.equals(elementType)) {
1098                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1099                    Object element = iter.next();
1100                    Array.setInt(array, idx, asInt(element));
1101                }
1102            }
1103            else if (long.class.equals(elementType)) {
1104                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1105                    Object element = iter.next();
1106                    Array.setLong(array, idx, asLong(element));
1107                }
1108            }
1109            else if (short.class.equals(elementType)) {
1110                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1111                    Object element = iter.next();
1112                    Array.setShort(array, idx, asShort(element));
1113                }
1114            }
1115            else {
1116                for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
1117                    Object element = iter.next();
1118                    Object coercedElement = asType(element, elementType);
1119                    Array.set(array, idx, coercedElement);
1120                }
1121            }
1122            return array;
1123        }
1124    
1125        public Number asNumber(Object value) {
1126            if (value instanceof Number) {
1127                return (Number) value;
1128            }
1129            else if (value instanceof String) {
1130                String s = (String) value;
1131    
1132                if (s.length() == 1) {
1133                    return new Integer(s.charAt(0));
1134                }
1135                else {
1136                    return new BigDecimal(s);
1137                }
1138            }
1139            else if (value instanceof Character) {
1140                return new Integer(((Character) value).charValue());
1141            }
1142            else {
1143                throw new GroovyRuntimeException("Could not convert object: " + value + " into a Number");
1144            }
1145        }
1146    
1147        public byte asByte(Object element) {
1148            return asNumber(element).byteValue();
1149        }
1150    
1151        public char asChar(Object element) {
1152            if (element instanceof String) {
1153                return asCharacter((String) element).charValue();
1154            }
1155            return asCharacter(asNumber(element)).charValue();
1156        }
1157    
1158        public float asFloat(Object element) {
1159            return asNumber(element).floatValue();
1160        }
1161    
1162        public double asDouble(Object element) {
1163            return asNumber(element).doubleValue();
1164        }
1165    
1166        public short asShort(Object element) {
1167            return asNumber(element).shortValue();
1168        }
1169    
1170        public int asInt(Object element) {
1171            return asNumber(element).intValue();
1172        }
1173    
1174        public long asLong(Object element) {
1175            return asNumber(element).longValue();
1176        }
1177    
1178        public boolean asBool(Object object) {
1179            if (object instanceof Boolean) {
1180                Boolean booleanValue = (Boolean) object;
1181                return booleanValue.booleanValue();
1182            }
1183            else if (object instanceof Matcher) {
1184                Matcher matcher = (Matcher) object;
1185                RegexSupport.setLastMatcher(matcher);
1186                return matcher.find();
1187            }
1188            else if (object instanceof Collection) {
1189                Collection collection = (Collection) object;
1190                return !collection.isEmpty();
1191            }
1192            else if (object instanceof Map) {
1193                Map map = (Map) object;
1194                return !map.isEmpty();
1195            }
1196            else if (object instanceof String) {
1197                String string = (String) object;
1198                return string.length() > 0;
1199            }
1200            else if (object instanceof Number) {
1201                Number n = (Number) object;
1202                return n.doubleValue() != 0;
1203            }
1204            else {
1205                return object != null;
1206            }
1207        }
1208    
1209        protected Character asCharacter(Number value) {
1210            return new Character((char) value.intValue());
1211        }
1212    
1213        protected Character asCharacter(String text) {
1214            return new Character(text.charAt(0));
1215        }
1216    
1217        /**
1218         * @return true if the given value is a valid character string (i.e. has length of 1)
1219         */
1220        protected boolean isValidCharacterString(Object value) {
1221            if (value instanceof String) {
1222                String s = (String) value;
1223                if (s.length() == 1) {
1224                    return true;
1225                }
1226            }
1227            return false;
1228        }
1229    
1230        public void removeMetaClass(Class clazz) {
1231            getMetaRegistry().removeMetaClass(clazz);
1232        }
1233    }