View Javadoc

1   /*
2    $Id: MetaClass.java,v 1.82 2004/07/26 20:51:54 glaforge Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.lang;
47  
48  import java.beans.BeanInfo;
49  import java.beans.EventSetDescriptor;
50  import java.beans.IntrospectionException;
51  import java.beans.Introspector;
52  import java.beans.PropertyDescriptor;
53  import java.lang.reflect.Array;
54  import java.lang.reflect.Constructor;
55  import java.lang.reflect.Field;
56  import java.lang.reflect.InvocationHandler;
57  import java.lang.reflect.InvocationTargetException;
58  import java.lang.reflect.Method;
59  import java.lang.reflect.Modifier;
60  import java.lang.reflect.Proxy;
61  import java.math.BigDecimal;
62  import java.math.BigInteger;
63  import java.net.URL;
64  import java.security.AccessControlException;
65  import java.security.AccessController;
66  import java.security.PrivilegedAction;
67  import java.security.PrivilegedActionException;
68  import java.security.PrivilegedExceptionAction;
69  import java.util.ArrayList;
70  import java.util.Arrays;
71  import java.util.Collection;
72  import java.util.Collections;
73  import java.util.HashMap;
74  import java.util.Iterator;
75  import java.util.List;
76  import java.util.Map;
77  import java.util.logging.Logger;
78  
79  import org.codehaus.groovy.ast.ClassNode;
80  import org.codehaus.groovy.classgen.ReflectorGenerator;
81  import org.codehaus.groovy.control.CompilationUnit;
82  import org.codehaus.groovy.control.Phases;
83  import org.codehaus.groovy.runtime.ClosureListener;
84  import org.codehaus.groovy.runtime.DefaultGroovyMethods;
85  import org.codehaus.groovy.runtime.GroovyCategorySupport;
86  import org.codehaus.groovy.runtime.InvokerHelper;
87  import org.codehaus.groovy.runtime.InvokerInvocationException;
88  import org.codehaus.groovy.runtime.MethodClosure;
89  import org.codehaus.groovy.runtime.MethodHelper;
90  import org.codehaus.groovy.runtime.MethodKey;
91  import org.codehaus.groovy.runtime.NewInstanceMetaMethod;
92  import org.codehaus.groovy.runtime.NewStaticMetaMethod;
93  import org.codehaus.groovy.runtime.ReflectionMetaMethod;
94  import org.codehaus.groovy.runtime.Reflector;
95  import org.codehaus.groovy.runtime.TemporaryMethodKey;
96  import org.codehaus.groovy.runtime.TransformMetaMethod;
97  import org.objectweb.asm.ClassVisitor;
98  import org.objectweb.asm.ClassWriter;
99  
100 /***
101  * Allows methods to be dynamically added to existing classes at runtime
102  * 
103  * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
104  * @author Guillaume Laforge
105  * @version $Revision: 1.82 $
106  */
107 public class MetaClass {
108 
109     private static final Logger log = Logger.getLogger(MetaClass.class.getName());
110 
111     public static final Object[] EMPTY_ARRAY = {
112     };
113     public static Class[] EMPTY_TYPE_ARRAY = {
114     };
115     protected static final Object[] ARRAY_WITH_NULL = { null };
116 
117     private static boolean useReflection = false;
118 
119     private MetaClassRegistry registry;
120     private Class theClass;
121     private ClassNode classNode;
122     private Map methodIndex = new HashMap();
123     private Map staticMethodIndex = new HashMap();
124     private List newGroovyMethodsList = new ArrayList();
125     //private Map propertyDescriptors = Collections.synchronizedMap(new HashMap());
126     private Map propertyMap = Collections.synchronizedMap(new HashMap());
127     private Map listeners = new HashMap();
128     private Map methodCache = Collections.synchronizedMap(new HashMap());
129     private Map staticMethodCache = Collections.synchronizedMap(new HashMap());
130     private MetaMethod genericGetMethod;
131     private MetaMethod genericSetMethod;
132     private List constructors;
133     private List allMethods = new ArrayList();
134     private List interfaceMethods;
135     private Reflector reflector;
136     private boolean initialised;
137 	// we only need one of these that can be reused over and over.
138 	private MetaProperty arrayLengthProperty = new MetaArrayLengthProperty();
139 	
140     public MetaClass(MetaClassRegistry registry, final Class theClass) throws IntrospectionException {
141         this.registry = registry;
142         this.theClass = theClass;
143 
144         constructors = Arrays.asList(theClass.getDeclaredConstructors());
145         addMethods(theClass);
146 
147         // introspect
148         BeanInfo info = null;
149         try {
150             info =(BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() {
151                 public Object run() throws IntrospectionException {
152                     return Introspector.getBeanInfo(theClass);
153                 }
154             });
155         } catch (PrivilegedActionException pae) {
156             if (pae.getException() instanceof IntrospectionException) {
157                 throw (IntrospectionException) pae.getException();
158             } else {
159                 throw new RuntimeException(pae.getException());
160             }
161         }
162 
163         PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
164         
165         // build up the metaproperties based on the public fields, property descriptors,
166         // and the getters and setters
167         setupProperties(descriptors);
168         
169         /* old code
170         for (int i = 0; i < descriptors.length; i++) {
171             PropertyDescriptor descriptor = descriptors[i];
172             propertyDescriptors.put(descriptor.getName(), descriptor);
173         }
174         */
175         
176         EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors();
177         for (int i = 0; i < eventDescriptors.length; i++) {
178             EventSetDescriptor descriptor = eventDescriptors[i];
179             Method[] listenerMethods = descriptor.getListenerMethods();
180             for (int j = 0; j < listenerMethods.length; j++) {
181                 Method listenerMethod = listenerMethods[j];
182                 MetaMethod metaMethod = createMetaMethod(descriptor.getAddListenerMethod());
183                 listeners.put(listenerMethod.getName(), metaMethod);
184             }
185         }
186     }
187 
188     public static boolean isUseReflection() {
189         return useReflection;
190     }
191 
192     /***
193      * Allows reflection to be enabled in situations where bytecode generation
194      * of method invocations causes issues.
195      * 
196      * @param useReflection
197      */
198     public static void setUseReflection(boolean useReflection) {
199         MetaClass.useReflection = useReflection;
200     }
201 
202     private void addInheritedMethods(Class theClass) {
203         // lets add all the base class methods
204         Class c = theClass;
205         if (c != Object.class) {
206             while (true) {
207                 c = c.getSuperclass();
208                 if (c == Object.class || c == null) {
209                     break;
210                 }
211                 addMethods(c);
212                 addNewStaticMethodsFrom(c);
213 
214             }
215         }
216 
217         // now lets see if there are any methods on one of my interfaces
218         Class[] interfaces = theClass.getInterfaces();
219         for (int i = 0; i < interfaces.length; i++) {
220             addNewStaticMethodsFrom(interfaces[i]);
221         }
222 
223         // lets add Object methods after interfaces, as all interfaces derive from Object. 
224         // this ensures List and Collection methods come before Object etc
225         if (theClass != Object.class) {
226             addMethods(Object.class);
227             addNewStaticMethodsFrom(Object.class);
228         }
229 
230         if (theClass.isArray() && !theClass.equals(Object[].class)) {
231             addNewStaticMethodsFrom(Object[].class);
232         }
233     }
234 
235     /***
236      * @return all the normal instance methods avaiable on this class for the
237      *         given name
238      */
239     public List getMethods(String name) {
240         List answer = (List) methodIndex.get(name);
241         List used = GroovyCategorySupport.getCategoryMethods(theClass, name);
242         if (used != null) {
243             if (answer != null) {
244                 answer.addAll(used);
245             } else{
246                 answer = used;
247             }
248         }
249         if (answer == null) {
250             answer = Collections.EMPTY_LIST;
251         }
252         return answer;
253     }
254 
255     /***
256      * @return all the normal static methods avaiable on this class for the
257      *         given name
258      */
259     public List getStaticMethods(String name) {
260         List answer = (List) staticMethodIndex.get(name);
261         if (answer == null) {
262             return Collections.EMPTY_LIST;
263         }
264         return answer;
265     }
266 
267     /***
268      * Allows static method definitions to be added to a meta class as if it
269      * was an instance method
270      * 
271      * @param method
272      */
273     protected void addNewInstanceMethod(Method method) {
274         if (initialised) {
275             throw new RuntimeException("Already initialized, cannot add new method: " + method);
276         }
277         else {
278             NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(createMetaMethod(method));
279             addMethod(newMethod);
280             addNewInstanceMethod(newMethod);
281         }
282     }
283 
284     protected void addNewInstanceMethod(MetaMethod method) {
285         newGroovyMethodsList.add(method);
286     }
287 
288     protected void addNewStaticMethod(Method method) {
289         if (initialised) {
290             throw new RuntimeException("Already initialized, cannot add new method: " + method);
291         }
292         else {
293             NewStaticMetaMethod newMethod = new NewStaticMetaMethod(createMetaMethod(method));
294             addMethod(newMethod);
295             addNewStaticMethod(newMethod);
296         }
297     }
298 
299     protected void addNewStaticMethod(MetaMethod method) {
300         newGroovyMethodsList.add(method);
301     }
302 
303     public Object invokeMethod(Object object, String methodName, Object arguments) {
304         return invokeMethod(object, methodName, asArray(arguments));
305     }
306 
307     /***
308      * Invokes the given method on the object.
309      *  
310      */
311     public Object invokeMethod(Object object, String methodName, Object[] arguments) {
312         if (object == null) {
313             throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
314         }
315 
316         MetaMethod method = retrieveMethod(object, methodName, arguments);
317 
318         if (method != null) {
319             return doMethodInvoke(object, method, arguments);
320         } else {
321             // if no method was found, try to find a closure defined as a field of the class and run it
322             try {
323                 Object value = this.getProperty(object, methodName);
324                 if (value instanceof Closure) {
325                     Closure closure = (Closure) value;
326                     closure.setDelegate(this);
327                     return closure.call(arguments);
328                 }
329                 else {
330                     throw new MissingMethodException(methodName, theClass, arguments);
331                 }
332             }
333             catch (Exception e) {
334                 throw new MissingMethodException(methodName, theClass, arguments);
335             }
336         }
337     }
338 
339     protected MetaMethod retrieveMethod(Object owner, String methodName, Object[] arguments) {
340         // lets try use the cache to find the method
341         MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
342         MetaMethod method = (MetaMethod) methodCache.get(methodKey);
343         if (method == null) {
344             method = pickMethod(owner, methodName, arguments);
345             if (method != null && method.isCacheable()) {
346                 methodCache.put(methodKey.createCopy(), method);
347             }
348         }
349         return method;
350     }
351 
352     public MetaMethod retrieveMethod(String methodName, Class[] arguments) {
353         // lets try use the cache to find the method
354         MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
355         MetaMethod method = (MetaMethod) methodCache.get(methodKey);
356         if (method == null) {
357             method = pickMethod(methodName, arguments); // todo shall call pickStaticMethod also?
358             if (method != null && method.isCacheable()) {
359                 methodCache.put(methodKey.createCopy(), method);
360             }
361         }
362         return method;
363     }
364 
365     public Constructor retrieveConstructor(Class[] arguments) {
366         Constructor constructor = (Constructor) chooseMethod("<init>", constructors, arguments, false);
367         if (constructor != null) {
368             return constructor;
369         }
370         else {
371             constructor = (Constructor) chooseMethod("<init>", constructors, arguments, true);
372             if (constructor != null) {
373                 return constructor;
374             }
375         }
376         return null;
377     }
378 
379     public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) {
380         MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
381         MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
382         if (method == null) {
383             method = pickStaticMethod(methodName, arguments);
384             if (method != null) {
385                 staticMethodCache.put(methodKey.createCopy(), method);
386             }
387         }
388         return method;
389     }
390     /***
391      * Picks which method to invoke for the given object, method name and arguments
392      */
393     protected MetaMethod pickMethod(Object object, String methodName, Object[] arguments) {
394         MetaMethod method = null;
395         List methods = getMethods(methodName);
396         if (!methods.isEmpty()) {
397             Class[] argClasses = convertToTypeArray(arguments);
398             method = (MetaMethod) chooseMethod(methodName, methods, argClasses, false);
399             if (method == null) {
400                 method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true);
401                 if (method == null) {
402                     int size = (arguments != null) ? arguments.length : 0;
403                     if (size == 1) {
404                         Object firstArgument = arguments[0];
405                         if (firstArgument instanceof List) {
406                             // lets coerce the list arguments into an array of
407                             // arguments
408                             // e.g. calling JFrame.setLocation( [100, 100] )
409 
410                             List list = (List) firstArgument;
411                             arguments = list.toArray();
412                             argClasses = convertToTypeArray(arguments);
413                             method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true);
414                             return new TransformMetaMethod(method) {
415                                 public Object invoke(Object object, Object[] arguments) throws Exception {
416                                     Object firstArgument = arguments[0];
417                                     List list = (List) firstArgument;
418                                     arguments = list.toArray();
419                                     return super.invoke(object, arguments);
420                                 }
421                             };
422                         }
423                     }
424                 }
425             }
426         }
427         return method;
428     }
429 
430     /***
431      * pick a method in a strict manner, i.e., without reinterpreting the first List argument.
432      * this method is used only by ClassGenerator for static binding
433      * @param methodName
434      * @param arguments
435      * @return
436      */
437     protected MetaMethod pickMethod(String methodName, Class[] arguments) {
438         MetaMethod method = null;
439         List methods = getMethods(methodName);
440         if (!methods.isEmpty()) {
441             method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
442 // no coersion at classgen time.
443 //            if (method == null) {
444 //                method = (MetaMethod) chooseMethod(methodName, methods, arguments, true);
445 //            }
446         }
447         return method;
448     }
449 
450     public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
451         //        System.out.println("Calling static method: " + methodName + " on args: " + InvokerHelper.toString(arguments));
452         //        Class type = arguments == null ? null : arguments.getClass();
453         //        System.out.println("Argument  type: " + type);
454         //        System.out.println("Type of first arg: " + arguments[0] + " type: " + arguments[0].getClass());
455 
456         // lets try use the cache to find the method
457         MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
458         MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
459         if (method == null) {
460             method = pickStaticMethod(object, methodName, arguments);
461             if (method != null) {
462                 staticMethodCache.put(methodKey.createCopy(), method);
463             }
464         }
465 
466         if (method != null) {
467             return doMethodInvoke(object, method, arguments);
468         }
469         /*
470         List methods = getStaticMethods(methodName);
471         
472         if (!methods.isEmpty()) {
473             MetaMethod method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
474             if (method != null) {
475                 return doMethodInvoke(theClass, method, arguments);
476             }
477         }
478         
479         if (theClass != Class.class) {
480             try {
481                 return registry.getMetaClass(Class.class).invokeMethod(object, methodName, arguments);
482             }
483             catch (GroovyRuntimeException e) {
484                 // throw our own exception
485             }
486         }
487         */
488         throw new MissingMethodException(methodName, theClass, arguments);
489     }
490 
491     protected MetaMethod pickStaticMethod(Object object, String methodName, Object[] arguments) {
492         MetaMethod method = null;
493         List methods = getStaticMethods(methodName);
494 
495         if (!methods.isEmpty()) {
496             method = (MetaMethod) chooseMethod(methodName, methods, convertToTypeArray(arguments), false);
497         }
498 
499         if (method == null && theClass != Class.class) {
500             MetaClass classMetaClass = registry.getMetaClass(Class.class);
501             method = classMetaClass.pickMethod(object, methodName, arguments);
502         }
503         return method;
504     }
505 
506     protected MetaMethod pickStaticMethod(String methodName, Class[] arguments) {
507         MetaMethod method = null;
508         List methods = getStaticMethods(methodName);
509 
510         if (!methods.isEmpty()) {
511             method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
512 // disabled to keep consistant with the original version of pickStatciMethod
513 //            if (method == null) {
514 //                method = (MetaMethod) chooseMethod(methodName, methods, arguments, true);
515 //            }
516         }
517 
518         if (method == null && theClass != Class.class) {
519             MetaClass classMetaClass = registry.getMetaClass(Class.class);
520             method = classMetaClass.pickMethod(methodName, arguments);
521         }
522         return method;
523     }
524 
525     public Object invokeConstructor(Object[] arguments) {
526         Class[] argClasses = convertToTypeArray(arguments);
527         Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false);
528         if (constructor != null) {
529             return doConstructorInvoke(constructor, arguments);
530         }
531         else {
532             constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true);
533             if (constructor != null) {
534                 return doConstructorInvoke(constructor, arguments);
535             }
536         }
537 
538         if (arguments.length == 1) {
539             Object firstArgument = arguments[0];
540             if (firstArgument instanceof Map) {
541                 constructor = (Constructor) chooseMethod("<init>", constructors, EMPTY_TYPE_ARRAY, false);
542                 if (constructor != null) {
543                     Object bean = doConstructorInvoke(constructor, EMPTY_ARRAY);
544                     setProperties(bean, ((Map) firstArgument));
545                     return bean;
546                 }
547             }
548         }
549         throw new GroovyRuntimeException(
550                     "Could not find matching constructor for: "
551                         + theClass.getName()
552                         + "("+InvokerHelper.toTypeString(arguments)+")");
553     }
554 
555     /***
556      * Sets a number of bean properties from the given Map where the keys are
557      * the String names of properties and the values are the values of the
558      * properties to set
559      */
560     public void setProperties(Object bean, Map map) {
561         for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
562             Map.Entry entry = (Map.Entry) iter.next();
563             String key = entry.getKey().toString();
564             
565             // do we have this property?
566             if(propertyMap.get(key) == null)
567                 continue;
568             
569             Object value = entry.getValue();
570             try {
571                 setProperty(bean, key, value);
572             }
573             catch (GroovyRuntimeException e) {
574                 // lets ignore missing properties
575                 /*** todo should replace this code with a getMetaProperty(key) != null check
576                  i.e. don't try and set a non-existent property
577                  */
578             }
579         }
580     }
581 
582     /***
583      * @return the given property's value on the object
584      */
585     public Object getProperty(final Object object, final String property) {
586         /* old code; we now use the metaProperty map to handle properties
587         MetaMethod metaMethod = null;
588         PropertyDescriptor descriptor = (PropertyDescriptor) propertyDescriptors.get(property);
589         if (descriptor != null) {
590             Method method = descriptor.getReadMethod();
591             if (method == null) {
592                 throw new GroovyRuntimeException("Cannot read property: " + property);
593             }
594             metaMethod = findMethod(method);
595             if (metaMethod == null) {
596                 // lets try invoke a static getter method
597                 metaMethod = findGetter(object, "get" + capitalize(property));
598             }
599             if (metaMethod != null) {
600                 return doMethodInvoke(object, metaMethod, EMPTY_ARRAY);
601             }
602         }
603         */
604         
605         // look for the property in our map
606         MetaProperty mp = (MetaProperty) propertyMap.get(property);
607         if(mp != null) {
608             try {
609                 //System.out.println("we found a metaproperty for " + theClass.getName() +
610                 //  "." + property);
611                 // delegate the get operation to the metaproperty
612                 return mp.getProperty(object);
613             }
614             catch(Exception e) {
615                 throw new GroovyRuntimeException("Cannot read property: " + property);
616             }
617         }
618 
619         if (genericGetMethod == null) {
620             // Make sure there isn't a generic method in the "use" cases
621             List possibleGenericMethods = getMethods("get");
622             if (possibleGenericMethods != null) {
623                 for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) {
624                     MetaMethod mmethod = (MetaMethod) i.next();
625                     Class[] paramTypes = mmethod.getParameterTypes();
626                     if (paramTypes.length == 1 && paramTypes[0] == String.class) {
627                         Object[] arguments = {property};
628                         Object answer = doMethodInvoke(object, mmethod, arguments);
629                         return answer;
630                     }
631                 }
632             }
633         } 
634         else {
635             Object[] arguments = { property };
636             Object answer = doMethodInvoke(object, genericGetMethod, arguments);
637             // jes bug? a property retrieved via a generic get() can't have a null value?
638             if (answer != null) {
639                 return answer;
640             }
641         }
642 
643         // is the property the name of a method - in which case return a
644         // closure
645         List methods = getMethods(property);
646         if (!methods.isEmpty()) {
647             return new MethodClosure(object, property);
648         }
649 
650         // lets try invoke a static getter method
651         // this case is for protected fields. I wish there was a better way...
652         Exception lastException = null;
653         try {
654             MetaMethod method = findGetter(object, "get" + capitalize(property));
655             if (method != null) {
656                 return doMethodInvoke(object, method, EMPTY_ARRAY);
657             }
658         }
659         catch (GroovyRuntimeException e) {
660             lastException = e;
661         }
662 
663         /*** todo or are we an extensible groovy class? */
664         if (genericGetMethod != null) {
665             return null;
666         }
667         else {
668             /*** todo these special cases should be special MetaClasses maybe */
669             if (object instanceof Class) {
670                 // lets try a static field
671                 return getStaticProperty((Class) object, property);
672             }
673             if (object instanceof Collection) {
674                 return DefaultGroovyMethods.getAt((Collection) object, property);
675             }
676             if (object instanceof Object[]) {
677                 return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), property);
678             }
679             if (object instanceof Object) {
680                 try {
681                     // lets try a public field
682                     Field field = object.getClass().getDeclaredField(property);
683                     return field.get(object);
684                 }
685                 catch (Exception e1) {
686                     // fall through
687                 }
688             }
689             
690             MetaMethod addListenerMethod = (MetaMethod) listeners.get(property);
691             if (addListenerMethod != null) {
692                 /* @todo one day we could try return the previously registered Closure listener for easy removal */
693                 return null;
694             }
695 
696             if (lastException == null)
697                 throw new MissingPropertyException(property, theClass);
698             else
699                 throw new MissingPropertyException(property, theClass, lastException);
700         }
701     }
702     
703     /***
704      * Get all the properties defined for this type
705      * @return a list of MetaProperty objects
706      */
707     public List getProperties() {
708         // simply return the values of the metaproperty map as a List
709         return new ArrayList(propertyMap.values());
710     }
711     
712     /***
713      * This will build up the property map (Map of MetaProperty objects, keyed on 
714      * property name).
715      */
716     protected void setupProperties(PropertyDescriptor[] propertyDescriptors) {
717         MetaProperty mp;
718         Method method;
719         MetaMethod getter = null;
720         MetaMethod setter = null;
721         Class klass;
722         
723         // first get the public fields and create MetaFieldProperty objects
724         klass = theClass;
725         while(klass != null) {
726             Field[] fields = klass.getDeclaredFields();
727             for(int i = 0; i < fields.length; i++) {
728                 // we're only interested in publics
729                 if((fields[i].getModifiers() & java.lang.reflect.Modifier.PUBLIC) == 0)
730                     continue;
731                 
732                 // see if we already got this
733                 if(propertyMap.get(fields[i].getName()) != null)
734                     continue;
735                 
736                 //System.out.println("adding field " + fields[i].getName() + 
737                 //  " for class " + klass.getName());
738                 // stick it in there!
739                 propertyMap.put(fields[i].getName(), new MetaFieldProperty(fields[i]));
740             }
741             
742             // now get the super class
743             klass = klass.getSuperclass();
744         }
745         
746 		// if this an Array, then add the special read-only "length" property
747 		if(theClass.isArray()) {
748 			propertyMap.put("length", arrayLengthProperty);
749 		}
750 		
751         // now iterate over the map of property descriptors and generate 
752         // MetaBeanProperty objects
753         for(int i=0; i<propertyDescriptors.length; i++) {
754             PropertyDescriptor pd = propertyDescriptors[i];
755             // skip if the field already exists in the map
756             if(propertyMap.get(pd.getName()) != null)
757                 continue;
758             
759             // skip if the property type is unknown (this seems to be the case if the 
760             // property descriptor is based on a setX() method that has two parameters,
761             // which is not a valid property)
762             if(pd.getPropertyType() == null)
763                 continue;
764             
765             // get the getter method
766             method = pd.getReadMethod();
767             if(method != null)
768                 getter = findMethod(method);
769             else
770                 getter = null;
771             
772             // get the setter method
773             method = pd.getWriteMethod();
774             if(method != null)
775                 setter = findMethod(method);
776             else
777                 setter = null;
778             
779             // now create the MetaProperty object
780             //System.out.println("creating a bean property for class " +
781             //  theClass.getName() + ": " + pd.getName());
782                 
783             mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter);
784             
785             // put it in the list
786             propertyMap.put(pd.getName(), mp);
787         }
788         
789         // now look for any stray getters that may be used to define a property
790         klass = theClass;
791         while(klass != null) {
792             Method[] methods = klass.getDeclaredMethods();
793             for (int i = 0; i < methods.length; i++) {
794                 // filter out the privates
795                 if(Modifier.isPublic(methods[i].getModifiers()) == false)
796                     continue;
797                 
798                 method = methods[i];
799                 
800                 String methodName = method.getName();
801                 
802                 // is this a getter?
803                 if(methodName.startsWith("get") && 
804                     methodName.length() > 3 &&
805                     method.getParameterTypes().length == 0) {
806                     
807                     // get the name of the property
808                     String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);
809                     
810                     // is this property already accounted for?
811                     mp = (MetaProperty) propertyMap.get(propName);
812                     if(mp != null) {
813                         // we may have already found the setter for this
814                         if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getGetter() == null) {
815                             // update the getter method to this one
816                             ((MetaBeanProperty) mp).setGetter(findMethod(method));
817                         }
818                     }
819                     else {
820                         // we need to create a new property object
821                         // type of the property is what the get method returns
822                         MetaBeanProperty mbp = new MetaBeanProperty(propName, 
823                             method.getReturnType(),
824                             findMethod(method), null);
825                             
826                         // add it to the map
827                         propertyMap.put(propName, mbp);
828                     }
829                 }
830                 else if(methodName.startsWith("set") && 
831                     methodName.length() > 3 &&
832                     method.getParameterTypes().length == 1) {
833                     
834                     // get the name of the property
835                     String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);
836                     
837                     // did we already find the getter of this?
838                     mp = (MetaProperty) propertyMap.get(propName);
839                     if(mp != null) {
840                         if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getSetter() == null) {
841                             // update the setter method to this one
842                             ((MetaBeanProperty) mp).setSetter(findMethod(method));
843                         }
844                     }
845                     else {
846                         // this is a new property to add
847                         MetaBeanProperty mbp = new MetaBeanProperty(propName, 
848                                                                     method.getParameterTypes()[0],
849                                                                     null,
850                                                                     findMethod(method));
851                             
852                         // add it to the map
853                         propertyMap.put(propName, mbp);
854                     }
855                 }
856             }
857             
858             // now get the super class
859             klass = klass.getSuperclass();
860         }
861     }
862     
863     /***
864      * Sets the property value on an object
865      */
866     public void setProperty(Object object, String property, Object newValue) {
867         /* old code, replaced with new MetaProperty stuff...
868         PropertyDescriptor descriptor = (PropertyDescriptor) propertyDescriptors.get(property);
869 
870         if (descriptor != null) {
871             Method method = descriptor.getWriteMethod();
872             if (method == null) {
873                 throw new ReadOnlyPropertyException(property, theClass);
874             }
875             MetaMethod metaMethod = findMethod(method);
876             Object[] arguments = { newValue };
877             try {
878                 doMethodInvoke(object, metaMethod, arguments);
879             }
880             catch (GroovyRuntimeException e) {
881                 // if the value is a List see if we can construct the value
882                 // from a constructor
883                 if (newValue instanceof List) {
884                     List list = (List) newValue;
885                     int params = list.size();
886                     Constructor[] propConstructors = descriptor.getPropertyType().getConstructors();
887                     for (int i = 0; i < propConstructors.length; i++) {
888                         Constructor constructor = propConstructors[i];
889                         if (constructor.getParameterTypes().length == params) {
890                             Object value = doConstructorInvoke(constructor, list.toArray());
891                             doMethodInvoke(object, metaMethod, new Object[] { value });
892                             return;
893                         }
894                     }
895                     
896                     // if value is an array  
897                     Class parameterType = method.getParameterTypes()[0];
898                     if (parameterType.isArray()) {
899                         Object objArray = asPrimitiveArray(list, parameterType);
900                         doMethodInvoke(object, metaMethod, new Object[]{
901                             objArray
902                         });
903                         return;
904                     }
905                 }
906                 
907                 // if value is an multidimensional array  
908                 if (newValue.getClass().isArray()) {
909                     List list = Arrays.asList((Object[])newValue);
910                     
911                     Class parameterType = method.getParameterTypes()[0];
912                     Class arrayType = parameterType.getComponentType();
913                     Object objArray = Array.newInstance(arrayType, list.size());
914                     
915                     for (int i = 0; i < list.size(); i++) {
916                         List list2 =Arrays.asList((Object[]) list.get(i));
917                         Object objArray2 = asPrimitiveArray(list2, arrayType);
918                         Array.set(objArray, i, objArray2);
919                     }
920 
921                     doMethodInvoke(object, metaMethod, new Object[]{
922                         objArray
923                     });
924                     return;
925                 }
926                 
927                 throw new MissingPropertyException(property, theClass, e);
928             }
929             return;
930         }
931         */
932         
933         MetaProperty mp = (MetaProperty) propertyMap.get(property);
934         if(mp != null) {
935             try {
936                 mp.setProperty(object, newValue);
937                 return;
938             }
939 			catch(ReadOnlyPropertyException e) {
940 				// just rethrow it; there's nothing left to do here
941 				throw e;
942 			}
943             catch (Exception e) {
944                 // if the value is a List see if we can construct the value
945                 // from a constructor
946                 if (newValue instanceof List) {
947                     List list = (List) newValue;
948                     int params = list.size();
949                     Constructor[] constructors = mp.getType().getConstructors();
950                     for (int i = 0; i < constructors.length; i++) {
951                         Constructor constructor = constructors[i];
952                         if (constructor.getParameterTypes().length == params) {
953                             Object value = doConstructorInvoke(constructor, list.toArray());
954                             mp.setProperty(object, value);
955                             return;
956                         }
957                     }
958                     
959                     // if value is an array  
960                     Class parameterType = mp.getType();
961                     if (parameterType.isArray()) {
962                         Object objArray = asPrimitiveArray(list, parameterType);
963                         mp.setProperty(object, objArray);
964                         return;
965                     }
966                 }
967 
968                 // if value is an multidimensional array  
969                 // jes currently this logic only supports metabeansproperties and
970                 // not metafieldproperties. It shouldn't be too hard to support
971                 // the latter...
972                 if (newValue.getClass().isArray() && mp instanceof MetaBeanProperty) {
973                     MetaBeanProperty mbp = (MetaBeanProperty) mp;
974                     List list = Arrays.asList((Object[])newValue);
975                     MetaMethod setter = mbp.getSetter();
976                     
977                     Class parameterType = setter.getParameterTypes()[0];
978                     Class arrayType = parameterType.getComponentType();
979                     Object objArray = Array.newInstance(arrayType, list.size());
980                     
981                     for (int i = 0; i < list.size(); i++) {
982                         List list2 =Arrays.asList((Object[]) list.get(i));
983                         Object objArray2 = asPrimitiveArray(list2, arrayType);
984                         Array.set(objArray, i, objArray2);
985                     }
986 
987                     doMethodInvoke(object, setter, new Object[]{
988                         objArray
989                     });
990                     return;
991                 }
992                 
993                 throw new MissingPropertyException(property, theClass, e);
994             }
995         }
996         
997         try {
998             MetaMethod addListenerMethod = (MetaMethod) listeners.get(property);
999             if (addListenerMethod != null && newValue instanceof Closure) {
1000                 // lets create a dynamic proxy
1001                 Object proxy =
1002                     createListenerProxy(addListenerMethod.getParameterTypes()[0], property, (Closure) newValue);
1003                 doMethodInvoke(object, addListenerMethod, new Object[] { proxy });
1004                 return;
1005             }
1006 
1007             if (genericSetMethod == null) {
1008                 // Make sure there isn't a generic method in the "use" cases
1009                 List possibleGenericMethods = getMethods("set");
1010                 if (possibleGenericMethods != null) {
1011                     for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) {
1012                         MetaMethod mmethod = (MetaMethod) i.next();
1013                         Class[] paramTypes = mmethod.getParameterTypes();
1014                         if (paramTypes.length == 2 && paramTypes[0] == String.class) {
1015                             Object[] arguments = {property, newValue};
1016                             Object answer = doMethodInvoke(object, mmethod, arguments);
1017                             return;
1018                         }
1019                     }
1020                 }
1021             } 
1022             else {
1023                 Object[] arguments = { property, newValue };
1024                 doMethodInvoke(object, genericSetMethod, arguments);
1025                 return;
1026             }
1027 
1028             /*** todo or are we an extensible class? */
1029 
1030             // lets try invoke the set method
1031             // this is kind of ugly: if it is a protected field, we fall
1032             // all the way down to this klunky code. Need a better
1033             // way to handle this situation...
1034 
1035             String method = "set" + capitalize(property);
1036             try {
1037                 invokeMethod(object, method, new Object[] { newValue });
1038             }
1039             catch (MissingMethodException e1) {
1040                 try {
1041                     Field field = object.getClass().getDeclaredField(property);
1042                     field.set(object, newValue);
1043                 }
1044                 catch (Exception e2) {
1045                     throw new MissingPropertyException(property, theClass, e2);
1046                 }
1047             }
1048             
1049         }
1050         catch (GroovyRuntimeException e) {
1051             throw new MissingPropertyException(property, theClass, e);
1052         }
1053         
1054         // if we got here, the damn thing just aint there...
1055         throw new MissingPropertyException(property, theClass);
1056     }
1057 
1058     /***
1059      * @param list
1060      * @param parameterType
1061      * @return
1062      */
1063     private Object asPrimitiveArray(List list, Class parameterType) {
1064         Class arrayType = parameterType.getComponentType();
1065         Object objArray = Array.newInstance(arrayType, list.size());
1066         for (int i = 0; i < list.size(); i++) {
1067             Object obj = list.get(i);
1068             if (arrayType.isPrimitive()) {
1069                 if (obj instanceof Integer) {
1070                     Array.setInt(objArray, i, ((Integer) obj).intValue());
1071                 }
1072                 else if (obj instanceof Double) {
1073                     Array.setDouble(objArray, i, ((Double) obj).doubleValue());
1074                 }
1075                 else if (obj instanceof Boolean) {
1076                     Array.setBoolean(objArray, i, ((Boolean) obj).booleanValue());
1077                 }
1078                 else if (obj instanceof Long) {
1079                     Array.setLong(objArray, i, ((Long) obj).longValue());
1080                 }
1081                 else if (obj instanceof Float) {
1082                     Array.setFloat(objArray, i, ((Float) obj).floatValue());
1083                 }
1084                 else if (obj instanceof Character) {
1085                     Array.setChar(objArray, i, ((Character) obj).charValue());
1086                 }
1087                 else if (obj instanceof Byte) {
1088                     Array.setByte(objArray, i, ((Byte) obj).byteValue());
1089                 }
1090                 else if (obj instanceof Short) {
1091                     Array.setShort(objArray, i, ((Short) obj).shortValue());
1092                 }
1093             }
1094             else {
1095                 Array.set(objArray, i, obj);
1096             }
1097         }
1098         return objArray;
1099     }
1100     
1101     public ClassNode getClassNode() {
1102         if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) {
1103             // lets try load it from the classpath
1104             String className = theClass.getName();
1105             String groovyFile = className;
1106             int idx = groovyFile.indexOf('$');
1107             if (idx > 0) {
1108                 groovyFile = groovyFile.substring(0, idx);
1109             }
1110             groovyFile = groovyFile.replace('.', '/') + ".groovy";
1111 
1112             //System.out.println("Attempting to load: " + groovyFile);
1113             URL url = theClass.getClassLoader().getResource(groovyFile);
1114             if (url == null) {
1115                 url = Thread.currentThread().getContextClassLoader().getResource(groovyFile);
1116             }
1117             if (url != null) {
1118                 try {
1119 
1120                     /***
1121                      * todo there is no CompileUnit in scope so class name
1122                      * checking won't work but that mostly affects the bytecode
1123                      * generation rather than viewing the AST
1124                      */
1125 
1126                     CompilationUnit.ClassgenCallback search = new CompilationUnit.ClassgenCallback() {
1127                         public void call( ClassVisitor writer, ClassNode node ) {
1128                             if( node.getName().equals(theClass.getName()) ) {
1129                                 MetaClass.this.classNode = node;
1130                             }
1131                         }
1132                     };
1133                     
1134                     
1135                     CompilationUnit unit = new CompilationUnit( getClass().getClassLoader() );
1136                     unit.setClassgenCallback( search );
1137                     unit.addSource( url );
1138                     unit.compile( Phases.CLASS_GENERATION );
1139                 }
1140                 catch (Exception e) {
1141                     throw new GroovyRuntimeException("Exception thrown parsing: " + groovyFile + ". Reason: " + e, e);
1142                 }
1143             }
1144 
1145         }
1146         return classNode;
1147     }
1148 
1149     public String toString() {
1150         return super.toString() + "[" + theClass + "]";
1151     }
1152 
1153     // Implementation methods
1154     //-------------------------------------------------------------------------
1155 
1156     /***
1157      * Converts the given object into an array; if its an array then just cast
1158      * otherwise wrap it in an array
1159      */
1160     protected Object[] asArray(Object arguments) {
1161         if (arguments == null) {
1162             return EMPTY_ARRAY;
1163         }
1164         if (arguments instanceof Tuple) {
1165             Tuple tuple = (Tuple) arguments;
1166             return tuple.toArray();
1167         }
1168         if (arguments instanceof Object[]) {
1169             return (Object[]) arguments;
1170         }
1171         else {
1172             return new Object[] { arguments };
1173         }
1174     }
1175 	
1176     /***
1177      * @param listenerType
1178      *            the interface of the listener to proxy
1179      * @param listenerMethodName
1180      *            the name of the method in the listener API to call the
1181      *            closure on
1182      * @param closure
1183      *            the closure to invoke on the listenerMethodName method
1184      *            invocation
1185      * @return a dynamic proxy which calls the given closure on the given
1186      *         method name
1187      */
1188     protected Object createListenerProxy(Class listenerType, final String listenerMethodName, final Closure closure) {
1189         InvocationHandler handler = new ClosureListener(listenerMethodName, closure);
1190         return Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[] { listenerType }, handler);
1191     }
1192 
1193     /***
1194      * Adds all the methods declared in the given class to the metaclass
1195      * ignoring any matching methods already defined by a derived class
1196      * 
1197      * @param theClass
1198      */
1199     protected void addMethods(Class theClass) {
1200         Method[] methodArray = theClass.getDeclaredMethods();
1201         for (int i = 0; i < methodArray.length; i++) {
1202             Method reflectionMethod = methodArray[i];
1203             if ( reflectionMethod.getName().indexOf('+') >= 0 ) {
1204                 continue;
1205             }
1206             MetaMethod method = createMetaMethod(reflectionMethod);
1207             addMethod(method);
1208         }
1209     }
1210 
1211     protected void addMethod(MetaMethod method) {
1212         String name = method.getName();
1213 
1214         //System.out.println(theClass.getName() + " == " + name + Arrays.asList(method.getParameterTypes()));
1215 
1216         if (isGenericGetMethod(method) && genericGetMethod == null) {
1217             genericGetMethod = method;
1218         }
1219         else if (isGenericSetMethod(method) && genericSetMethod == null) {
1220             genericSetMethod = method;
1221         }
1222         if (method.isStatic()) {
1223             List list = (List) staticMethodIndex.get(name);
1224             if (list == null) {
1225                 list = new ArrayList();
1226                 staticMethodIndex.put(name, list);
1227                 list.add(method);
1228             }
1229             else {
1230                 if (!containsMatchingMethod(list, method)) {
1231                     list.add(method);
1232                 }
1233             }
1234         }
1235 
1236         List list = (List) methodIndex.get(name);
1237         if (list == null) {
1238             list = new ArrayList();
1239             methodIndex.put(name, list);
1240             list.add(method);
1241         }
1242         else {
1243             if (!containsMatchingMethod(list, method)) {
1244                 list.add(method);
1245             }
1246         }
1247     }
1248 
1249     /***
1250      * @return true if a method of the same matching prototype was found in the
1251      *         list
1252      */
1253     protected boolean containsMatchingMethod(List list, MetaMethod method) {
1254         for (Iterator iter = list.iterator(); iter.hasNext();) {
1255             MetaMethod aMethod = (MetaMethod) iter.next();
1256             Class[] params1 = aMethod.getParameterTypes();
1257             Class[] params2 = method.getParameterTypes();
1258             if (params1.length == params2.length) {
1259                 boolean matches = true;
1260                 for (int i = 0; i < params1.length; i++) {
1261                     if (params1[i] != params2[i]) {
1262                         matches = false;
1263                         break;
1264                     }
1265                 }
1266                 if (matches) {
1267                     return true;
1268                 }
1269             }
1270         }
1271         return false;
1272     }
1273 
1274     /***
1275      * Adds all of the newly defined methods from the given class to this
1276      * metaclass
1277      * 
1278      * @param theClass
1279      */
1280     protected void addNewStaticMethodsFrom(Class theClass) {
1281         MetaClass interfaceMetaClass = registry.getMetaClass(theClass);
1282         Iterator iter = interfaceMetaClass.newGroovyMethodsList.iterator();
1283         while (iter.hasNext()) {
1284             MetaMethod method = (MetaMethod) iter.next();
1285             addMethod(method);
1286             newGroovyMethodsList.add(method);
1287         }
1288     }
1289 
1290     /***
1291      * @return the value of the static property of the given class
1292      */
1293     protected Object getStaticProperty(Class aClass, String property) {
1294         //System.out.println("Invoking property: " + property + " on class: "
1295         // + aClass);
1296 
1297         Exception lastException = null;
1298         try {
1299             Field field = aClass.getField(property);
1300             if (field != null) {
1301                 if ((field.getModifiers() & Modifier.STATIC) != 0) {
1302                     return field.get(null);
1303                 }
1304             }
1305         }
1306         catch (Exception e) {
1307             lastException = e;
1308         }
1309 
1310         // lets try invoke a static getter method
1311         try {
1312             MetaMethod method = findStaticGetter(aClass, "get" + capitalize(property));
1313             if (method != null) {
1314                 return doMethodInvoke(aClass, method, EMPTY_ARRAY);
1315             }
1316         }
1317         catch (GroovyRuntimeException e) {
1318             throw new MissingPropertyException(property, aClass, e);
1319         }
1320 
1321         if (lastException == null) {
1322             throw new MissingPropertyException(property, aClass);
1323         }
1324         else {
1325             throw new MissingPropertyException(property, aClass, lastException);
1326         }
1327     }
1328 
1329     /***
1330      * @return the matching method which should be found
1331      */
1332     protected MetaMethod findMethod(Method aMethod) {
1333         List methods = getMethods(aMethod.getName());
1334         for (Iterator iter = methods.iterator(); iter.hasNext();) {
1335             MetaMethod method = (MetaMethod) iter.next();
1336             if (method.isMethod(aMethod)) {
1337                 return method;
1338             }
1339         }
1340         //log.warning("Creating reflection based dispatcher for: " + aMethod);
1341         return new ReflectionMetaMethod(aMethod);
1342     }
1343 
1344     /***
1345      * @return the getter method for the given object
1346      */
1347     protected MetaMethod findGetter(Object object, String name) {
1348         List methods = getMethods(name);
1349         for (Iterator iter = methods.iterator(); iter.hasNext();) {
1350             MetaMethod method = (MetaMethod) iter.next();
1351             if (method.getParameterTypes().length == 0) {
1352                 return method;
1353             }
1354         }
1355         return null;
1356     }
1357 
1358     /***
1359      * @return the Method of the given name with no parameters or null
1360      */
1361     protected MetaMethod findStaticGetter(Class type, String name) {
1362         List methods = getStaticMethods(name);
1363         for (Iterator iter = methods.iterator(); iter.hasNext();) {
1364             MetaMethod method = (MetaMethod) iter.next();
1365             if (method.getParameterTypes().length == 0) {
1366                 return method;
1367             }
1368         }
1369 
1370         /*** todo dirty hack - don't understand why this code is necessary - all methods should be in the allMethods list! */
1371         try {
1372             Method method = type.getMethod(name, EMPTY_TYPE_ARRAY);
1373             if ((method.getModifiers() & Modifier.STATIC) != 0) {
1374                 return findMethod(method);
1375             }
1376             else {
1377                 return null;
1378             }
1379         }
1380         catch (Exception e) {
1381             return null;
1382         }
1383     }
1384 
1385     protected Object doMethodInvoke(Object object, MetaMethod method, Object[] argumentArray) {
1386         //System.out.println("Evaluating method: " + method);
1387         //System.out.println("on object: " + object + " with arguments: " +
1388         // InvokerHelper.toString(argumentArray));
1389         //System.out.println(this.theClass);
1390 
1391         try {
1392             if (argumentArray == null) {
1393                 argumentArray = EMPTY_ARRAY;
1394             }
1395             else if (method.getParameterTypes().length == 1 && argumentArray.length == 0) {
1396                 argumentArray = ARRAY_WITH_NULL;
1397             }
1398             return method.invoke(object, argumentArray);
1399         }
1400         catch (ClassCastException e) {
1401             if (coerceGStrings(argumentArray)) {
1402                 try {
1403                     return doMethodInvoke(object, method, argumentArray);
1404                 }
1405                 catch (Exception e2) {
1406                     // allow fall through
1407                 }
1408             }
1409             throw new GroovyRuntimeException(
1410                 "failed to invoke method: "
1411                     + method
1412                     + " on: "
1413                     + object
1414                     + " with arguments: "
1415                     + InvokerHelper.toString(argumentArray)
1416                     + " reason: "
1417                     + e,
1418                 e);
1419         }
1420         catch (InvocationTargetException e) {
1421             Throwable t = e.getTargetException();
1422             if (t instanceof Error) {
1423                 Error error = (Error) t;
1424                 throw error;
1425             }
1426             if (t instanceof RuntimeException) {
1427                 RuntimeException runtimeEx = (RuntimeException) t;
1428                 throw runtimeEx;
1429             }
1430             throw new InvokerInvocationException(e);
1431         }
1432         catch (IllegalAccessException e) {
1433             throw new GroovyRuntimeException(
1434                 "could not access method: "
1435                     + method
1436                     + " on: "
1437                     + object
1438                     + " with arguments: "
1439                     + InvokerHelper.toString(argumentArray)
1440                     + " reason: "
1441                     + e,
1442                 e);
1443         }
1444         catch (IllegalArgumentException e) {
1445             if (coerceGStrings(argumentArray)) {
1446                 try {
1447                     return doMethodInvoke(object, method, argumentArray);
1448                 }
1449                 catch (Exception e2) {
1450                     // allow fall through
1451                 }
1452             }
1453             Object[] args = coerceNumbers(method, argumentArray);
1454             if (args != null) {
1455                 try {
1456                     return doMethodInvoke(object, method, args);
1457                 }
1458                 catch (Exception e3) {
1459                     // allow fall through
1460                 }
1461             }
1462 
1463             throw new GroovyRuntimeException(
1464                     "failed to invoke method: "
1465                     + method
1466                     + " on: "
1467                     + object
1468                     + " with arguments: "
1469                     + InvokerHelper.toString(argumentArray)
1470                     + " reason: "
1471                     + e,
1472                     e);
1473         }
1474         catch (RuntimeException e) {
1475             throw e;
1476         }
1477         catch (Exception e) {
1478             throw new GroovyRuntimeException(
1479                 "failed to invoke method: "
1480                     + method
1481                     + " on: "
1482                     + object
1483                     + " with arguments: "
1484                     + InvokerHelper.toString(argumentArray)
1485                     + " reason: "
1486                     + e,
1487                 e);
1488         }
1489     }
1490 
1491     private static Object[] coerceNumbers(MetaMethod method, Object[] arguments) {
1492         Object[] ans = null;
1493         boolean coerced = false; // to indicate that at least one param is coerced
1494 
1495         Class[] params = method.getParameterTypes();
1496 
1497         if (params.length != arguments.length) {
1498             return null;
1499         }
1500 
1501         ans = new Object[arguments.length];
1502 
1503         for (int i = 0, size = arguments.length; i < size; i++) {
1504             Object argument = arguments[i];
1505             Class param = params[i];
1506             if ((Number.class.isAssignableFrom(param) || param.isPrimitive()) && argument instanceof Number) { // Number types
1507                 if (param == Byte.class || param == Byte.TYPE ) {
1508                     ans[i] = new Byte(((Number)argument).byteValue());
1509                     coerced = true; continue;
1510                 }
1511                 if (param == Double.class || param == Double.TYPE) {
1512                     ans[i] = new Double(((Number)argument).doubleValue());
1513                     coerced = true; continue;
1514                 }
1515                 if (param == Float.class || param == Float.TYPE) {
1516                     ans[i] = new Float(((Number)argument).floatValue());
1517                     coerced = true; continue;
1518                 }
1519                 if (param == Integer.class || param == Integer.TYPE) {
1520                     ans[i] = new Integer(((Number)argument).intValue());
1521                     coerced = true; continue;
1522                 }
1523                 if (param == Long.class || param == Long.TYPE) {
1524                     ans[i] = new Long(((Number)argument).longValue());
1525                     coerced = true; continue;
1526                 }
1527                 if (param == Short.class || param == Short.TYPE) {
1528                     ans[i] = new Short(((Number)argument).shortValue());
1529                     coerced = true; continue;
1530                 }
1531                 if (param == BigDecimal.class ) {
1532                     ans[i] = new BigDecimal(((Number)argument).doubleValue());
1533                     coerced = true; continue;
1534                 }
1535                 if (param == BigInteger.class) {
1536                     ans[i] = new BigInteger(String.valueOf(((Number)argument).longValue()));
1537                     coerced = true; continue;
1538                 }
1539             }
1540             else if (param.isArray() && argument.getClass().isArray()) {
1541                 Class paramElem = param.getComponentType();
1542                 if (paramElem.isPrimitive()) {
1543                     if (paramElem == boolean.class && argument.getClass().getName().equals("[Ljava.lang.Boolean;")) {
1544                         ans[i] = InvokerHelper.convertToBooleanArray(argument);
1545                         coerced = true;
1546                         continue;
1547                     }
1548                     if (paramElem == byte.class && argument.getClass().getName().equals("[Ljava.lang.Byte;")) {
1549                         ans[i] = InvokerHelper.convertToByteArray(argument);
1550                         coerced = true;
1551                         continue;
1552                     }
1553                     if (paramElem == char.class && argument.getClass().getName().equals("[Ljava.lang.Character;")) {
1554                         ans[i] = InvokerHelper.convertToCharArray(argument);
1555                         coerced = true;
1556                         continue;
1557                     }
1558                     if (paramElem == short.class && argument.getClass().getName().equals("[Ljava.lang.Short;")) {
1559                         ans[i] = InvokerHelper.convertToShortArray(argument);
1560                         coerced = true;
1561                         continue;
1562                     }
1563                     if (paramElem == int.class && argument.getClass().getName().equals("[Ljava.lang.Integer;")) {
1564                         ans[i] = InvokerHelper.convertToIntArray(argument);
1565                         coerced = true;
1566                         continue;
1567                     }
1568                     if (paramElem == long.class
1569                             && argument.getClass().getName().equals("[Ljava.lang.Long;")
1570                             && argument.getClass().getName().equals("[Ljava.lang.Integer;")
1571                                                             ) {
1572                         ans[i] = InvokerHelper.convertToLongArray(argument);
1573                         coerced = true;
1574                         continue;
1575                     }
1576                     if (paramElem == float.class
1577                             && argument.getClass().getName().equals("[Ljava.lang.Float;")
1578                             && argument.getClass().getName().equals("[Ljava.lang.Integer;")
1579                                                             ) {
1580                         ans[i] = InvokerHelper.convertToFloatArray(argument);
1581                         coerced = true;
1582                         continue;
1583                     }
1584                     if (paramElem == double.class &&
1585                             argument.getClass().getName().equals("[Ljava.lang.Double;") &&
1586                             argument.getClass().getName().equals("[Ljava.lang.BigDecimal;") &&
1587                             argument.getClass().getName().equals("[Ljava.lang.Float;")) {
1588                         ans[i] = InvokerHelper.convertToDoubleArray(argument);
1589                         coerced = true;
1590                         continue;
1591                     }
1592                 }
1593             }
1594         }
1595         return coerced ? ans : null;
1596     }
1597 
1598     protected Object doConstructorInvoke(Constructor constructor, Object[] argumentArray) {
1599         //System.out.println("Evaluating constructor: " + constructor + " with
1600         // arguments: " + InvokerHelper.toString(argumentArray));
1601         //System.out.println(this.theClass);
1602 
1603         try {
1604 			// the following patch was provided by Mori Kouhei to fix JIRA 435
1605 			/* but it opens the ctor up to everyone, so it is no longer private!
1606             final Constructor ctor = constructor;
1607             AccessController.doPrivileged(new PrivilegedAction() {
1608                 public Object run() {
1609                     ctor.setAccessible(ctor.getDeclaringClass().equals(theClass));
1610                     return null;
1611                 }
1612             });
1613 			*/
1614 			// end of patch
1615 
1616             return constructor.newInstance(argumentArray);
1617         }
1618         catch (InvocationTargetException e) {
1619             Throwable t = e.getTargetException();
1620             if (t instanceof Error) {
1621                 Error error = (Error) t;
1622                 throw error;
1623             }
1624             if (t instanceof RuntimeException) {
1625                 RuntimeException runtimeEx = (RuntimeException) t;
1626                 throw runtimeEx;
1627             }
1628             throw new InvokerInvocationException(e);
1629         }
1630         catch (IllegalArgumentException e) {
1631             if (coerceGStrings(argumentArray)) {
1632                 try {
1633                     return constructor.newInstance(argumentArray);
1634                 }
1635                 catch (Exception e2) {
1636                     // allow fall through
1637                 }
1638             }
1639             throw new GroovyRuntimeException(
1640                 "failed to invoke constructor: "
1641                     + constructor
1642                     + " with arguments: "
1643                     + InvokerHelper.toString(argumentArray)
1644                     + " reason: "
1645                     + e,
1646                 e);
1647         }
1648         catch (IllegalAccessException e) {
1649             throw new GroovyRuntimeException(
1650                 "could not access constructor: "
1651                     + constructor
1652                     + " with arguments: "
1653                     + InvokerHelper.toString(argumentArray)
1654                     + " reason: "
1655                     + e,
1656                 e);
1657         }
1658         catch (Exception e) {
1659             throw new GroovyRuntimeException(
1660                 "failed to invoke constructor: "
1661                     + constructor
1662                     + " with arguments: "
1663                     + InvokerHelper.toString(argumentArray)
1664                     + " reason: "
1665                     + e,
1666                 e);
1667         }
1668     }
1669 
1670     /***
1671      * Chooses the correct method to use from a list of methods which match by
1672      * name.
1673      * 
1674      * @param methods
1675      *            the possible methods to choose from
1676      * @param arguments
1677      *            the original argument to the method
1678      * @return
1679      */
1680     protected Object chooseMethod(String methodName, List methods, Class[] arguments, boolean coerce) {
1681         int methodCount = methods.size();
1682         if (methodCount <= 0) {
1683             return null;
1684         }
1685         else if (methodCount == 1) {
1686             Object method = methods.get(0);
1687             if (isValidMethod(method, arguments, coerce)) {
1688                 return method;
1689             }
1690             return null;
1691         }
1692         Object answer = null;
1693         if (arguments == null || arguments.length == 0) {
1694             answer = chooseEmptyMethodParams(methods);
1695         }
1696         else if (arguments.length == 1 && arguments[0] == null) {
1697             answer = chooseMostGeneralMethodWith1Param(methods);
1698         }
1699         else {
1700             List matchingMethods = new ArrayList();
1701 
1702             for (Iterator iter = methods.iterator(); iter.hasNext();) {
1703                 Object method = iter.next();
1704                 Class[] paramTypes;
1705 
1706                 // making this false helps find matches
1707                 if (isValidMethod(method, arguments, coerce)) {
1708                     matchingMethods.add(method);
1709                 }
1710             }
1711             if (matchingMethods.isEmpty()) {
1712                 return null;
1713             }
1714             else if (matchingMethods.size() == 1) {
1715                 return matchingMethods.get(0);
1716             }
1717             return chooseMostSpecificParams(methodName, matchingMethods, arguments);
1718 
1719         }
1720         if (answer != null) {
1721             return answer;
1722         }
1723         throw new GroovyRuntimeException(
1724             "Could not find which method to invoke from this list: "
1725                 + methods
1726                 + " for arguments: "
1727                 + InvokerHelper.toString(arguments));
1728     }
1729 
1730     protected boolean isValidMethod(Object method, Class[] arguments, boolean includeCoerce) {
1731         Class[] paramTypes = getParameterTypes(method);
1732         return isValidMethod(paramTypes, arguments, includeCoerce);
1733     }
1734 
1735     public static boolean isValidMethod(Class[] paramTypes, Class[] arguments, boolean includeCoerce) {
1736         if (arguments == null) {
1737             return true;
1738         }
1739         int size = arguments.length;
1740         boolean validMethod = false;
1741         if (paramTypes.length == size) {
1742             // lets check the parameter types match
1743             validMethod = true;
1744             for (int i = 0; i < size; i++) {
1745                 if (!isCompatibleClass(paramTypes[i], arguments[i], includeCoerce)) {
1746                     validMethod = false;
1747                 }
1748             }
1749         }
1750         else {
1751             if (paramTypes.length == 1 && size == 0) {
1752                 return true;
1753             }
1754         }
1755         return validMethod;
1756     }
1757 
1758     protected Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) {
1759         Object answer = null;
1760         int size = arguments.length;
1761         Class[] mostSpecificTypes = null;
1762         for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) {
1763             Object method = iter.next();
1764             Class[] paramTypes = getParameterTypes(method);
1765             if (answer == null) {
1766                 answer = method;
1767                 mostSpecificTypes = paramTypes;
1768             }
1769             else {
1770                 boolean useThisMethod = false;
1771                 for (int i = 0; i < size; i++) {
1772                     Class mostSpecificType = mostSpecificTypes[i];
1773                     Class type = paramTypes[i];
1774 
1775                     if (!isAssignableFrom(mostSpecificType, type)) {
1776 
1777                         useThisMethod = true;
1778                         break;
1779                     }
1780 
1781                 }
1782                 if (useThisMethod) {
1783 
1784                     if (size > 1) {
1785                         checkForInvalidOverloading(name, mostSpecificTypes, paramTypes);
1786                     }
1787 
1788                     answer = method;
1789                     mostSpecificTypes = paramTypes;
1790                 }
1791             }
1792         }
1793         return answer;
1794     }
1795 
1796     /***
1797      * Checks that one of the parameter types is a superset of the other and
1798      * that the two lists of types don't conflict. e.g. foo(String, Object) and
1799      * foo(Object, String) would conflict if called with foo("a", "b").
1800      * 
1801      * Note that this method is only called with 2 possible signatures. i.e.
1802      * possible invalid combinations will already have been filtered out. So if
1803      * there were methods foo(String, Object) and foo(Object, String) then one
1804      * of these would be already filtered out if foo was called as foo(12, "a")
1805      */
1806     protected void checkForInvalidOverloading(String name, Class[] baseTypes, Class[] derivedTypes) {
1807         for (int i = 0, size = baseTypes.length; i < size; i++) {
1808             Class baseType = baseTypes[i];
1809             Class derivedType = derivedTypes[i];
1810             if (!isAssignableFrom(derivedType, baseType)) {
1811                 throw new GroovyRuntimeException(
1812                     "Ambiguous method overloading for method: "
1813                         + name
1814                         + ". Cannot resolve which method to invoke due to overlapping prototypes between: "
1815                         + InvokerHelper.toString(baseTypes)
1816                         + " and: "
1817                         + InvokerHelper.toString(derivedTypes));
1818             }
1819         }
1820     }
1821 
1822     protected Class[] getParameterTypes(Object methodOrConstructor) {
1823         if (methodOrConstructor instanceof MetaMethod) {
1824             MetaMethod method = (MetaMethod) methodOrConstructor;
1825             return method.getParameterTypes();
1826         }
1827         if (methodOrConstructor instanceof Method) {
1828             Method method = (Method) methodOrConstructor;
1829             return method.getParameterTypes();
1830         }
1831         if (methodOrConstructor instanceof Constructor) {
1832             Constructor constructor = (Constructor) methodOrConstructor;
1833             return constructor.getParameterTypes();
1834         }
1835         throw new IllegalArgumentException("Must be a Method or Constructor");
1836     }
1837 
1838     /***
1839      * @return the method with 1 parameter which takes the most general type of
1840      *         object (e.g. Object)
1841      */
1842     protected Object chooseMostGeneralMethodWith1Param(List methods) {
1843         // lets look for methods with 1 argument which matches the type of the
1844         // arguments
1845         Class closestClass = null;
1846         Object answer = null;
1847 
1848         for (Iterator iter = methods.iterator(); iter.hasNext();) {
1849             Object method = iter.next();
1850             Class[] paramTypes = getParameterTypes(method);
1851             int paramLength = paramTypes.length;
1852             if (paramLength == 1) {
1853                 Class theType = paramTypes[0];
1854                 if (closestClass == null || isAssignableFrom(closestClass, theType)) {
1855                     closestClass = theType;
1856                     answer = method;
1857                 }
1858             }
1859         }
1860         return answer;
1861     }
1862 
1863     /***
1864      * @return the method with 1 parameter which takes the most general type of
1865      *         object (e.g. Object)
1866      */
1867     protected Object chooseEmptyMethodParams(List methods) {
1868         for (Iterator iter = methods.iterator(); iter.hasNext();) {
1869             Object method = iter.next();
1870             Class[] paramTypes = getParameterTypes(method);
1871             int paramLength = paramTypes.length;
1872             if (paramLength == 0) {
1873                 return method;
1874             }
1875         }
1876         return null;
1877     }
1878 
1879     protected static boolean isCompatibleInstance(Class type, Object value, boolean includeCoerce) {
1880         boolean answer = value == null || type.isInstance(value);
1881         if (!answer) {
1882             if (type.isPrimitive()) {
1883                 if (type == int.class) {
1884                     return value instanceof Integer;
1885                 }
1886                 else if (type == double.class) {
1887                     return value instanceof Double || value instanceof Float || value instanceof Integer || value instanceof BigDecimal;
1888                 }
1889                 else if (type == boolean.class) {
1890                     return value instanceof Boolean;
1891                 }
1892                 else if (type == long.class) {
1893                     return value instanceof Long || value instanceof Integer;
1894                 }
1895                 else if (type == float.class) {
1896                     return value instanceof Float || value instanceof Integer;
1897                 }
1898                 else if (type == char.class) {
1899                     return value instanceof Character;
1900                 }
1901                 else if (type == byte.class) {
1902                     return value instanceof Byte;
1903                 }
1904                 else if (type == short.class) {
1905                     return value instanceof Short;
1906                 }
1907             }
1908             else if(type.isArray() && value.getClass().isArray()) {
1909                 return isCompatibleClass(type.getComponentType(), value.getClass().getComponentType(), false);
1910             }
1911             else if (includeCoerce) {
1912                 if (type == String.class && value instanceof GString) {
1913                     return true;
1914                 }
1915                 else if (value instanceof Number) {
1916                     // lets allow numbers to be coerced downwards?
1917                     return Number.class.isAssignableFrom(type);
1918                 }
1919             }
1920         }
1921         return answer;
1922     }
1923     protected static boolean isCompatibleClass(Class type, Class value, boolean includeCoerce) {
1924         boolean answer = value == null || type.isAssignableFrom(value); // this might have taken care of primitive types, rendering part of the following code unnecessary
1925         if (!answer) {
1926             if (type.isPrimitive()) {
1927                 if (type == int.class) {
1928                     return value == Integer.class;// || value == BigDecimal.class; //br added BigDecimal
1929                 }
1930                 else if (type == double.class) {
1931                     return value == Double.class || value == Float.class || value == Integer.class || value == BigDecimal.class;
1932                 }
1933                 else if (type == boolean.class) {
1934                     return value == Boolean.class;
1935                 }
1936                 else if (type == long.class) {
1937                     return value == Long.class || value == Integer.class; // || value == BigDecimal.class;//br added BigDecimal
1938                 }
1939                 else if (type == float.class) {
1940                     return value == Float.class || value == Integer.class; // || value == BigDecimal.class;//br added BigDecimal
1941                 }
1942                 else if (type == char.class) {
1943                     return value == Character.class;
1944                 }
1945                 else if (type == byte.class) {
1946                     return value == Byte.class;
1947                 }
1948                 else if (type == short.class) {
1949                     return value == Short.class;
1950                 }
1951             }
1952             else if(type.isArray() && value.isArray()) {
1953                 return isCompatibleClass(type.getComponentType(), value.getComponentType(), false);
1954             }
1955             else if (includeCoerce) {
1956 //if (type == String.class && value == GString.class) {
1957                 if (type == String.class && GString.class.isAssignableFrom(value)) {
1958                     return true;
1959                 }
1960                 else if (value == Number.class) {
1961                     // lets allow numbers to be coerced downwards?
1962                     return Number.class.isAssignableFrom(type);
1963                 }
1964             }
1965         }
1966         return answer;
1967     }
1968 
1969     protected boolean isAssignableFrom(Class mostSpecificType, Class type) {
1970         // let's handle primitives
1971         if (mostSpecificType.isPrimitive() && type.isPrimitive()) {
1972             if (mostSpecificType == type) {
1973                 return true;
1974             }
1975             else {  // note: there is not coercion for boolean and char. Range matters, precision doesn't
1976                 if (type == int.class) {
1977                     return
1978                             mostSpecificType == int.class
1979                             || mostSpecificType == short.class
1980                             || mostSpecificType == byte.class;
1981                 }
1982                 else if (type == double.class) {
1983                     return
1984                             mostSpecificType == double.class
1985                             || mostSpecificType == int.class
1986                             || mostSpecificType == long.class
1987                             || mostSpecificType == short.class
1988                             || mostSpecificType == byte.class
1989                             || mostSpecificType == float.class;
1990                 }
1991                 else if (type == long.class) {
1992                     return
1993                             mostSpecificType == long.class
1994                             || mostSpecificType == int.class
1995                             || mostSpecificType == short.class
1996                             || mostSpecificType == byte.class;
1997                 }
1998                 else if (type == float.class) {
1999                     return
2000                             mostSpecificType == float.class
2001                             || mostSpecificType == int.class
2002                             || mostSpecificType == long.class
2003                             || mostSpecificType == short.class
2004                             || mostSpecificType == byte.class;
2005                 }
2006                 else if (type == short.class) {
2007                     return
2008                             mostSpecificType == short.class
2009                             || mostSpecificType == byte.class;
2010                 }
2011                 else {
2012                     return false;
2013                 }
2014             }
2015         }
2016 
2017         boolean answer = type.isAssignableFrom(mostSpecificType);
2018         if (!answer) {
2019             answer = autoboxType(type).isAssignableFrom(autoboxType(mostSpecificType));
2020         }
2021         return answer;
2022     }
2023 
2024     private Class autoboxType(Class type) {
2025         if (type.isPrimitive()) {
2026             if (type == int.class) {
2027                 return Integer.class;
2028             }
2029             else if (type == double.class) {
2030                 return Double.class;
2031             }
2032             else if (type == long.class) {
2033                 return Long.class;
2034             }
2035             else if (type == boolean.class) {
2036                 return Boolean.class;
2037             }
2038             else if (type == float.class) {
2039                 return Float.class;
2040             }
2041             else if (type == char.class) {
2042                 return Character.class;
2043             }
2044             else if (type == byte.class) {
2045                 return Byte.class;
2046             }
2047             else if (type == short.class) {
2048                 return Short.class;
2049             }
2050         }
2051         return type;
2052     }
2053 
2054     /***
2055      * Coerces any GString instances into Strings
2056      * 
2057      * @return true if some coercion was done. 
2058      */
2059     protected static boolean coerceGStrings(Object[] arguments) {
2060         boolean coerced = false;
2061         for (int i = 0, size = arguments.length; i < size; i++) {
2062             Object argument = arguments[i];
2063             if (argument instanceof GString) {
2064                 arguments[i] = argument.toString();
2065                 coerced = true;
2066             }
2067         }
2068         return coerced;
2069     }
2070 
2071     protected boolean isGenericSetMethod(MetaMethod method) {
2072         return (method.getName().equals("set"))
2073             && method.getParameterTypes().length == 2;
2074     }
2075 
2076     protected boolean isGenericGetMethod(MetaMethod method) {
2077         if (method.getName().equals("get")) {
2078             Class[] parameterTypes = method.getParameterTypes();
2079             return parameterTypes.length == 1 && parameterTypes[0] == String.class;
2080         }
2081         return false;
2082     }
2083 
2084     private void registerMethods(boolean instanceMethods) {
2085         Method[] methods = theClass.getMethods();
2086         for (int i = 0; i < methods.length; i++) {
2087             Method method = methods[i];
2088             if (MethodHelper.isStatic(method)) {
2089                 Class[] paramTypes = method.getParameterTypes();
2090                 if (paramTypes.length > 0) {
2091                     Class owner = paramTypes[0];
2092                     if (instanceMethods) {
2093                         registry.lookup(owner).addNewInstanceMethod(method);
2094                     } else {
2095                         registry.lookup(owner).addNewStaticMethod(method);
2096                     }
2097                 }
2098             }
2099         }
2100     }
2101 
2102     protected void registerStaticMethods() {
2103         registerMethods(false);
2104     }
2105 
2106     protected void registerInstanceMethods() {
2107         registerMethods(true);
2108     }
2109 
2110     protected String capitalize(String property) {
2111         return property.substring(0, 1).toUpperCase() + property.substring(1, property.length());
2112     }
2113 
2114     /***
2115      * Call this method when any mutation method is called, such as adding a new
2116      * method to this MetaClass so that any caching or bytecode generation can be
2117      * regenerated.
2118      */
2119     protected synchronized void onMethodChange() {
2120         reflector = null;
2121     }
2122     
2123     protected synchronized void checkInitialised() {
2124         if (!initialised) {
2125             initialised = true;
2126             addInheritedMethods(theClass);
2127         }
2128         if (reflector == null) {
2129             generateReflector();
2130         }
2131     }
2132 
2133     protected MetaMethod createMetaMethod(final Method method) {
2134         if (registry.useAccessible()) {
2135             AccessController.doPrivileged(new PrivilegedAction() {
2136                 public Object run() {
2137                     method.setAccessible(true);
2138                     return null;
2139                 }
2140             });
2141         }
2142         if (useReflection) {
2143             //log.warning("Creating reflection based dispatcher for: " + method);
2144             return new ReflectionMetaMethod(method);
2145         }
2146         MetaMethod answer = new MetaMethod(method);
2147         if (isValidReflectorMethod(answer)) {
2148             allMethods.add(answer);
2149             answer.setMethodIndex(allMethods.size());
2150         }
2151         else {
2152             //log.warning("Creating reflection based dispatcher for: " + method);
2153             answer = new ReflectionMetaMethod(method);
2154         }
2155         return answer;
2156     }
2157 
2158     protected boolean isValidReflectorMethod(MetaMethod method) {
2159         // We cannot use a reflector if the method is private, protected, or package accessible only.
2160         if (!method.isPublic()) {
2161             return false;
2162         }
2163         Class declaringClass = method.getDeclaringClass();
2164         if (!Modifier.isPublic(declaringClass.getModifiers())) {
2165             // lets see if this method is implemented on an interface
2166             List list = getInterfaceMethods();
2167             for (Iterator iter = list.iterator(); iter.hasNext();) {
2168                 MetaMethod aMethod = (MetaMethod) iter.next();
2169                 if (method.isSame(aMethod)) {
2170                     method.setInterfaceClass(aMethod.getDeclaringClass());
2171                     return true;
2172                 }
2173             }
2174             /*** todo */
2175             //log.warning("Cannot invoke method on protected/private class which isn't visible on an interface so must use reflection instead: " + method);
2176             return false;
2177         }
2178         return true;
2179     }
2180 
2181     protected void generateReflector() {
2182         reflector = loadReflector(allMethods);
2183         if (reflector == null) {
2184             throw new RuntimeException("Should have a reflector!");
2185         }
2186         // lets set the reflector on all the methods
2187         for (Iterator iter = allMethods.iterator(); iter.hasNext();) {
2188             MetaMethod metaMethod = (MetaMethod) iter.next();
2189             //System.out.println("Setting reflector for method: " + metaMethod + " with index: " + metaMethod.getMethodIndex());
2190             metaMethod.setReflector(reflector);
2191         }
2192     }
2193 
2194     protected Reflector loadReflector(List methods) {
2195         ReflectorGenerator generator = new ReflectorGenerator(methods);
2196         String className = theClass.getName();
2197         String packagePrefix = "gjdk.";
2198         /*
2199         if (className.startsWith("java.")) {
2200             packagePrefix = "gjdk.";
2201         }
2202         */
2203         String name = packagePrefix + className + "_GroovyReflector";
2204         if (theClass.isArray()) {
2205             String componentName = theClass.getComponentType().getName();
2206             /*
2207             if (componentName.startsWith("java.")) {
2208                 packagePrefix = "gjdk.";
2209             }
2210             */
2211             name = packagePrefix + componentName + "_GroovyReflectorArray";
2212         }
2213         // lets see if its already loaded
2214         try {
2215             Class type = loadReflectorClass(name);
2216             return (Reflector) type.newInstance();
2217         }
2218         catch (AccessControlException ace) {
2219             //Don't ignore this exception type
2220             throw ace;
2221         } 
2222         catch (Exception e) {
2223             // lets ignore, lets generate it && load it
2224         }
2225 
2226         ClassWriter cw = new ClassWriter(true);
2227         generator.generate(cw, name);
2228 
2229         byte[] bytecode = cw.toByteArray();
2230 
2231         try {
2232             Class type = loadReflectorClass(name, bytecode);
2233             return (Reflector) type.newInstance();
2234         }
2235         catch (Exception e) {
2236             throw new GroovyRuntimeException("Could not load the reflector for class: " + name + ". Reason: " + e, e);
2237         }
2238     }
2239 
2240     protected Class loadReflectorClass(final String name, final byte[] bytecode) throws ClassNotFoundException {
2241         ClassLoader loader = theClass.getClassLoader();
2242         if (loader instanceof GroovyClassLoader) {
2243             final GroovyClassLoader gloader = (GroovyClassLoader) loader;
2244             return (Class) AccessController.doPrivileged(new PrivilegedAction() {
2245                 public Object run() {
2246                     return gloader.defineClass(name, bytecode, getClass().getProtectionDomain());
2247                 }
2248             });
2249         }
2250         return registry.loadClass(name, bytecode);
2251     }
2252 
2253     protected Class loadReflectorClass(String name) throws ClassNotFoundException {
2254         ClassLoader loader = theClass.getClassLoader();
2255         if (loader instanceof GroovyClassLoader) {
2256             GroovyClassLoader gloader = (GroovyClassLoader) loader;
2257             return gloader.loadClass(name);
2258         }
2259         return registry.loadClass(name);
2260     }
2261 
2262     public List getMethods() {
2263         return allMethods;
2264     }
2265 
2266     public List getMetaMethods() {
2267         return (List) ((ArrayList)newGroovyMethodsList).clone();
2268     }
2269 
2270     protected synchronized List getInterfaceMethods() {
2271         if (interfaceMethods == null) {
2272             interfaceMethods = new ArrayList();
2273             Class type = theClass;
2274             while (type != null) {
2275                 Class[] interfaces = type.getInterfaces();
2276                 for (int i = 0; i < interfaces.length; i++) {
2277                     Class iface = interfaces[i];
2278                     Method[] methods = iface.getMethods();
2279                     addInterfaceMethods(interfaceMethods, methods);
2280                 }
2281                 type = type.getSuperclass();
2282             }
2283         }
2284         return interfaceMethods;
2285     }
2286 
2287     private void addInterfaceMethods(List list, Method[] methods) {
2288         for (int i = 0; i < methods.length; i++) {
2289             list.add(createMetaMethod(methods[i]));
2290         }
2291     }
2292 
2293     /***
2294      * param instance array to the type array
2295      * @param args
2296      * @return
2297      */
2298     Class[] convertToTypeArray(Object[] args) {
2299         if (args == null)
2300             return null;
2301         int s = args.length;
2302         Class[] ans = new Class[s];
2303         for (int i = 0; i < s; i++) {
2304             Object o = args[i];
2305             if (o != null) {
2306                 ans[i] = o.getClass();
2307             } else {
2308                 ans[i] = null;
2309             }
2310         }
2311         return ans;
2312     }
2313 }