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