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