View Javadoc

1   /*
2   $Id: MetaClassImpl.java,v 1.9 2005/11/13 16:42:09 blackdrag Exp $
3   
4   Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6   Redistribution and use of this software and associated documentation
7   ("Software"), with or without modification, are permitted provided
8   that the following conditions are met:
9   
10  1. Redistributions of source code must retain copyright
11     statements and notices.  Redistributions must also contain a
12     copy of this document.
13  
14  2. Redistributions in binary form must reproduce the
15     above copyright notice, this list of conditions and the
16     following disclaimer in the documentation and/or other
17     materials provided with the distribution.
18  
19  3. The name "groovy" must not be used to endorse or promote
20     products derived from this Software without prior written
21     permission of The Codehaus.  For written permission,
22     please contact info@codehaus.org.
23  
24  4. Products derived from this Software may not be called "groovy"
25     nor may "groovy" appear in their names without prior written
26     permission of The Codehaus. "groovy" is a registered
27     trademark of The Codehaus.
28  
29  5. Due credit should be given to The Codehaus -
30     http://groovy.codehaus.org/
31  
32  THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33  ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36  THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43  OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45  */
46  package groovy.lang;
47  
48  import java.beans.BeanInfo;
49  import java.beans.EventSetDescriptor;
50  import java.beans.IntrospectionException;
51  import java.beans.Introspector;
52  import java.beans.PropertyDescriptor;
53  import java.lang.reflect.Array;
54  import java.lang.reflect.Constructor;
55  import java.lang.reflect.Field;
56  import java.lang.reflect.InvocationTargetException;
57  import java.lang.reflect.Method;
58  import java.lang.reflect.Modifier;
59  import java.net.URL;
60  import java.security.AccessController;
61  import java.security.PrivilegedAction;
62  import java.security.PrivilegedActionException;
63  import java.security.PrivilegedExceptionAction;
64  import java.util.ArrayList;
65  import java.util.Arrays;
66  import java.util.Collection;
67  import java.util.Collections;
68  import java.util.HashMap;
69  import java.util.Iterator;
70  import java.util.LinkedList;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.logging.Level;
74  
75  import org.codehaus.groovy.ast.ClassNode;
76  import org.codehaus.groovy.classgen.ReflectorGenerator;
77  import org.codehaus.groovy.control.CompilationUnit;
78  import org.codehaus.groovy.control.CompilerConfiguration;
79  import org.codehaus.groovy.control.Phases;
80  import org.codehaus.groovy.runtime.CurriedClosure;
81  import org.codehaus.groovy.runtime.DefaultGroovyMethods;
82  import org.codehaus.groovy.runtime.GroovyCategorySupport;
83  import org.codehaus.groovy.runtime.InvokerHelper;
84  import org.codehaus.groovy.runtime.InvokerInvocationException;
85  import org.codehaus.groovy.runtime.MetaClassHelper;
86  import org.codehaus.groovy.runtime.MethodClosure;
87  import org.codehaus.groovy.runtime.MethodKey;
88  import org.codehaus.groovy.runtime.NewInstanceMetaMethod;
89  import org.codehaus.groovy.runtime.NewStaticMetaMethod;
90  import org.codehaus.groovy.runtime.ReflectionMetaMethod;
91  import org.codehaus.groovy.runtime.Reflector;
92  import org.codehaus.groovy.runtime.TemporaryMethodKey;
93  import org.codehaus.groovy.runtime.TransformMetaMethod;
94  import org.objectweb.asm.ClassVisitor;
95  import org.objectweb.asm.ClassWriter;
96  
97  /***
98  * Allows methods to be dynamically added to existing classes at runtime
99  *
100 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
101 * @author Guillaume Laforge
102 * @author Jochen Theodorou
103 * @version $Revision: 1.9 $
104 */
105 public class MetaClassImpl extends MetaClass {
106 
107    protected MetaClassRegistry registry;
108    private ClassNode classNode;
109    private Map methodIndex = new HashMap();
110    private Map staticMethodIndex = new HashMap();
111    //private Map propertyDescriptors = Collections.synchronizedMap(new HashMap());
112    private Map propertyMap = Collections.synchronizedMap(new HashMap());
113    private Map listeners = new HashMap();
114    private Map methodCache = Collections.synchronizedMap(new HashMap());
115    private Map staticMethodCache = Collections.synchronizedMap(new HashMap());
116    private MetaMethod genericGetMethod;
117    private MetaMethod genericSetMethod;
118    private List constructors;
119    private List allMethods = new ArrayList();
120    private List interfaceMethods;
121    private Reflector reflector;
122    private boolean initialised;
123    // we only need one of these that can be reused over and over.
124    private MetaProperty arrayLengthProperty = new MetaArrayLengthProperty();
125 
126    public MetaClassImpl(MetaClassRegistry registry, final Class theClass) throws IntrospectionException {
127        super(theClass);
128        this.registry = registry;
129 
130        constructors = (List) AccessController.doPrivileged(new  PrivilegedAction() {
131                public Object run() {
132                    return Arrays.asList (theClass.getDeclaredConstructors());
133                }
134            });
135 
136        addMethods(theClass,true);
137 
138        // introspect
139        BeanInfo info = null;
140        try {
141            info =(BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() {
142                public Object run() throws IntrospectionException {
143                    return Introspector.getBeanInfo(theClass);
144                }
145            });
146        } catch (PrivilegedActionException pae) {
147            if (pae.getException() instanceof IntrospectionException) {
148                throw (IntrospectionException) pae.getException();
149            } else {
150                throw new RuntimeException(pae.getException());
151            }
152        }
153 
154        PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
155 
156        // build up the metaproperties based on the public fields, property descriptors,
157        // and the getters and setters
158        setupProperties(descriptors);
159 
160        /* old code
161        for (int i = 0; i < descriptors.length; i++) {
162            PropertyDescriptor descriptor = descriptors[i];
163            propertyDescriptors.put(descriptor.getName(), descriptor);
164        }
165        */
166 
167        EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors();
168        for (int i = 0; i < eventDescriptors.length; i++) {
169            EventSetDescriptor descriptor = eventDescriptors[i];
170            Method[] listenerMethods = descriptor.getListenerMethods();
171            for (int j = 0; j < listenerMethods.length; j++) {
172                Method listenerMethod = listenerMethods[j];
173                MetaMethod metaMethod = createMetaMethod(descriptor.getAddListenerMethod());
174                listeners.put(listenerMethod.getName(), metaMethod);
175            }
176        }
177    }
178 
179    private void addInheritedMethods() {
180        LinkedList superClasses = new LinkedList();
181        for (Class c = theClass.getSuperclass(); c!=Object.class && c!= null; c = c.getSuperclass()) {
182            superClasses.addFirst(c);
183        }
184        // lets add all the base class methods
185        for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
186            Class c = (Class) iter.next();
187            addMethods(c,true);
188            addNewStaticMethodsFrom(c);
189        }
190 
191        // now lets see if there are any methods on one of my interfaces
192        Class[] interfaces = theClass.getInterfaces();
193        for (int i = 0; i < interfaces.length; i++) {
194            addNewStaticMethodsFrom(interfaces[i]);
195        }
196 
197        // lets add Object methods after interfaces, as all interfaces derive from Object.
198        // this ensures List and Collection methods come before Object etc
199        if (theClass != Object.class) {
200            addMethods(Object.class, false);
201            addNewStaticMethodsFrom(Object.class);
202        }
203 
204        if (theClass.isArray() && !theClass.equals(Object[].class)) {
205            addNewStaticMethodsFrom(Object[].class);
206        }
207    }
208 
209    /***
210     * @return all the normal instance methods avaiable on this class for the
211     *         given name
212     */
213    private List getMethods(String name) {
214        List answer = (List) methodIndex.get(name);
215        List used = GroovyCategorySupport.getCategoryMethods(theClass, name);
216        if (used != null) {
217            if (answer != null) {
218                used.addAll(answer);
219            }
220            answer = used;
221        }
222        if (answer == null) {
223            answer = Collections.EMPTY_LIST;
224        }
225        return answer;
226    }
227 
228    /***
229     * @return all the normal static methods avaiable on this class for the
230     *         given name
231     */
232    private List getStaticMethods(String name) {
233        List answer = (List) staticMethodIndex.get(name);
234        if (answer == null) {
235            return Collections.EMPTY_LIST;
236        }
237        return answer;
238    }
239 
240    /***
241     * Allows static method definitions to be added to a meta class as if it
242     * was an instance method
243     *
244     * @param method
245     */
246    protected void addNewInstanceMethod(Method method) {
247        if (initialised) {
248            throw new RuntimeException("Already initialized, cannot add new method: " + method);
249        }
250        else {
251            NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(createMetaMethod(method));
252            if (! newGroovyMethodsList.contains(newMethod)){
253                newGroovyMethodsList.add(newMethod);
254                addMethod(newMethod,false);
255            }
256        }
257    }
258 
259    protected void addNewStaticMethod(Method method) {
260        if (initialised) {
261            throw new RuntimeException("Already initialized, cannot add new method: " + method);
262        }
263        else {
264            NewStaticMetaMethod newMethod = new NewStaticMetaMethod(createMetaMethod(method));
265            if (! newGroovyMethodsList.contains(newMethod)){
266                newGroovyMethodsList.add(newMethod);
267                addMethod(newMethod,false);
268            }
269        }
270    }
271 
272    /***
273     * Invokes the given method on the object.
274     *
275     */
276    public Object invokeMethod(Object object, String methodName, Object[] arguments) {
277        if (object == null) {
278            throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
279        }
280        if (log.isLoggable(Level.FINER)){
281            MetaClassHelper.logMethodCall(object, methodName, arguments);
282        }
283 
284        MetaMethod method = retrieveMethod(object, methodName, arguments);
285 
286        boolean isClosure = object instanceof Closure;
287        if (isClosure) {
288            Closure closure = (Closure) object;
289            Object delegate = closure.getDelegate();
290            Object owner = closure.getOwner();
291 
292            if ("call".equals(methodName) || "doCall".equals(methodName)) {
293                if (object.getClass()==MethodClosure.class) {
294                    MethodClosure mc = (MethodClosure) object;
295                    methodName = mc.getMethod();
296                    MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass());
297                    return ownerMetaClass.invokeMethod(owner,methodName,arguments);
298                } else if (object.getClass()==CurriedClosure.class) {
299                    CurriedClosure cc = (CurriedClosure) object;
300                    // change the arguments for an uncurried call
301                    arguments = cc.getUncurriedArguments(arguments);
302                    MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass());
303                    return ownerMetaClass.invokeMethod(owner,methodName,arguments);
304                }
305            } else if ("curry".equals(methodName)) {
306                return closure.curry(arguments);
307            }
308 
309            if (method==null && owner!=closure) {
310                MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass());
311                method = ownerMetaClass.retrieveMethod(owner,methodName,arguments);
312                if (method!=null) return ownerMetaClass.invokeMethod(owner,methodName,arguments);
313            }
314            if (method==null && delegate!=closure && delegate!=null) {
315                MetaClass delegateMetaClass = registry.getMetaClass(delegate.getClass());
316                method = delegateMetaClass.retrieveMethod(delegate,methodName,arguments);
317                if (method!=null) return delegateMetaClass.invokeMethod(delegate,methodName,arguments);
318            }
319            if (method==null) {
320                // still no methods found, test if delegate or owner are GroovyObjects
321                // and invoke the method on them if so.
322                MissingMethodException last = null;
323                if (delegate!=closure && (delegate instanceof GroovyObject)) {
324                    try {
325                        GroovyObject go = (GroovyObject) delegate;
326                        return go.invokeMethod(methodName,arguments);
327                    } catch (MissingMethodException mme) {
328                        last = mme;
329                    }
330                }
331                if (owner!=closure && (owner instanceof GroovyObject)) {
332                    try {
333                        GroovyObject go = (GroovyObject) owner;
334                        return go.invokeMethod(methodName,arguments);
335                    } catch (MissingMethodException mme) {
336                        if (last==null) last = mme;
337                    }
338                }
339                if (last!=null) throw last;
340            }
341 
342        }
343 
344        if (method != null) {
345            return MetaClassHelper.doMethodInvoke(object, method, arguments);
346        } else {
347            // if no method was found, try to find a closure defined as a field of the class and run it
348            try {
349                Object value = this.getProperty(object, methodName);
350                if (value instanceof Closure) {  // This test ensures that value != this If you ever change this ensure that value != this
351                    Closure closure = (Closure) value;
352                    MetaClass delegateMetaClass = registry.getMetaClass(closure.getClass());
353                    return delegateMetaClass.invokeMethod(closure,"doCall",arguments);
354                }
355            } catch (MissingPropertyException mpe) {}
356 
357            throw new MissingMethodException(methodName, theClass, arguments);
358        }
359    }
360 
361    public MetaMethod retrieveMethod(Object owner, String methodName, Object[] arguments) {
362        // lets try use the cache to find the method
363        MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
364        MetaMethod method = (MetaMethod) methodCache.get(methodKey);
365        if (method == null) {
366            method = pickMethod(owner, methodName, arguments);
367            if (method != null && method.isCacheable()) {
368                methodCache.put(methodKey.createCopy(), method);
369            }
370        }
371        return method;
372    }
373 
374    public MetaMethod retrieveMethod(String methodName, Class[] arguments) {
375        // lets try use the cache to find the method
376        MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
377        MetaMethod method = (MetaMethod) methodCache.get(methodKey);
378        if (method == null) {
379            method = pickMethod(methodName, arguments); // todo shall call pickStaticMethod also?
380            if (method != null && method.isCacheable()) {
381                methodCache.put(methodKey.createCopy(), method);
382            }
383        }
384        return method;
385    }
386 
387    public Constructor retrieveConstructor(Class[] arguments) {
388        Constructor constructor = (Constructor) chooseMethod("<init>", constructors, arguments, false);
389        if (constructor != null) {
390            return constructor;
391        }
392        else {
393            constructor = (Constructor) chooseMethod("<init>", constructors, arguments, true);
394            if (constructor != null) {
395                return constructor;
396            }
397        }
398        return null;
399    }
400 
401    public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) {
402        MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
403        MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
404        if (method == null) {
405            method = pickStaticMethod(methodName, arguments);
406            if (method != null) {
407                staticMethodCache.put(methodKey.createCopy(), method);
408            }
409        }
410        return method;
411    }
412    /***
413     * Picks which method to invoke for the given object, method name and arguments
414     */
415    protected MetaMethod pickMethod(Object object, String methodName, Object[] arguments) {
416        MetaMethod method = null;
417        List methods = getMethods(methodName);
418        if (!methods.isEmpty()) {
419            Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
420            method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true);
421            if (method == null) {
422                int size = (arguments != null) ? arguments.length : 0;
423                if (size == 1) {
424                    Object firstArgument = arguments[0];
425                    if (firstArgument instanceof List) {
426                        // lets coerce the list arguments into an array of
427                        // arguments
428                        // e.g. calling JFrame.setLocation( [100, 100] )
429 
430                        List list = (List) firstArgument;
431                        arguments = list.toArray();
432                        argClasses = MetaClassHelper.convertToTypeArray(arguments);
433                        method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true);
434                        if (method==null) return null;
435                            return new TransformMetaMethod(method) {
436                                public Object invoke(Object object, Object[] arguments) throws Exception {
437                                    Object firstArgument = arguments[0];
438                                    List list = (List) firstArgument;
439                                    arguments = list.toArray();
440                                    return super.invoke(object, arguments);
441                                }
442                            };
443                    }
444                }
445            }
446        }
447        return method;
448    }
449 
450    /***
451     * pick a method in a strict manner, i.e., without reinterpreting the first List argument.
452     * this method is used only by ClassGenerator for static binding
453     * @param methodName
454     * @param arguments
455     * @return
456     */
457    protected MetaMethod pickMethod(String methodName, Class[] arguments) {
458        MetaMethod method = null;
459        List methods = getMethods(methodName);
460        if (!methods.isEmpty()) {
461            method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
462 //no coersion at classgen time.
463 //           if (method == null) {
464 //               method = (MetaMethod) chooseMethod(methodName, methods, arguments, true);
465 //           }
466        }
467        return method;
468    }
469 
470    public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
471        if (log.isLoggable(Level.FINER)){
472            MetaClassHelper.logMethodCall(object, methodName, arguments);
473        }
474        // lets try use the cache to find the method
475        MethodKey methodKey = new TemporaryMethodKey(methodName, arguments);
476        MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
477        if (method == null) {
478            method = pickStaticMethod(object, methodName, arguments);
479            if (method != null) {
480                staticMethodCache.put(methodKey.createCopy(), method);
481            }
482        }
483 
484        if (method != null) {
485            return MetaClassHelper.doMethodInvoke(object, method, arguments);
486        }
487        /*
488        List methods = getStaticMethods(methodName);
489 
490        if (!methods.isEmpty()) {
491            MetaMethod method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
492            if (method != null) {
493                return doMethodInvoke(theClass, method, arguments);
494            }
495        }
496 
497        if (theClass != Class.class) {
498            try {
499                return registry.getMetaClass(Class.class).invokeMethod(object, methodName, arguments);
500            }
501            catch (GroovyRuntimeException e) {
502                // throw our own exception
503            }
504        }
505        */
506        throw new MissingMethodException(methodName, theClass, arguments);
507    }
508 
509    private MetaMethod pickStaticMethod(Object object, String methodName, Object[] arguments) {
510        MetaMethod method = null;
511        List methods = getStaticMethods(methodName);
512 
513        if (!methods.isEmpty()) {
514            method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments), false);
515        }
516 
517        if (method == null && theClass != Class.class) {
518            MetaClass classMetaClass = registry.getMetaClass(Class.class);
519            method = classMetaClass.pickMethod(object, methodName, arguments);
520        }
521        if (method == null) {
522            method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments), true);
523        }
524        return method;
525    }
526 
527    private MetaMethod pickStaticMethod(String methodName, Class[] arguments) {
528        MetaMethod method = null;
529        List methods = getStaticMethods(methodName);
530 
531        if (!methods.isEmpty()) {
532            method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
533 //disabled to keep consistant with the original version of pickStatciMethod
534 //           if (method == null) {
535 //               method = (MetaMethod) chooseMethod(methodName, methods, arguments, true);
536 //           }
537        }
538 
539        if (method == null && theClass != Class.class) {
540            MetaClass classMetaClass = registry.getMetaClass(Class.class);
541            method = classMetaClass.pickMethod(methodName, arguments);
542        }
543        return method;
544    }
545 
546    public Object invokeConstructor(Object[] arguments) {
547        Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
548        Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false);
549        if (constructor != null) {
550            return MetaClassHelper.doConstructorInvoke(constructor, arguments);
551        }
552        else {
553            constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true);
554            if (constructor != null) {
555                return MetaClassHelper.doConstructorInvoke(constructor, arguments);
556            }
557        }
558 
559        if (arguments.length == 1) {
560            Object firstArgument = arguments[0];
561            if (firstArgument instanceof Map) {
562                constructor = (Constructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false);
563                if (constructor != null) {
564                    Object bean = MetaClassHelper.doConstructorInvoke(constructor, MetaClassHelper.EMPTY_ARRAY);
565                    setProperties(bean, ((Map) firstArgument));
566                    return bean;
567                }
568            }
569        }
570        throw new GroovyRuntimeException(
571                    "Could not find matching constructor for: "
572                        + theClass.getName()
573                        + "("+InvokerHelper.toTypeString(arguments)+")");
574    }
575 
576    public Object invokeConstructorAt(Class at, Object[] arguments) {
577        Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
578        Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false);
579        if (constructor != null) {
580            return doConstructorInvokeAt(at, constructor, arguments);
581        }
582        else {
583            constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true);
584            if (constructor != null) {
585                return doConstructorInvokeAt(at, constructor, arguments);
586            }
587        }
588 
589        if (arguments.length == 1) {
590            Object firstArgument = arguments[0];
591            if (firstArgument instanceof Map) {
592                constructor = (Constructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false);
593                if (constructor != null) {
594                    Object bean = doConstructorInvokeAt(at, constructor, MetaClassHelper.EMPTY_ARRAY);
595                    setProperties(bean, ((Map) firstArgument));
596                    return bean;
597                }
598            }
599        }
600        throw new GroovyRuntimeException(
601                    "Could not find matching constructor for: "
602                        + theClass.getName()
603                        + "("+InvokerHelper.toTypeString(arguments)+")");
604    }
605 
606    /***
607     * Sets a number of bean properties from the given Map where the keys are
608     * the String names of properties and the values are the values of the
609     * properties to set
610     */
611    public void setProperties(Object bean, Map map) {
612        for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
613            Map.Entry entry = (Map.Entry) iter.next();
614            String key = entry.getKey().toString();
615 
616            // do we have this property?
617            if(propertyMap.get(key) == null)
618                continue;
619 
620            Object value = entry.getValue();
621            try {
622                setProperty(bean, key, value);
623            }
624            catch (GroovyRuntimeException e) {
625                // lets ignore missing properties
626                /*** todo should replace this code with a getMetaProperty(key) != null check
627                 i.e. don't try and set a non-existent property
628                 */
629            }
630        }
631    }
632 
633    /***
634     * @return the given property's value on the object
635     */
636    public Object getProperty(final Object object, final String property) {
637        // look for the property in our map
638        MetaProperty mp = (MetaProperty) propertyMap.get(property);
639        if (mp != null) {
640            try {
641                //System.out.println("we found a metaproperty for " + theClass.getName() +
642                //  "." + property);
643                // delegate the get operation to the metaproperty
644                return mp.getProperty(object);
645            }
646            catch(Exception e) {
647                throw new GroovyRuntimeException("Cannot read property: " + property);
648            }
649        }
650 
651        if (genericGetMethod == null) {
652            // Make sure there isn't a generic method in the "use" cases
653            List possibleGenericMethods = getMethods("get");
654            if (possibleGenericMethods != null) {
655                for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) {
656                    MetaMethod mmethod = (MetaMethod) i.next();
657                    Class[] paramTypes = mmethod.getParameterTypes();
658                    if (paramTypes.length == 1 && paramTypes[0] == String.class) {
659                        Object[] arguments = {property};
660                        Object answer = MetaClassHelper.doMethodInvoke(object, mmethod, arguments);
661                        return answer;
662                    }
663                }
664            }
665        }
666        else {
667            Object[] arguments = { property };
668            Object answer = MetaClassHelper.doMethodInvoke(object, genericGetMethod, arguments);
669            // jes bug? a property retrieved via a generic get() can't have a null value?
670            if (answer != null) {
671                return answer;
672            }
673        }
674 
675        if (!CompilerConfiguration.isJsrGroovy()) {
676            // is the property the name of a method - in which case return a
677            // closure
678            List methods = getMethods(property);
679            if (!methods.isEmpty()) {
680                return new MethodClosure(object, property);
681            }
682        }
683 
684        // lets try invoke a static getter method
685        // this case is for protected fields. I wish there was a better way...
686        Exception lastException = null;
687        try {
688            if ( !(object instanceof Class) ) {
689                MetaMethod method = findGetter(object, "get" + MetaClassHelper.capitalize(property));
690                if (method != null) {
691                   return MetaClassHelper.doMethodInvoke(object, method, MetaClassHelper.EMPTY_ARRAY);
692               }
693           }
694        }
695        catch (GroovyRuntimeException e) {
696            lastException = e;
697        }
698 
699        /*** todo or are we an extensible groovy class? */
700        if (genericGetMethod != null) {
701            return null;
702        }
703        else {
704            /*** todo these special cases should be special MetaClasses maybe */
705            if (object instanceof Class) {
706                // lets try a static field
707                return getStaticProperty((Class) object, property);
708            }
709            if (object instanceof Collection) {
710                return DefaultGroovyMethods.getAt((Collection) object, property);
711            }
712            if (object instanceof Object[]) {
713                return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), property);
714            }
715            if (object instanceof Object) {
716                try {
717                    return getAttribute(object,property);
718                } catch (MissingFieldException mfe) {
719                    // do nothing
720                }
721            }
722 
723            MetaMethod addListenerMethod = (MetaMethod) listeners.get(property);
724            if (addListenerMethod != null) {
725                /* @todo one day we could try return the previously registered Closure listener for easy removal */
726                return null;
727            }
728 
729            if (lastException == null)
730                throw new MissingPropertyException(property, theClass);
731            else
732                throw new MissingPropertyException(property, theClass, lastException);
733        }
734    }
735 
736    /***
737     * Get all the properties defined for this type
738     * @return a list of MetaProperty objects
739     */
740    public List getProperties() {
741        // simply return the values of the metaproperty map as a List
742        return new ArrayList(propertyMap.values());
743    }
744 
745    /***
746     * This will build up the property map (Map of MetaProperty objects, keyed on
747     * property name).
748     */
749    private void setupProperties(PropertyDescriptor[] propertyDescriptors) {
750        MetaProperty mp;
751        Method method;
752        MetaMethod getter = null;
753        MetaMethod setter = null;
754        Class klass;
755 
756        // first get the public fields and create MetaFieldProperty objects
757        klass = theClass;
758        while(klass != null) {
759            final Class clazz = klass;
760            Field[] fields = (Field[]) AccessController.doPrivileged(new  PrivilegedAction() {
761                public Object run() {
762                    return clazz.getDeclaredFields();
763                }
764            });
765            for(int i = 0; i < fields.length; i++) {
766                // todo: GROOVY-996
767                // we're only interested in publics and protected
768                if ((fields[i].getModifiers() & (java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.PROTECTED)) == 0)
769                     continue;
770 
771                // see if we already got this
772                if(propertyMap.get(fields[i].getName()) != null)
773                    continue;
774 
775                //System.out.println("adding field " + fields[i].getName() +
776                //  " for class " + klass.getName());
777                // stick it in there!
778                propertyMap.put(fields[i].getName(), new MetaFieldProperty(fields[i]));
779            }
780 
781            // now get the super class
782            klass = klass.getSuperclass();
783        }
784 
785   // if this an Array, then add the special read-only "length" property
786        if (theClass.isArray()) {
787            propertyMap.put("length", arrayLengthProperty);
788        }
789 
790        // now iterate over the map of property descriptors and generate
791        // MetaBeanProperty objects
792        for(int i=0; i<propertyDescriptors.length; i++) {
793            PropertyDescriptor pd = propertyDescriptors[i];
794 
795            // skip if the property type is unknown (this seems to be the case if the
796            // property descriptor is based on a setX() method that has two parameters,
797            // which is not a valid property)
798            if(pd.getPropertyType() == null)
799                continue;
800 
801            // get the getter method
802            method = pd.getReadMethod();
803            if(method != null)
804                getter = findMethod(method);
805            else
806                getter = null;
807 
808            // get the setter method
809            method = pd.getWriteMethod();
810            if(method != null)
811                setter = findMethod(method);
812            else
813                setter = null;
814 
815            // now create the MetaProperty object
816            //System.out.println("creating a bean property for class " +
817            //  theClass.getName() + ": " + pd.getName());
818 
819            mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter);
820 
821            // put it in the list
822            // this will overwrite a possible field property
823            propertyMap.put(pd.getName(), mp);
824        }
825 
826        // now look for any stray getters that may be used to define a property
827        klass = theClass;
828        while(klass != null) {
829            final Class clazz = klass;
830            Method[] methods = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
831                public Object run() {
832                    return clazz.getDeclaredMethods();
833                }
834            });
835            for (int i = 0; i < methods.length; i++) {
836                // filter out the privates
837                if(Modifier.isPublic(methods[i].getModifiers()) == false)
838                    continue;
839 
840                method = methods[i];
841 
842                String methodName = method.getName();
843 
844                // is this a getter?
845                if(methodName.startsWith("get") &&
846                    methodName.length() > 3 &&
847                    method.getParameterTypes().length == 0) {
848 
849                    // get the name of the property
850                    String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);
851 
852                    // is this property already accounted for?
853                    mp = (MetaProperty) propertyMap.get(propName);
854                    if(mp != null) {
855                        // we may have already found the setter for this
856                        if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getGetter() == null) {
857                            // update the getter method to this one
858                            ((MetaBeanProperty) mp).setGetter(findMethod(method));
859                        }
860                    }
861                    else {
862                        // we need to create a new property object
863                        // type of the property is what the get method returns
864                        MetaBeanProperty mbp = new MetaBeanProperty(propName,
865                            method.getReturnType(),
866                            findMethod(method), null);
867 
868                        // add it to the map
869                        propertyMap.put(propName, mbp);
870                    }
871                }
872                else if(methodName.startsWith("set") &&
873                    methodName.length() > 3 &&
874                    method.getParameterTypes().length == 1) {
875 
876                    // get the name of the property
877                    String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);
878 
879                    // did we already find the getter of this?
880                    mp = (MetaProperty) propertyMap.get(propName);
881                    if(mp != null) {
882                        if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getSetter() == null) {
883                            // update the setter method to this one
884                            ((MetaBeanProperty) mp).setSetter(findMethod(method));
885                        }
886                    }
887                    else {
888                        // this is a new property to add
889                        MetaBeanProperty mbp = new MetaBeanProperty(propName,
890                                                                    method.getParameterTypes()[0],
891                                                                    null,
892                                                                    findMethod(method));
893 
894                        // add it to the map
895                        propertyMap.put(propName, mbp);
896                    }
897                }
898            }
899 
900            // now get the super class
901            klass = klass.getSuperclass();
902        }
903    }
904 
905    /***
906     * Sets the property value on an object
907     */
908    public void setProperty(Object object, String property, Object newValue) {
909        MetaProperty mp = (MetaProperty) propertyMap.get(property);
910        if(mp != null) {
911            try {
912                mp.setProperty(object, newValue);
913                return;
914            }
915            catch(ReadOnlyPropertyException e) {
916                // just rethrow it; there's nothing left to do here
917                throw e;
918            }
919            catch (TypeMismatchException e) {
920                // tried to access to mismatched object.
921                throw e;
922            }
923            catch (Exception e) {
924                // if the value is a List see if we can construct the value
925                // from a constructor
926                if (newValue == null)
927                    return;
928                if (newValue instanceof List) {
929                    List list = (List) newValue;
930                    int params = list.size();
931                    Constructor[] constructors = mp.getType().getConstructors();
932                    for (int i = 0; i < constructors.length; i++) {
933                        Constructor constructor = constructors[i];
934                        if (constructor.getParameterTypes().length == params) {
935                            Object value = MetaClassHelper.doConstructorInvoke(constructor, list.toArray());
936                            mp.setProperty(object, value);
937                            return;
938                        }
939                    }
940 
941                    // if value is an array
942                    Class parameterType = mp.getType();
943                    if (parameterType.isArray()) {
944                        Object objArray = MetaClassHelper.asPrimitiveArray(list, parameterType);
945                        mp.setProperty(object, objArray);
946                        return;
947                    }
948                }
949 
950                // if value is an multidimensional array
951                // jes currently this logic only supports metabeansproperties and
952                // not metafieldproperties. It shouldn't be too hard to support
953                // the latter...
954                if (newValue.getClass().isArray() && mp instanceof MetaBeanProperty) {
955                    MetaBeanProperty mbp = (MetaBeanProperty) mp;
956                    List list = Arrays.asList((Object[])newValue);
957                    MetaMethod setter = mbp.getSetter();
958 
959                    Class parameterType = setter.getParameterTypes()[0];
960                    Class arrayType = parameterType.getComponentType();
961                    Object objArray = Array.newInstance(arrayType, list.size());
962 
963                    for (int i = 0; i < list.size(); i++) {
964                        List list2 =Arrays.asList((Object[]) list.get(i));
965                        Object objArray2 = MetaClassHelper.asPrimitiveArray(list2, arrayType);
966                        Array.set(objArray, i, objArray2);
967                    }
968 
969                    MetaClassHelper.doMethodInvoke(object, setter, new Object[]{
970                        objArray
971                    });
972                    return;
973                }
974 
975                throw new MissingPropertyException(property, theClass, e);
976            }
977        }
978 
979        try {
980            MetaMethod addListenerMethod = (MetaMethod) listeners.get(property);
981            if (addListenerMethod != null && newValue instanceof Closure) {
982                // lets create a dynamic proxy
983                Object proxy =
984                    MetaClassHelper.createListenerProxy(addListenerMethod.getParameterTypes()[0], property, (Closure) newValue);
985                MetaClassHelper.doMethodInvoke(object, addListenerMethod, new Object[] { proxy });
986                return;
987            }
988 
989            if (genericSetMethod == null) {
990                // Make sure there isn't a generic method in the "use" cases
991                List possibleGenericMethods = getMethods("set");
992                if (possibleGenericMethods != null) {
993                    for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) {
994                        MetaMethod mmethod = (MetaMethod) i.next();
995                        Class[] paramTypes = mmethod.getParameterTypes();
996                        if (paramTypes.length == 2 && paramTypes[0] == String.class) {
997                            Object[] arguments = {property, newValue};
998                            Object answer = MetaClassHelper.doMethodInvoke(object, mmethod, arguments);
999                            return;
1000                        }
1001                    }
1002                }
1003            }
1004            else {
1005                Object[] arguments = { property, newValue };
1006                MetaClassHelper.doMethodInvoke(object, genericSetMethod, arguments);
1007                return;
1008            }
1009 
1010            /*** todo or are we an extensible class? */
1011 
1012            // lets try invoke the set method
1013            // this is kind of ugly: if it is a protected field, we fall
1014            // all the way down to this klunky code. Need a better
1015            // way to handle this situation...
1016 
1017            String method = "set" + MetaClassHelper.capitalize(property);
1018            try {
1019                invokeMethod(object, method, new Object[] { newValue });
1020            }
1021            catch (MissingMethodException e1) {
1022                setAttribute(object,property,newValue);
1023            }
1024 
1025        }
1026        catch (GroovyRuntimeException e) {
1027            throw new MissingPropertyException(property, theClass, e);
1028        }
1029 
1030    }
1031 
1032 
1033    /***
1034     * Looks up the given attribute (field) on the given object
1035     */
1036    public Object getAttribute(final Object object, final String attribute) {
1037        PrivilegedActionException firstException = null;
1038 
1039        final Class clazz;
1040        if (object instanceof Class) {
1041            clazz=(Class) object;
1042        } else {
1043            clazz=theClass;
1044        }
1045 
1046        try {
1047            return AccessController.doPrivileged(new PrivilegedExceptionAction() {
1048                public Object run() throws NoSuchFieldException, IllegalAccessException {
1049                    final Field field = clazz.getDeclaredField(attribute);
1050 
1051                    field.setAccessible(true);
1052                    return field.get(object);
1053                }
1054            });
1055        } catch (final PrivilegedActionException pae) {
1056            firstException = pae;
1057        }
1058 
1059        try {
1060            return AccessController.doPrivileged(new PrivilegedExceptionAction() {
1061                public Object run() throws NoSuchFieldException, IllegalAccessException {
1062                    final Field field = clazz.getField(attribute);
1063 
1064                    field.setAccessible(true);
1065                    return field.get(object);
1066                }
1067            });
1068        } catch (final PrivilegedActionException pae) {
1069            // prefere the first exception.
1070        }
1071 
1072 
1073        if (firstException.getException() instanceof NoSuchFieldException) {
1074            throw new MissingFieldException(attribute, theClass);
1075        } else {
1076            throw new RuntimeException(firstException.getException());
1077        }
1078    }
1079 
1080    /***
1081     * Sets the given attribute (field) on the given object
1082     */
1083    public void setAttribute(final Object object, final String attribute, final Object newValue) {
1084        PrivilegedActionException firstException = null;
1085 
1086        final Class clazz;
1087        if (object instanceof Class) {
1088            clazz=(Class) object;
1089        } else {
1090            clazz=theClass;
1091        }
1092 
1093        try {
1094            AccessController.doPrivileged(new PrivilegedExceptionAction() {
1095                public Object run() throws NoSuchFieldException, IllegalAccessException {
1096                    final Field field = clazz.getDeclaredField(attribute);
1097 
1098                    field.setAccessible(true);
1099                    field.set(object,newValue);
1100                    return null;
1101                }
1102            });
1103            return;
1104        } catch (final PrivilegedActionException pae) {
1105            firstException = pae;
1106        }
1107 
1108        try {
1109            AccessController.doPrivileged(new PrivilegedExceptionAction() {
1110                public Object run() throws NoSuchFieldException, IllegalAccessException {
1111                    final Field field = clazz.getField(attribute);
1112 
1113                    field.setAccessible(true);
1114                    field.set(object, newValue);
1115                    return null;
1116                }
1117            });
1118            return;
1119        } catch (final PrivilegedActionException pae) {
1120            // prefere the first exception.
1121        }
1122 
1123        if (firstException.getException() instanceof NoSuchFieldException) {
1124            throw new MissingFieldException(attribute, theClass);
1125        } else {
1126            throw new RuntimeException(firstException.getException());
1127        }
1128    }
1129 
1130    public ClassNode getClassNode() {
1131        if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) {
1132            // lets try load it from the classpath
1133            String className = theClass.getName();
1134            String groovyFile = className;
1135            int idx = groovyFile.indexOf('$');
1136            if (idx > 0) {
1137                groovyFile = groovyFile.substring(0, idx);
1138            }
1139            groovyFile = groovyFile.replace('.', '/') + ".groovy";
1140 
1141            //System.out.println("Attempting to load: " + groovyFile);
1142            URL url = theClass.getClassLoader().getResource(groovyFile);
1143            if (url == null) {
1144                url = Thread.currentThread().getContextClassLoader().getResource(groovyFile);
1145            }
1146            if (url != null) {
1147                try {
1148 
1149                    /***
1150                     * todo there is no CompileUnit in scope so class name
1151                     * checking won't work but that mostly affects the bytecode
1152                     * generation rather than viewing the AST
1153                     */
1154                    CompilationUnit.ClassgenCallback search = new CompilationUnit.ClassgenCallback() {
1155                        public void call( ClassVisitor writer, ClassNode node ) {
1156                            if( node.getName().equals(theClass.getName()) ) {
1157                                MetaClassImpl.this.classNode = node;
1158                            }
1159                        }
1160                    };
1161 
1162 
1163                    CompilationUnit unit = new CompilationUnit(new GroovyClassLoader(getClass().getClassLoader()) );
1164                    unit.setClassgenCallback( search );
1165                    unit.addSource( url );
1166                    unit.compile( Phases.CLASS_GENERATION );
1167                }
1168                catch (Exception e) {
1169                    throw new GroovyRuntimeException("Exception thrown parsing: " + groovyFile + ". Reason: " + e, e);
1170                }
1171            }
1172 
1173        }
1174        return classNode;
1175    }
1176 
1177    public String toString() {
1178        return super.toString() + "[" + theClass + "]";
1179    }
1180 
1181    // Implementation methods
1182    //-------------------------------------------------------------------------
1183 
1184    /***
1185     * Adds all the methods declared in the given class to the metaclass
1186     * ignoring any matching methods already defined by a derived class
1187     *
1188     * @param theClass
1189     */
1190    private void addMethods(final Class theClass, boolean forceOverwrite) {
1191        // add methods directly declared in the class
1192        Method[] methodArray = (Method[]) AccessController.doPrivileged(new  PrivilegedAction() {
1193                public Object run() {
1194                    return theClass.getDeclaredMethods();
1195                }
1196            });
1197        for (int i = 0; i < methodArray.length; i++) {
1198            Method reflectionMethod = methodArray[i];
1199            if ( reflectionMethod.getName().indexOf('+') >= 0 ) {
1200                // Skip Synthetic methods inserted by JDK 1.5 compilers and later
1201                continue;
1202            }
1203            MetaMethod method = createMetaMethod(reflectionMethod);
1204            addMethod(method,forceOverwrite);
1205        }
1206    }
1207 
1208    private void addMethod(MetaMethod method, boolean forceOverwrite) {
1209        String name = method.getName();
1210 
1211        //System.out.println(theClass.getName() + " == " + name + Arrays.asList(method.getParameterTypes()));
1212 
1213        if (isGenericGetMethod(method) && genericGetMethod == null) {
1214            genericGetMethod = method;
1215        }
1216        else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) {
1217            genericSetMethod = method;
1218        }
1219        if (method.isStatic()) {
1220            List list = (List) staticMethodIndex.get(name);
1221            if (list == null) {
1222                list = new ArrayList();
1223                staticMethodIndex.put(name, list);
1224                list.add(method);
1225            }
1226            else {
1227                if (!MetaClassHelper.containsMatchingMethod(list, method)) {
1228                    list.add(method);
1229                }
1230            }
1231        }
1232 
1233        List list = (List) methodIndex.get(name);
1234        if (list == null) {
1235            list = new ArrayList();
1236            methodIndex.put(name, list);
1237            list.add(method);
1238        }
1239        else {
1240            if (forceOverwrite) {
1241                removeMatchingMethod(list,method);
1242                list.add(method);
1243            } else if (!MetaClassHelper.containsMatchingMethod(list, method)) {
1244                list.add(method);
1245            }
1246        }
1247    }
1248 
1249    /***
1250     * remove a method of the same matching prototype was found in the list
1251     */
1252    private void removeMatchingMethod(List list, MetaMethod method) {
1253        for (Iterator iter = list.iterator(); iter.hasNext();) {
1254            MetaMethod aMethod = (MetaMethod) iter.next();
1255            Class[] params1 = aMethod.getParameterTypes();
1256            Class[] params2 = method.getParameterTypes();
1257            if (params1.length == params2.length) {
1258                boolean matches = true;
1259                for (int i = 0; i < params1.length; i++) {
1260                    if (params1[i] != params2[i]) {
1261                        matches = false;
1262                        break;
1263                    }
1264                }
1265                if (matches) {
1266                    iter.remove();
1267                    return;
1268                }
1269            }
1270        }
1271        return;
1272    }
1273 
1274 
1275    /***
1276     * Adds all of the newly defined methods from the given class to this
1277     * metaclass
1278     *
1279     * @param theClass
1280     */
1281    private void addNewStaticMethodsFrom(Class theClass) {
1282        MetaClass interfaceMetaClass = registry.getMetaClass(theClass);
1283        Iterator iter = interfaceMetaClass.newGroovyMethodsList.iterator();
1284        while (iter.hasNext()) {
1285            MetaMethod method = (MetaMethod) iter.next();
1286            if (! newGroovyMethodsList.contains(method)){
1287                newGroovyMethodsList.add(method);
1288                addMethod(method,false);
1289            }
1290        }
1291    }
1292 
1293    /***
1294     * @return the value of the static property of the given class
1295     */
1296    private Object getStaticProperty(Class aClass, String property) {
1297        //System.out.println("Invoking property: " + property + " on class: "
1298        // + aClass);
1299 
1300        // lets try invoke a static getter method
1301        MetaMethod method = findStaticGetter(aClass, "get" + MetaClassHelper.capitalize(property));
1302        if (method != null) {
1303            return MetaClassHelper.doMethodInvoke(aClass, method, MetaClassHelper.EMPTY_ARRAY);
1304        }
1305 
1306        //no static getter found, try attribute  
1307        try {
1308            return getAttribute(aClass,property);
1309        } catch (MissingFieldException mfe) {
1310            throw new MissingPropertyException(property, aClass, mfe);
1311        }
1312    }
1313 
1314    /***
1315     * @return the matching method which should be found
1316     */
1317    private MetaMethod findMethod(Method aMethod) {
1318        List methods = getMethods(aMethod.getName());
1319        for (Iterator iter = methods.iterator(); iter.hasNext();) {
1320            MetaMethod method = (MetaMethod) iter.next();
1321            if (method.isMethod(aMethod)) {
1322                return method;
1323            }
1324        }
1325        //log.warning("Creating reflection based dispatcher for: " + aMethod);
1326        return new ReflectionMetaMethod(aMethod);
1327    }
1328 
1329    /***
1330     * @return the getter method for the given object
1331     */
1332    private MetaMethod findGetter(Object object, String name) {
1333        List methods = getMethods(name);
1334        for (Iterator iter = methods.iterator(); iter.hasNext();) {
1335            MetaMethod method = (MetaMethod) iter.next();
1336            if (method.getParameterTypes().length == 0) {
1337                return method;
1338            }
1339        }
1340        return null;
1341    }
1342 
1343    /***
1344     * @return the Method of the given name with no parameters or null
1345     */
1346    private MetaMethod findStaticGetter(Class type, String name) {
1347        List methods = getStaticMethods(name);
1348        for (Iterator iter = methods.iterator(); iter.hasNext();) {
1349            MetaMethod method = (MetaMethod) iter.next();
1350            if (method.getParameterTypes().length == 0) {
1351                return method;
1352            }
1353        }
1354 
1355        /*** todo dirty hack - don't understand why this code is necessary - all methods should be in the allMethods list! */
1356        try {
1357            Method method = type.getMethod(name, MetaClassHelper.EMPTY_TYPE_ARRAY);
1358            if ((method.getModifiers() & Modifier.STATIC) != 0) {
1359                return findMethod(method);
1360            }
1361            else {
1362                return null;
1363            }
1364        }
1365        catch (Exception e) {
1366            return null;
1367        }
1368    }
1369    
1370    private static Object doConstructorInvokeAt(final Class at, Constructor constructor, Object[] argumentArray) {
1371        if (log.isLoggable(Level.FINER)) {
1372            MetaClassHelper.logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray);
1373        }
1374 
1375        try {
1376            // To fix JIRA 435
1377            // Every constructor should be opened to the accessible classes.
1378            final boolean accessible = MetaClassHelper.accessibleToConstructor(at, constructor);
1379 
1380            final Constructor ctor = constructor;
1381            AccessController.doPrivileged(new PrivilegedAction() {
1382                public Object run() {
1383                    ctor.setAccessible(accessible);
1384                    return null;
1385                }
1386            });
1387            // end of patch
1388 
1389            return constructor.newInstance(argumentArray);
1390        }
1391        catch (InvocationTargetException e) {
1392            /*Throwable t = e.getTargetException();
1393            if (t instanceof Error) {
1394                Error error = (Error) t;
1395                throw error;
1396            }
1397            if (t instanceof RuntimeException) {
1398                RuntimeException runtimeEx = (RuntimeException) t;
1399                throw runtimeEx;
1400            }*/
1401            throw new InvokerInvocationException(e);
1402        }
1403        catch (IllegalArgumentException e) {
1404            if (MetaClassHelper.coerceGStrings(argumentArray)) {
1405                try {
1406                    return constructor.newInstance(argumentArray);
1407                }
1408                catch (Exception e2) {
1409                    // allow fall through
1410                }
1411            }
1412            throw new GroovyRuntimeException(
1413                "failed to invoke constructor: "
1414                    + constructor
1415                    + " with arguments: "
1416                    + InvokerHelper.toString(argumentArray)
1417                    + " reason: "
1418                    + e);
1419        }
1420        catch (IllegalAccessException e) {
1421            throw new GroovyRuntimeException(
1422                "could not access constructor: "
1423                    + constructor
1424                    + " with arguments: "
1425                    + InvokerHelper.toString(argumentArray)
1426                    + " reason: "
1427                    + e);
1428        }
1429        catch (Exception e) {
1430            throw new GroovyRuntimeException(
1431                "failed to invoke constructor: "
1432                    + constructor
1433                    + " with arguments: "
1434                    + InvokerHelper.toString(argumentArray)
1435                    + " reason: "
1436                    + e,
1437                    e);
1438        }
1439    }
1440 
1441    /***
1442     * Chooses the correct method to use from a list of methods which match by
1443     * name.
1444     *
1445     * @param methods
1446     *            the possible methods to choose from
1447     * @param arguments
1448     *            the original argument to the method
1449     * @return
1450     */
1451    private Object chooseMethod(String methodName, List methods, Class[] arguments, boolean coerce) {
1452        int methodCount = methods.size();
1453        if (methodCount <= 0) {
1454            return null;
1455        }
1456        else if (methodCount == 1) {
1457            Object method = methods.get(0);
1458            if (MetaClassHelper.isValidMethod(method, arguments, coerce)) {
1459                return method;
1460            }
1461            return null;
1462        }
1463        Object answer = null;
1464        if (arguments == null || arguments.length == 0) {
1465            answer = MetaClassHelper.chooseEmptyMethodParams(methods);
1466        }
1467        else if (arguments.length == 1 && arguments[0] == null) {
1468            answer = MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods);
1469        }
1470        else {
1471            List matchingMethods = new ArrayList();
1472 
1473            for (Iterator iter = methods.iterator(); iter.hasNext();) {
1474                Object method = iter.next();
1475                Class[] paramTypes;
1476 
1477                // making this false helps find matches
1478                if (MetaClassHelper.isValidMethod(method, arguments, coerce)) {
1479                    matchingMethods.add(method);
1480                }
1481            }
1482            if (matchingMethods.isEmpty()) {
1483                return null;
1484            }
1485            else if (matchingMethods.size() == 1) {
1486                return matchingMethods.get(0);
1487            }
1488            return chooseMostSpecificParams(methodName, matchingMethods, arguments);
1489 
1490        }
1491        if (answer != null) {
1492            return answer;
1493        }
1494        throw new GroovyRuntimeException(
1495            "Could not find which method to invoke from this list: "
1496                + methods
1497                + " for arguments: "
1498                + InvokerHelper.toString(arguments));
1499    }
1500 
1501    private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) {
1502 
1503        Class[] wrappedArguments = MetaClassHelper.wrap(arguments);
1504 
1505        int matchesDistance = -1;
1506        LinkedList matches = new LinkedList();
1507        for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) {
1508            Object method = iter.next();
1509            Class[] paramTypes = MetaClassHelper.getParameterTypes(method);
1510            if (!MetaClassHelper.parametersAreCompatible(arguments, paramTypes)) continue;
1511            int dist = MetaClassHelper.calculateParameterDistance(arguments, paramTypes);
1512            if (matches.size()==0) {
1513                matches.add(method);
1514                matchesDistance = dist;
1515            } else if (dist<matchesDistance) {
1516                matchesDistance=dist;
1517                matches.clear();
1518                matches.add(method);
1519            } else if (dist==matchesDistance) {
1520                matches.add(method);
1521            }
1522 
1523        }
1524        if (matches.size()==1) {
1525            return matches.getFirst();
1526        }
1527        if (matches.size()==0) {
1528            return null;
1529        }
1530 
1531        //more than one matching method found --> ambigous!
1532        String msg = "Ambiguous method overloading for method ";
1533        msg+= theClass.getName()+"#"+name;
1534        msg+= ".\nCannot resolve which method to invoke for ";
1535        msg+= InvokerHelper.toString(arguments);
1536        msg+= " due to overlapping prototypes between:";
1537        for (Iterator iter = matches.iterator(); iter.hasNext();) {
1538            Class[] types=MetaClassHelper.getParameterTypes(iter.next());
1539            msg+= "\n\t"+InvokerHelper.toString(types);
1540        }
1541        throw new GroovyRuntimeException(msg);
1542    }
1543 
1544    private boolean isGenericGetMethod(MetaMethod method) {
1545        if (method.getName().equals("get")) {
1546            Class[] parameterTypes = method.getParameterTypes();
1547            return parameterTypes.length == 1 && parameterTypes[0] == String.class;
1548        }
1549        return false;
1550    }
1551 
1552    /***
1553     * Call this method when any mutation method is called, such as adding a new
1554     * method to this MetaClass so that any caching or bytecode generation can be
1555     * regenerated.
1556     */
1557    private synchronized void onMethodChange() {
1558        reflector = null;
1559    }
1560 
1561    protected synchronized void checkInitialised() {
1562        if (!initialised) {
1563            initialised = true;
1564            addInheritedMethods();
1565        }
1566        if (reflector == null) {
1567            generateReflector();
1568        }
1569    }
1570 
1571    private MetaMethod createMetaMethod(final Method method) {
1572        if (registry.useAccessible()) {
1573            AccessController.doPrivileged(new PrivilegedAction() {
1574                public Object run() {
1575                    method.setAccessible(true);
1576                    return null;
1577                }
1578            });
1579        }
1580 
1581        MetaMethod answer = new MetaMethod(method);
1582        if (isValidReflectorMethod(answer)) {
1583            allMethods.add(answer);
1584            answer.setMethodIndex(allMethods.size());
1585        }
1586        else {
1587            //log.warning("Creating reflection based dispatcher for: " + method);
1588            answer = new ReflectionMetaMethod(method);
1589        }
1590 
1591        if (useReflection) {
1592            //log.warning("Creating reflection based dispatcher for: " + method);
1593            return new ReflectionMetaMethod(method);
1594        }
1595 
1596        return answer;
1597    }
1598 
1599    private boolean isValidReflectorMethod(MetaMethod method) {
1600        // We cannot use a reflector if the method is private, protected, or package accessible only.
1601        if (!method.isPublic()) {
1602            return false;
1603        }
1604        // lets see if this method is implemented on an interface
1605        List interfaceMethods = getInterfaceMethods();
1606        for (Iterator iter = interfaceMethods.iterator(); iter.hasNext();) {
1607            MetaMethod aMethod = (MetaMethod) iter.next();
1608            if (method.isSame(aMethod)) {
1609                method.setInterfaceClass(aMethod.getDeclaringClass());
1610                return true;
1611            }
1612        }
1613        // it's no interface method, so try to find the highest class
1614        // in hierarchy defining this method
1615        Class declaringClass = method.getDeclaringClass();
1616        for (Class clazz=declaringClass; clazz!=null; clazz=clazz.getSuperclass()) {
1617            try {
1618                final Class klazz = clazz;
1619                final String mName = method.getName();
1620                final Class[] parms = method.getParameterTypes();
1621                try {
1622                    Method m = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1623                        public Object run() throws NoSuchMethodException {
1624                            return klazz.getDeclaredMethod(mName, parms);
1625                        }
1626                    });
1627                    if (!Modifier.isPublic(clazz.getModifiers())) continue;
1628                    if (!Modifier.isPublic(m.getModifiers())) continue;
1629                    declaringClass = clazz;
1630                } catch (PrivilegedActionException pae) {
1631                    if (pae.getException() instanceof NoSuchMethodException) {
1632                        throw (NoSuchMethodException) pae.getException();
1633                    } else {
1634                        throw new RuntimeException(pae.getException());
1635                    }
1636                }
1637            } catch (SecurityException e) {
1638                continue;
1639            } catch (NoSuchMethodException e) {
1640                continue;
1641            }
1642        }
1643        if (!Modifier.isPublic(declaringClass.getModifiers())) return false;
1644        method.setDeclaringClass(declaringClass);
1645 
1646        return true;
1647    }
1648 
1649    private void generateReflector() {
1650        reflector = loadReflector(allMethods);
1651        if (reflector == null) {
1652            throw new RuntimeException("Should have a reflector for "+theClass.getName());
1653        }
1654        // lets set the reflector on all the methods
1655        for (Iterator iter = allMethods.iterator(); iter.hasNext();) {
1656            MetaMethod metaMethod = (MetaMethod) iter.next();
1657            //System.out.println("Setting reflector for method: " + metaMethod + " with index: " + metaMethod.getMethodIndex());
1658            metaMethod.setReflector(reflector);
1659        }
1660    }
1661 
1662    private String getReflectorName() {
1663        String className = theClass.getName();
1664        String packagePrefix = "gjdk.";
1665        String name = packagePrefix + className + "_GroovyReflector";
1666        if (theClass.isArray()) {
1667        	   Class clazz = theClass;
1668        	   name = packagePrefix;
1669        	   int level = 0;
1670        	   while (clazz.isArray()) {
1671        	   	  clazz = clazz.getComponentType();
1672        	   	  level++;
1673        	   }
1674            String componentName = clazz.getName();
1675            name = packagePrefix + componentName + "_GroovyReflectorArray";
1676            if (level>1) name += level;
1677        }
1678        return name;
1679    }
1680 
1681    private Reflector loadReflector(List methods) {
1682        ReflectorGenerator generator = new ReflectorGenerator(methods);
1683        String name = getReflectorName();
1684        /* 
1685         * Lets see if its already loaded.
1686         */
1687        try {
1688            Class type = loadReflectorClass(name);
1689            return (Reflector) type.newInstance();
1690        }
1691        catch (ClassNotFoundException cnfe) {
1692            /*
1693             * Lets generate it && load it.
1694             */                        
1695            try {
1696                ClassWriter cw = new ClassWriter(true);
1697                generator.generate(cw, name);
1698                byte[] bytecode = cw.toByteArray();
1699                Class type = loadReflectorClass(name, bytecode);
1700                if  (Reflector.class.getClassLoader()!=type.getSuperclass().getClassLoader()) {
1701                    throw new Error(
1702                      name+" does have Reflector.class as superclass, "+
1703                      "Reflector.class is loaded through the loader "+
1704                      Reflector.class.getClassLoader()+
1705                      " and "+name+"'s superclass is loaded through "+
1706                      type.getSuperclass().getClassLoader()+
1707                      ". This should never happen, check your classloader configuration."
1708                    );  
1709                }
1710                return (Reflector) type.newInstance();
1711            }
1712            catch (Exception e) {
1713                e.printStackTrace();
1714                throw new GroovyRuntimeException("Could not generate and load the reflector for class: " + name + ". Reason: " + e, e);
1715            }
1716        } catch (Error e) {
1717            throw e;
1718        } catch (Throwable t) {
1719            /*
1720             * All other exception and error types are reported at once.
1721             */
1722            throw new GroovyRuntimeException("Could not load the reflector for class: " + name + ". Reason: " + t, t);
1723        }
1724    }
1725 
1726    private Class loadReflectorClass(final String name, final byte[] bytecode) throws ClassNotFoundException {
1727        ClassLoader loader = (ClassLoader) AccessController.doPrivileged(new  PrivilegedAction() {
1728            public Object run() {
1729                return theClass.getClassLoader();
1730            }
1731        }); 
1732        if (loader instanceof GroovyClassLoader) {
1733            final GroovyClassLoader gloader = (GroovyClassLoader) loader;
1734            return (Class) AccessController.doPrivileged(new PrivilegedAction() {
1735                public Object run() {
1736                    return gloader.defineClass(name, bytecode, getClass().getProtectionDomain());
1737                }
1738            });
1739        }
1740        return registry.loadClass(loader, name, bytecode);
1741    }
1742 
1743    private Class loadReflectorClass(String name) throws ClassNotFoundException {
1744        ClassLoader loader = (ClassLoader) AccessController.doPrivileged(new  PrivilegedAction() {
1745            public Object run() {
1746                return theClass.getClassLoader();
1747            }
1748        }); 
1749        if (loader instanceof GroovyClassLoader) {
1750            GroovyClassLoader gloader = (GroovyClassLoader) loader;
1751            return gloader.loadClass(name);
1752        }
1753        return registry.loadClass(loader, name);
1754    }
1755 
1756    public List getMethods() {
1757        return allMethods;
1758    }
1759 
1760    public List getMetaMethods() {
1761        return new ArrayList(newGroovyMethodsList);
1762    }
1763 
1764    private synchronized List getInterfaceMethods() {
1765        if (interfaceMethods == null) {
1766            interfaceMethods = new ArrayList();
1767            Class type = theClass;
1768            while (type != null) {
1769                Class[] interfaces = type.getInterfaces();
1770                for (int i = 0; i < interfaces.length; i++) {
1771                    Class iface = interfaces[i];
1772                    Method[] methods = iface.getMethods();
1773                    addInterfaceMethods(interfaceMethods, methods);
1774                }
1775                type = type.getSuperclass();
1776            }
1777        }
1778        return interfaceMethods;
1779    }
1780 
1781    private void addInterfaceMethods(List list, Method[] methods) {
1782        for (int i = 0; i < methods.length; i++) {
1783            list.add(createMetaMethod(methods[i]));
1784        }
1785    }
1786 }