001    /*
002    $Id: MetaClassImpl.java,v 1.9 2005/11/13 16:42:09 blackdrag 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.InvocationTargetException;
057    import java.lang.reflect.Method;
058    import java.lang.reflect.Modifier;
059    import java.net.URL;
060    import java.security.AccessController;
061    import java.security.PrivilegedAction;
062    import java.security.PrivilegedActionException;
063    import java.security.PrivilegedExceptionAction;
064    import java.util.ArrayList;
065    import java.util.Arrays;
066    import java.util.Collection;
067    import java.util.Collections;
068    import java.util.HashMap;
069    import java.util.Iterator;
070    import java.util.LinkedList;
071    import java.util.List;
072    import java.util.Map;
073    import java.util.logging.Level;
074    
075    import org.codehaus.groovy.ast.ClassNode;
076    import org.codehaus.groovy.classgen.ReflectorGenerator;
077    import org.codehaus.groovy.control.CompilationUnit;
078    import org.codehaus.groovy.control.CompilerConfiguration;
079    import org.codehaus.groovy.control.Phases;
080    import org.codehaus.groovy.runtime.CurriedClosure;
081    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
082    import org.codehaus.groovy.runtime.GroovyCategorySupport;
083    import org.codehaus.groovy.runtime.InvokerHelper;
084    import org.codehaus.groovy.runtime.InvokerInvocationException;
085    import org.codehaus.groovy.runtime.MetaClassHelper;
086    import org.codehaus.groovy.runtime.MethodClosure;
087    import org.codehaus.groovy.runtime.MethodKey;
088    import org.codehaus.groovy.runtime.NewInstanceMetaMethod;
089    import org.codehaus.groovy.runtime.NewStaticMetaMethod;
090    import org.codehaus.groovy.runtime.ReflectionMetaMethod;
091    import org.codehaus.groovy.runtime.Reflector;
092    import org.codehaus.groovy.runtime.TemporaryMethodKey;
093    import org.codehaus.groovy.runtime.TransformMetaMethod;
094    import org.objectweb.asm.ClassVisitor;
095    import org.objectweb.asm.ClassWriter;
096    
097    /**
098    * Allows methods to be dynamically added to existing classes at runtime
099    *
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    }