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 }