View Javadoc

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