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