View Javadoc

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