View Javadoc

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