001 /*
002 $Id: MetaClassImpl.java 4611 2006-12-23 11:17:12Z blackdrag $
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.Constructor;
054 import java.lang.reflect.Field;
055 import java.lang.reflect.Method;
056 import java.lang.reflect.Modifier;
057 import java.net.URL;
058 import java.security.AccessController;
059 import java.security.PrivilegedAction;
060 import java.security.PrivilegedActionException;
061 import java.security.PrivilegedExceptionAction;
062 import java.util.ArrayList;
063 import java.util.Arrays;
064 import java.util.Collection;
065 import java.util.Collections;
066 import java.util.Comparator;
067 import java.util.HashMap;
068 import java.util.HashSet;
069 import java.util.Iterator;
070 import java.util.LinkedList;
071 import java.util.List;
072 import java.util.Map;
073 import java.util.Set;
074 import java.util.logging.Level;
075
076 import org.codehaus.groovy.GroovyBugError;
077 import org.codehaus.groovy.ast.ClassNode;
078 import org.codehaus.groovy.classgen.BytecodeHelper;
079 import org.codehaus.groovy.control.CompilationUnit;
080 import org.codehaus.groovy.control.Phases;
081 import org.codehaus.groovy.runtime.CurriedClosure;
082 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
083 import org.codehaus.groovy.runtime.DefaultMethodKey;
084 import org.codehaus.groovy.runtime.GroovyCategorySupport;
085 import org.codehaus.groovy.runtime.InvokerHelper;
086 import org.codehaus.groovy.runtime.MetaClassHelper;
087 import org.codehaus.groovy.runtime.MethodClosure;
088 import org.codehaus.groovy.runtime.MethodKey;
089 import org.codehaus.groovy.runtime.NewInstanceMetaMethod;
090 import org.codehaus.groovy.runtime.NewStaticMetaMethod;
091 import org.codehaus.groovy.runtime.ReflectionMetaMethod;
092 import org.codehaus.groovy.runtime.Reflector;
093 import org.codehaus.groovy.runtime.TransformMetaMethod;
094 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
095 import org.codehaus.groovy.runtime.wrappers.Wrapper;
096 import org.objectweb.asm.ClassVisitor;
097
098 /**
099 * Allows methods to be dynamically added to existing classes at runtime
100 *
101 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
102 * @author Guillaume Laforge
103 * @author Jochen Theodorou
104 * @version $Revision: 4611 $
105 * @see groovy.lang.MetaClass
106 */
107 public class MetaClassImpl extends MetaClass {
108
109 protected MetaClassRegistry registry;
110 private ClassNode classNode;
111 private Map classMethodIndex = new HashMap();
112 private Map classMethodIndexForSuper;
113 private Map classStaticMethodIndex = new HashMap();
114 private Map classPropertyIndex = new HashMap();
115 private Map classPropertyIndexForSuper = new HashMap();
116 private Map staticPropertyIndex = new HashMap();
117 private Map listeners = new HashMap();
118 private Map methodCache = Collections.synchronizedMap(new HashMap());
119 private Map staticMethodCache = Collections.synchronizedMap(new HashMap());
120 private MetaMethod genericGetMethod;
121 private MetaMethod genericSetMethod;
122 private List constructors;
123 private List allMethods = new ArrayList();
124 private List interfaceMethods;
125 private Reflector reflector;
126 private boolean initialized;
127 // we only need one of these that can be reused over and over.
128 private MetaProperty arrayLengthProperty = new MetaArrayLengthProperty();
129 private final static MetaMethod AMBIGOUS_LISTENER_METHOD = new MetaMethod(null,null,new Class[]{},null,0);
130 private static final Object[] EMPTY_ARGUMENTS = {};
131 private List newGroovyMethodsList = new LinkedList();
132
133 public MetaClassImpl(MetaClassRegistry registry, final Class theClass) {
134 super(theClass);
135 this.registry = registry;
136
137 constructors = (List) AccessController.doPrivileged(new PrivilegedAction() {
138 public Object run() {
139 return Arrays.asList (theClass.getDeclaredConstructors());
140 }
141 });
142 }
143
144 private void fillMethodIndex() {
145 LinkedList superClasses = getSuperClasses();
146 // let's add all the base class methods
147 for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
148 Class c = (Class) iter.next();
149 addMethods(c);
150 }
151
152 Set interfaces = new HashSet();
153 makeInterfaceSet(theClass,interfaces);
154
155 inheritMethods(superClasses,classMethodIndex);
156 inheritInterfaceMethods(interfaces);
157 copyClassMethodIndexForSuper();
158
159 connectMultimethods(superClasses);
160 populateInterfaces(interfaces);
161 removeMultimethodsOverloadedWithPrivateMethods();
162
163 replaceWithMOPCalls();
164 }
165
166 private LinkedList getSuperClasses() {
167 LinkedList superClasses = new LinkedList();
168 for (Class c = theClass; c!= null; c = c.getSuperclass()) {
169 superClasses.addFirst(c);
170 }
171 if (theClass.isArray() && theClass!=Object[].class && !theClass.getComponentType().isPrimitive()) {
172 superClasses.addFirst(Object[].class);
173 }
174 return superClasses;
175 }
176
177 private void removeMultimethodsOverloadedWithPrivateMethods() {
178 Map privates = new HashMap();
179 MethodIndexAction mia = new MethodIndexAction() {
180 public List methodNameAction(Class clazz, String methodName, List methods) {
181 boolean hasPrivate=false;
182 for (Iterator iter = methods.iterator(); iter.hasNext();) {
183 MetaMethod method = (MetaMethod) iter.next();
184 if (method.isPrivate() && clazz == method.getDeclaringClass()) {
185 hasPrivate = true;
186 break;
187 }
188 }
189 if (!hasPrivate) return null;
190 // We have private methods for that name, so remove the
191 // multimethods. That is the same as in our index for
192 // super, so just copy the list from there. It is not
193 // possible to use a pointer here, because the methods
194 // in the index for super are replaced later by MOP
195 // methods like super$5$foo
196 methods.clear();
197 methods.addAll((Collection) ((Map) classMethodIndexForSuper.get(clazz)).get(methodName));
198 return methods;
199 }
200 public boolean replaceMethodList() {return false;}
201 };
202 mia.iterate(classMethodIndex);
203 }
204
205
206 private void replaceWithMOPCalls() {
207 // no MOP methods if not a child of GroovyObject
208 if (!GroovyObject.class.isAssignableFrom(theClass)) return;
209
210 final Map mainClassMethodIndex = (Map) classMethodIndex.get(theClass);
211 class MOPIter extends MethodIndexAction {
212 boolean useThis;
213 public boolean skipClass(Class clazz) {
214 return !useThis && clazz==theClass;
215 }
216 public void methodListAction(Class clazz, String methodName, MetaMethod method, List oldList, List newList) {
217 String mopName = getMOPMethodName(method.getDeclaringClass(), methodName,useThis);
218 List matches = (List) mainClassMethodIndex.get(mopName);
219 if (matches==null) {
220 newList.add(method);
221 return;
222 }
223 matches = new ArrayList(matches);
224 MetaMethod matchingMethod = removeMatchingMethod(matches,method);
225 if (matchingMethod==null) {
226 newList.add(method);
227 return;
228 } else {
229 newList.add(matchingMethod);
230 }
231 }
232 }
233 MOPIter iter = new MOPIter();
234
235 // replace all calls for super with the correct MOP method
236 iter.useThis = false;
237 iter.iterate(classMethodIndexForSuper);
238 // replace all calls for this with the correct MOP method
239 iter.useThis = true;
240 iter.iterate(classMethodIndex);
241 }
242
243 private String getMOPMethodName(Class declaringClass, String name, boolean useThis) {
244 int distance = 0;
245 for (;declaringClass!=null; declaringClass=declaringClass.getSuperclass()) {
246 distance++;
247 }
248 return (useThis?"this":"super")+"$"+distance+"$"+name;
249 }
250
251 private void copyClassMethodIndexForSuper() {
252 classMethodIndexForSuper = new HashMap(classMethodIndex.size());
253 for (Iterator iter = classMethodIndex.entrySet().iterator(); iter.hasNext();) {
254 Map.Entry cmiEntry = (Map.Entry) iter.next();
255 Map methodIndex = (Map) cmiEntry.getValue();
256 Map copy = new HashMap (methodIndex.size());
257 for (Iterator iterator = methodIndex.entrySet().iterator(); iterator.hasNext();) {
258 Map.Entry mEntry = (Map.Entry) iterator.next();
259 copy.put(mEntry.getKey(), new ArrayList((List) mEntry.getValue()));
260 }
261 classMethodIndexForSuper.put(cmiEntry.getKey(),copy);
262 }
263 }
264
265 private void inheritInterfaceMethods(Set interfaces) {
266 // add methods declared by DGM for interfaces
267 List methods = registry.getInstanceMethods();
268 for (Iterator iter = methods.iterator(); iter.hasNext();) {
269 Method element = (Method) iter.next();
270 Class dgmClass = element.getParameterTypes()[0];
271 if (!interfaces.contains(dgmClass)) continue;
272 NewInstanceMetaMethod method = new NewInstanceMetaMethod(createMetaMethod(element));
273 if (! newGroovyMethodsList.contains(method)){
274 newGroovyMethodsList.add(method);
275 }
276 Map methodIndex = (Map) classMethodIndex.get(theClass);
277 List list = (List) methodIndex.get(method.getName());
278 if (list == null) {
279 list = new ArrayList();
280 methodIndex.put(method.getName(), list);
281 list.add(method);
282 } else {
283 addMethodToList(list,method);
284 }
285 }
286 methods = registry.getStaticMethods();
287 for (Iterator iter = methods.iterator(); iter.hasNext();) {
288 Method element = (Method) iter.next();
289 Class dgmClass = element.getParameterTypes()[0];
290 if (!interfaces.contains(dgmClass)) continue;
291 addNewStaticMethod(element);
292 }
293 }
294
295 private void populateInterfaces(Set interfaces){
296 Map currentIndex = (Map) classMethodIndex.get(theClass);
297 Map index = new HashMap();
298 copyNonPrivateMethods(currentIndex,index);
299 for (Iterator iter = interfaces.iterator(); iter.hasNext();) {
300 Class iClass = (Class) iter.next();
301 Map methodIndex = (Map) classMethodIndex.get(iClass);
302 if (methodIndex==null || methodIndex.size()==0) {
303 classMethodIndex.put(iClass,index);
304 continue;
305 }
306 copyNonPrivateMethods(currentIndex,methodIndex);
307 }
308 }
309
310 private static void makeInterfaceSet(Class c, Set s) {
311 if (c==null) return;
312 Class[] interfaces = c.getInterfaces();
313 for (int i = 0; i < interfaces.length; i++) {
314 if (!s.contains(interfaces[i])) {
315 s.add(interfaces[i]);
316 makeInterfaceSet(interfaces[i],s);
317 }
318 }
319 makeInterfaceSet(c.getSuperclass(),s);
320 }
321
322 private void copyNonPrivateMethods(Map from, Map to) {
323 for (Iterator iterator = from.entrySet().iterator(); iterator.hasNext();) {
324 Map.Entry element = (Map.Entry) iterator.next();
325 List oldList = (List) element.getValue();
326 List newList = (List) to.get(element.getKey());
327 if (newList==null) {
328 to.put(element.getKey(),new ArrayList(oldList));
329 } else {
330 addNonPrivateMethods(newList,oldList);
331 }
332 }
333 }
334
335 private void connectMultimethods(List superClasses){
336 superClasses = DefaultGroovyMethods.reverse(superClasses);
337 Map last = null;
338 for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
339 Class c = (Class) iter.next();
340 Map methodIndex = (Map) classMethodIndex.get(c);
341 if (methodIndex==last) continue;
342 if (last!=null) copyNonPrivateMethods(last,methodIndex);
343 last = methodIndex;
344 }
345 }
346
347 private void inheritMethods(Collection superClasses, Map classMethodIndex){
348 Map last = null;
349 for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
350 Class c = (Class) iter.next();
351 Map methodIndex = (Map) classMethodIndex.get(c);
352 if (last!=null) {
353 if (methodIndex.size()==0) {
354 classMethodIndex.put(c,last);
355 continue;
356 }
357 copyNonPrivateMethods(last,methodIndex);
358 }
359 last = methodIndex;
360 }
361 }
362
363 private void addNonPrivateMethods(List newList, List oldList) {
364 for (Iterator iter = oldList.iterator(); iter.hasNext();) {
365 MetaMethod element = (MetaMethod) iter.next();
366 if (element.isPrivate()) continue;
367 addMethodToList(newList,element);
368 }
369 }
370
371 /**
372 * @return all the normal instance methods avaiable on this class for the
373 * given name
374 */
375 private List getMethods(Class sender, String name, boolean isCallToSuper) {
376 Map methodIndex;
377 if (isCallToSuper) {
378 methodIndex = (Map) classMethodIndexForSuper.get(sender);
379 } else {
380 methodIndex = (Map) classMethodIndex.get(sender);
381 }
382 List answer;
383 if (methodIndex!=null) {
384 answer = (List) methodIndex.get(name);
385 if (answer == null) answer = Collections.EMPTY_LIST;
386 } else {
387 answer = Collections.EMPTY_LIST;
388 }
389
390 if (!isCallToSuper && GroovyCategorySupport.hasCategoryInAnyThread()) {
391 List used = GroovyCategorySupport.getCategoryMethods(sender, name);
392 if (used != null) {
393 answer = new ArrayList(answer);
394 for (Iterator iter = used.iterator(); iter.hasNext();) {
395 MetaMethod element = (MetaMethod) iter.next();
396 removeMatchingMethod(answer,element);
397 }
398 answer.addAll(used);
399 }
400 }
401 return answer;
402 }
403
404 /**
405 * @return all the normal static methods avaiable on this class for the
406 * given name
407 */
408 private List getStaticMethods(Class sender, String name) {
409 Map methodIndex = (Map) classStaticMethodIndex.get(sender);
410 if (methodIndex == null) return Collections.EMPTY_LIST;
411 List answer = (List) methodIndex.get(name);
412 if (answer == null) return Collections.EMPTY_LIST;
413 return answer;
414 }
415
416 public void addNewInstanceMethod(Method method) {
417 NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(createMetaMethod(method));
418 if (! newGroovyMethodsList.contains(newMethod)){
419 newGroovyMethodsList.add(newMethod);
420 addMetaMethod(newMethod);
421 }
422 }
423
424 public void addNewStaticMethod(Method method) {
425 NewStaticMetaMethod newMethod = new NewStaticMetaMethod(createMetaMethod(method));
426 if (! newGroovyMethodsList.contains(newMethod)){
427 newGroovyMethodsList.add(newMethod);
428 addMetaMethod(newMethod);
429 }
430 }
431
432 private void unwrap(Object[] arguments) {
433 //
434 // Temp code to ignore wrapped parameters
435 // The New MOP will deal with these properly
436 //
437 for (int i = 0; i != arguments.length; i++) {
438 if (arguments[i] instanceof Wrapper) {
439 arguments[i] = ((Wrapper)arguments[i]).unwrap();
440 }
441 }
442 }
443
444
445 /**
446 * Invokes the given method on the object.
447 * @deprecated
448 */
449 public Object invokeMethod(Object object, String methodName, Object[] originalArguments) {
450 return invokeMethod(theClass,object,methodName,originalArguments,false,false);
451 }
452
453
454 /**
455 * Invokes the given method on the object.
456 *
457 */
458 public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
459 checkInitalised();
460 if (object == null) {
461 throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
462 }
463 if (log.isLoggable(Level.FINER)){
464 MetaClassHelper.logMethodCall(object, methodName, originalArguments);
465 }
466 Object[] arguments = originalArguments;
467 if (arguments==null) arguments = EMPTY_ARGUMENTS;
468 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
469 unwrap(arguments);
470
471 MetaMethod method = retrieveMethod(sender, methodName, argClasses, isCallToSuper);
472
473 if (method==null && arguments.length==1 && arguments[0] instanceof List) {
474 Object[] newArguments = ((List) arguments[0]).toArray();
475 Class[] newArgClasses = MetaClassHelper.convertToTypeArray(newArguments);
476 method = retrieveMethod(sender, methodName, newArgClasses, isCallToSuper);
477 if (method!=null) {
478 MethodKey methodKey = new DefaultMethodKey(sender, methodName, argClasses);
479 method = new TransformMetaMethod(method) {
480 public Object invoke(Object object, Object[] arguments) {
481 Object firstArgument = arguments[0];
482 List list = (List) firstArgument;
483 arguments = list.toArray();
484 return super.invoke(object, arguments);
485 }
486 };
487 cacheInstanceMethod(methodKey, method);
488 return invokeMethod(sender,object,methodName, originalArguments, isCallToSuper, fromInsideClass);
489 }
490 }
491
492 boolean isClosure = object instanceof Closure;
493 if (isClosure) {
494 Closure closure = (Closure) object;
495 Object delegate = closure.getDelegate();
496 Object owner = closure.getOwner();
497
498
499 if ("call".equals(methodName) || "doCall".equals(methodName)) {
500 if (object.getClass()==MethodClosure.class) {
501 MethodClosure mc = (MethodClosure) object;
502 methodName = mc.getMethod();
503 Class ownerClass = owner.getClass();
504 if (owner instanceof Class) ownerClass = (Class) owner;
505 MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
506 return ownerMetaClass.invokeMethod(ownerClass,owner,methodName,arguments,false,false);
507 } else if (object.getClass()==CurriedClosure.class) {
508 CurriedClosure cc = (CurriedClosure) object;
509 // change the arguments for an uncurried call
510 arguments = cc.getUncurriedArguments(arguments);
511 Class ownerClass = owner.getClass();
512 if (owner instanceof Class) ownerClass = (Class) owner;
513 MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
514 return ownerMetaClass.invokeMethod(owner,methodName,arguments);
515 }
516 } else if ("curry".equals(methodName)) {
517 return closure.curry(arguments);
518 }
519
520 if (method==null && owner!=closure) {
521 Class ownerClass = owner.getClass();
522 if (owner instanceof Class) ownerClass = (Class) owner;
523 MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
524 method = ownerMetaClass.retrieveMethod(methodName,argClasses);
525 if (method!=null) return ownerMetaClass.invokeMethod(owner,methodName,originalArguments);
526 }
527 if (method==null && delegate!=closure && delegate!=null) {
528 Class delegateClass = delegate.getClass();
529 if (delegate instanceof Class) delegateClass = (Class) delegate;
530 MetaClass delegateMetaClass = registry.getMetaClass(delegateClass);
531 method = delegateMetaClass.retrieveMethod(methodName,argClasses);
532 if (method!=null) return delegateMetaClass.invokeMethod(delegate,methodName,originalArguments);
533 }
534 if (method==null) {
535 // still no methods found, test if delegate or owner are GroovyObjects
536 // and invoke the method on them if so.
537 MissingMethodException last = null;
538 if (owner!=closure && (owner instanceof GroovyObject)) {
539 try {
540 GroovyObject go = (GroovyObject) owner;
541 return go.invokeMethod(methodName,originalArguments);
542 } catch (MissingMethodException mme) {
543 if (last==null) last = mme;
544 }
545 }
546 if (delegate!=closure && (delegate instanceof GroovyObject)) {
547 try {
548 GroovyObject go = (GroovyObject) delegate;
549 return go.invokeMethod(methodName,originalArguments);
550 } catch (MissingMethodException mme) {
551 last = mme;
552 }
553 }
554 if (last!=null) throw last;
555 }
556
557 }
558
559 if (method != null) {
560 return MetaClassHelper.doMethodInvoke(object, method, arguments);
561 } else {
562 // if no method was found, try to find a closure defined as a field of the class and run it
563 try {
564 Object value = this.getProperty(object, methodName);
565 if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this
566 Closure closure = (Closure) value;
567 MetaClass delegateMetaClass = closure.getMetaClass();
568 return delegateMetaClass.invokeMethod(closure.getClass(),closure,"doCall",originalArguments,false,fromInsideClass);
569 }
570 } catch (MissingPropertyException mpe) {}
571
572 throw new MissingMethodException(methodName, theClass, originalArguments, false);
573 }
574 }
575
576 public MetaMethod retrieveMethod(Class sender, String methodName, Class[] arguments, boolean isCallToSuper) {
577 // lets try use the cache to find the method
578 if (GroovyCategorySupport.hasCategoryInAnyThread() && !isCallToSuper) {
579 return pickMethod(sender, methodName, arguments, isCallToSuper);
580 } else {
581 //TODO: add isSuperCall to key
582 MethodKey methodKey = new DefaultMethodKey(sender, methodName, arguments);
583 MetaMethod method = (MetaMethod) methodCache.get(methodKey);
584 if (method == null) {
585 method = pickMethod(sender, methodName, arguments, isCallToSuper);
586 cacheInstanceMethod(methodKey, method);
587 }
588 return method;
589 }
590 }
591
592 protected void cacheInstanceMethod(MethodKey key, MetaMethod method) {
593 if (method != null && method.isCacheable()) {
594 methodCache.put(key, method);
595 }
596 }
597
598 protected void cacheStaticMethod(MethodKey key, MetaMethod method) {
599 if (method != null && method.isCacheable()) {
600 staticMethodCache.put(key, method);
601 }
602 }
603
604
605 public Constructor retrieveConstructor(Class[] arguments) {
606 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, arguments, false);
607 if (constructor != null) {
608 return constructor;
609 }
610 constructor = (Constructor) chooseMethod("<init>", constructors, arguments, true);
611 if (constructor != null) {
612 return constructor;
613 }
614 return null;
615 }
616
617 public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) {
618 MethodKey methodKey = new DefaultMethodKey(theClass, methodName, arguments);
619 MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
620 if (method == null) {
621 method = pickStaticMethod(theClass,methodName, arguments);
622 cacheStaticMethod(methodKey, method);
623 }
624 return method;
625 }
626
627
628
629 /**
630 * pick a method in a strict manner, i.e., without reinterpreting the first List argument.
631 * this method is used only by ClassGenerator for static binding
632 * @param methodName
633 * @param arguments
634 */
635 public MetaMethod pickMethod(Class sender, String methodName, Class[] arguments, boolean isCallToSuper) {
636 MetaMethod method = null;
637 List methods = getMethods(sender,methodName,isCallToSuper);
638 if (methods!=null && !methods.isEmpty()) {
639 method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
640 }
641 return method;
642 }
643
644 public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
645 checkInitalised();
646 if (log.isLoggable(Level.FINER)){
647 MetaClassHelper.logMethodCall(object, methodName, arguments);
648 }
649
650 Class sender = object.getClass();
651 if (object instanceof Class) sender = (Class) object;
652 if (sender!=theClass) {
653 MetaClass mc = registry.getMetaClass(sender);
654 return mc.invokeStaticMethod(sender,methodName,arguments);
655 }
656 if (sender==Class.class) {
657 return invokeMethod(object,methodName,arguments);
658 }
659
660 if (arguments==null) arguments = EMPTY_ARGUMENTS;
661 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
662 unwrap(arguments);
663
664 // lets try use the cache to find the method
665 MethodKey methodKey = new DefaultMethodKey(sender, methodName, argClasses);
666 MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey);
667 if (method == null) {
668 method = pickStaticMethod(sender, methodName, argClasses);
669 cacheStaticMethod(methodKey.createCopy(), method);
670 }
671
672 if (method != null) {
673 return MetaClassHelper.doMethodInvoke(object, method, arguments);
674 }
675
676 throw new MissingMethodException(methodName, sender, arguments, true);
677 }
678
679 private MetaMethod pickStaticMethod(Class sender, String methodName, Class[] arguments) {
680 MetaMethod method = null;
681 List methods = getStaticMethods(sender,methodName);
682
683 if (!methods.isEmpty()) {
684 method = (MetaMethod) chooseMethod(methodName, methods, arguments, false);
685 }
686 if (method == null && theClass != Class.class) {
687 MetaClass classMetaClass = registry.getMetaClass(Class.class);
688 method = classMetaClass.pickMethod(methodName, arguments);
689 }
690 if (method == null) {
691 method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments), true);
692 }
693 return method;
694 }
695
696 public Object invokeConstructor(Object[] arguments) {
697 return invokeConstructor(theClass,arguments,false);
698 }
699
700 public int selectConstructorAndTransformArguments(int numberOfCosntructors, Object[] arguments) {
701 //TODO: that is just a quick prototype, not the real thing!
702 if (numberOfCosntructors != constructors.size()) {
703 throw new IncompatibleClassChangeError("the number of constructors during runtime and compile time for "+
704 this.theClass.getName()+" do not match. Expected "+numberOfCosntructors+" but got "+constructors.size());
705 }
706
707 if (arguments==null) arguments = EMPTY_ARGUMENTS;
708 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
709 unwrap(arguments);
710 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false);
711 if (constructor == null) {
712 constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true);
713 }
714 if (constructor==null) {
715 throw new GroovyRuntimeException(
716 "Could not find matching constructor for: "
717 + theClass.getName()
718 + "("+InvokerHelper.toTypeString(arguments)+")");
719 }
720 List l = new ArrayList(constructors);
721 Comparator comp = new Comparator() {
722 public int compare(Object arg0, Object arg1) {
723 Constructor c0 = (Constructor) arg0;
724 Constructor c1 = (Constructor) arg1;
725 String descriptor0 = BytecodeHelper.getMethodDescriptor(Void.TYPE, c0.getParameterTypes());
726 String descriptor1 = BytecodeHelper.getMethodDescriptor(Void.TYPE, c1.getParameterTypes());
727 return descriptor0.compareTo(descriptor1);
728 }
729 };
730 Collections.sort(l,comp);
731 int found = -1;
732 for (int i=0; i<l.size(); i++) {
733 if (l.get(i)!=constructor) continue;
734 found = i;
735 break;
736 }
737 // NOTE: must be changed to "1 |" if constructor was vargs
738 int ret = 0 | (found << 8);
739 return ret;
740 }
741
742 /**
743 * checks if the initialisation of the class id complete.
744 * This method should be called as a form of assert, it is no
745 * way to test if there is still initialisation work to be done.
746 * Such logic must be implemented in a different way.
747 * @throws IllegalStateException if the initialisation is incomplete yet
748 */
749 protected void checkInitalised() {
750 if (!isInitialized())
751 throw new IllegalStateException(
752 "initialize must be called for meta " +
753 "class of "+ theClass +
754 "("+this.getClass() + ") " +
755 "to complete initialisation process " +
756 "before any invocation or field/property " +
757 "access can be done");
758 }
759
760 private Object invokeConstructor(Class at, Object[] arguments, boolean setAccessible) {
761 checkInitalised();
762 if (arguments==null) arguments = EMPTY_ARGUMENTS;
763 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);
764 unwrap(arguments);
765 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false);
766 if (constructor != null) {
767 return doConstructorInvoke(at, constructor, arguments, true);
768 }
769 constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true);
770 if (constructor != null) {
771 return doConstructorInvoke(at, constructor, arguments, true);
772 }
773
774 if (arguments.length == 1) {
775 Object firstArgument = arguments[0];
776 if (firstArgument instanceof Map) {
777 constructor = (Constructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false);
778 if (constructor != null) {
779 Object bean = doConstructorInvoke(at, constructor, MetaClassHelper.EMPTY_ARRAY, true);
780 setProperties(bean, ((Map) firstArgument));
781 return bean;
782 }
783 }
784 }
785 throw new GroovyRuntimeException(
786 "Could not find matching constructor for: "
787 + theClass.getName()
788 + "("+InvokerHelper.toTypeString(arguments)+")");
789 }
790
791 /**
792 * Sets a number of bean properties from the given Map where the keys are
793 * the String names of properties and the values are the values of the
794 * properties to set
795 */
796 public void setProperties(Object bean, Map map) {
797 checkInitalised();
798 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
799 Map.Entry entry = (Map.Entry) iter.next();
800 String key = entry.getKey().toString();
801
802 Object value = entry.getValue();
803 setProperty(bean, key, value);
804 }
805 }
806
807 /**
808 * @return the given property's value on the object
809 */
810 public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) {
811 checkInitalised();
812
813 //----------------------------------------------------------------------
814 // handling of static
815 //----------------------------------------------------------------------
816 boolean isStatic = theClass != Class.class && object instanceof Class;
817 if (isStatic && object != theClass) {
818 MetaClass mc = registry.getMetaClass((Class) object);
819 return mc.getProperty(sender,object,name,useSuper,false);
820 }
821
822 MetaMethod method = null;
823 Object[] arguments = EMPTY_ARGUMENTS;
824
825 //----------------------------------------------------------------------
826 // getter
827 //----------------------------------------------------------------------
828 MetaProperty mp = getMetaProperty(sender,name,useSuper, isStatic);
829 if (mp != null) {
830 if (mp instanceof MetaBeanProperty) {
831 MetaBeanProperty mbp = (MetaBeanProperty) mp;
832 method = mbp.getGetter();
833 mp = mbp.getField();
834 }
835 }
836
837 // check for a category method named like a getter
838 if (method==null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInAnyThread()) {
839 String getterName = "get"+MetaClassHelper.capitalize(name);
840 method = getCategoryMethodGetter(sender,getterName,false);
841 }
842
843 //----------------------------------------------------------------------
844 // field
845 //----------------------------------------------------------------------
846 if (method==null && mp!=null) {
847 return mp.getProperty(object);
848 }
849
850
851 //----------------------------------------------------------------------
852 // generic get method
853 //----------------------------------------------------------------------
854 // check for a generic get method provided through a category
855 if (method==null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInAnyThread()) {
856 method = getCategoryMethodGetter(sender,"get",true);
857 if (method!=null) arguments = new Object[]{name};
858 }
859
860 // the generic method is valid, if available (!=null), if static or
861 // if it is not static and we do no static access
862 if (method==null && genericGetMethod != null && !(!genericGetMethod.isStatic() && isStatic)) {
863 arguments = new Object[]{ name };
864 method = genericGetMethod;
865 }
866
867 //----------------------------------------------------------------------
868 // special cases
869 //----------------------------------------------------------------------
870 if (method==null) {
871 /** todo these special cases should be special MetaClasses maybe */
872 if (theClass != Class.class && object instanceof Class) {
873 MetaClass mc = registry.getMetaClass(Class.class);
874 return mc.getProperty(Class.class,object,name,useSuper,false);
875 } else if (object instanceof Collection) {
876 return DefaultGroovyMethods.getAt((Collection) object, name);
877 } else if (object instanceof Object[]) {
878 return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name);
879 } else {
880 MetaMethod addListenerMethod = (MetaMethod) listeners.get(name);
881 if (addListenerMethod != null) {
882 //TODO: one day we could try return the previously registered Closure listener for easy removal
883 return null;
884 }
885 }
886 } else {
887
888 //----------------------------------------------------------------------
889 // executing the getter method
890 //----------------------------------------------------------------------
891 return MetaClassHelper.doMethodInvoke(object,method,arguments);
892 }
893
894 //----------------------------------------------------------------------
895 // error due to missing method/field
896 //----------------------------------------------------------------------
897 throw new MissingPropertyException(name, theClass);
898 }
899
900 private MetaMethod getCategoryMethodGetter(Class sender, String name, boolean useLongVersion) {
901 List possibleGenericMethods = GroovyCategorySupport.getCategoryMethods(sender, name);
902 if (possibleGenericMethods != null) {
903 for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) {
904 MetaMethod mmethod = (MetaMethod) iter.next();
905 Class[] paramTypes = mmethod.getParameterTypes();
906 if (useLongVersion) {
907 if (paramTypes.length==1 && paramTypes[0] == String.class) {
908 return mmethod;
909 }
910 } else {
911 if (paramTypes.length==0) return mmethod;
912 }
913 }
914 }
915 return null;
916 }
917
918 private MetaMethod getCategoryMethodSetter(Class sender, String name, boolean useLongVersion) {
919 List possibleGenericMethods = GroovyCategorySupport.getCategoryMethods(sender, name);
920 if (possibleGenericMethods != null) {
921 for (Iterator iter = possibleGenericMethods.iterator(); iter.hasNext();) {
922 MetaMethod mmethod = (MetaMethod) iter.next();
923 Class[] paramTypes = mmethod.getParameterTypes();
924 if (useLongVersion) {
925 if (paramTypes.length==2 && paramTypes[0] == String.class) {
926 return mmethod;
927 }
928 } else {
929 if (paramTypes.length==1) return mmethod;
930 }
931 }
932 }
933 return null;
934 }
935
936 /**
937 * Get all the properties defined for this type
938 * @return a list of MetaProperty objects
939 */
940 public List getProperties() {
941 checkInitalised();
942 Map propertyMap = (Map) classPropertyIndex.get(theClass);
943 // simply return the values of the metaproperty map as a List
944 List ret = new ArrayList(propertyMap.size());
945 for (Iterator iter = propertyMap.values().iterator(); iter.hasNext();) {
946 MetaProperty element = (MetaProperty) iter.next();
947 if (element instanceof MetaFieldProperty) continue;
948 // filter out DGM beans
949 if (element instanceof MetaBeanProperty) {
950 MetaBeanProperty mp = (MetaBeanProperty) element;
951 boolean setter = true;
952 boolean getter = true;
953 if (mp.getGetter()==null || mp.getGetter() instanceof NewInstanceMetaMethod) {
954 getter=false;
955 }
956 if (mp.getSetter()==null || mp.getSetter() instanceof NewInstanceMetaMethod) {
957 setter=false;
958 }
959 if (!setter && !getter) continue;
960 if (!setter && mp.getSetter()!=null) {
961 element = new MetaBeanProperty(mp.getName(),mp.getType(),mp.getGetter(),null);
962 }
963 if (!getter && mp.getGetter()!=null) {
964 element = new MetaBeanProperty(mp.getName(),mp.getType(), null, mp.getSetter());
965 }
966 }
967 ret.add(element);
968 }
969 return ret;
970 }
971
972 private MetaMethod findPropertyMethod(List methods, boolean isGetter) {
973 LinkedList ret = new LinkedList();
974 for (Iterator iter = methods.iterator(); iter.hasNext();) {
975 MetaMethod element = (MetaMethod) iter.next();
976 if ( !isGetter &&
977 //(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) &&
978 element.getParameterTypes().length == 1)
979 {
980 ret.add(element);
981 }
982 if ( isGetter &&
983 !(element.getReturnType() == Void.class || element.getReturnType() == Void.TYPE) &&
984 element.getParameterTypes().length == 0)
985 {
986 ret.add(element);
987 }
988 }
989 if (ret.size() == 0) return null;
990 if (ret.size() == 1) return (MetaMethod) ret.getFirst();
991
992 // we found multiple matching methods
993 // this is a problem, because we can use only one
994 // if it is a getter, then use the most general return
995 // type to decide which method to use. If it is a setter
996 // we use the type of the first parameter
997 MetaMethod method = null;
998 int distance = -1;
999 for (Iterator iter = ret.iterator(); iter.hasNext();) {
1000 MetaMethod element = (MetaMethod) iter.next();
1001 Class c;
1002 if (isGetter) {
1003 c = element.getReturnType();
1004 } else {
1005 c = element.getParameterTypes()[0];
1006 }
1007 int localDistance = distanceToObject(c);
1008 //TODO: maybe implement the case localDistance==distance
1009 if (distance==-1 || distance>localDistance) {
1010 distance = localDistance;
1011 method = element;
1012 }
1013 }
1014 return method;
1015 }
1016
1017 private static int distanceToObject(Class c) {
1018 int count;
1019 for (count=0; c!=null; count++) {
1020 c=c.getSuperclass();
1021 }
1022 return count;
1023 }
1024
1025
1026 /**
1027 * This will build up the property map (Map of MetaProperty objects, keyed on
1028 * property name).
1029 */
1030 private void setupProperties(PropertyDescriptor[] propertyDescriptors) {
1031 LinkedList superClasses = getSuperClasses();
1032 Set interfaces = new HashSet();
1033 makeInterfaceSet(theClass,interfaces);
1034
1035 // if this an Array, then add the special read-only "length" property
1036 if (theClass.isArray()) {
1037 Map map = new HashMap();
1038 map.put("length", arrayLengthProperty);
1039 classPropertyIndex.put(theClass,map);
1040 }
1041
1042 inheritStaticInterfaceFields(superClasses, interfaces);
1043 inheritFields(superClasses);
1044 applyPropertyDescriptors(propertyDescriptors);
1045
1046 applyStrayPropertyMethods(superClasses,classMethodIndex,classPropertyIndex);
1047 applyStrayPropertyMethods(superClasses,classMethodIndexForSuper,classPropertyIndexForSuper);
1048
1049 copyClassPropertyIndexForSuper();
1050 makeStaticPropertyIndex();
1051 }
1052
1053 private void makeStaticPropertyIndex() {
1054 Map propertyMap = (Map) classPropertyIndex.get(theClass);
1055 for (Iterator iter = propertyMap.entrySet().iterator(); iter.hasNext();) {
1056 Map.Entry entry = (Map.Entry) iter.next();
1057 MetaProperty mp = (MetaProperty) entry.getValue();
1058 if (mp instanceof MetaFieldProperty) {
1059 MetaFieldProperty mfp = (MetaFieldProperty) mp;
1060 if (!mfp.isStatic()) continue;
1061 } else if (mp instanceof MetaBeanProperty) {
1062 MetaBeanProperty mbp = (MetaBeanProperty) mp;
1063 boolean getter = mbp.getGetter()==null || mbp.getGetter().isStatic();
1064 boolean setter = mbp.getSetter()==null || mbp.getSetter().isStatic();
1065 boolean field = mbp.getField()==null || mbp.getField().isStatic();
1066
1067 if (!getter && !setter && !field) {
1068 continue;
1069 } else if (setter && getter) {
1070 if (field) {
1071 mp = mbp; // nothing to do
1072 } else {
1073 mp = new MetaBeanProperty(mbp.getName(),mbp.getType(),mbp.getGetter(),mbp.getSetter());
1074 }
1075 } else if (getter && !setter) {
1076 if (mbp.getGetter()==null) {
1077 mp = mbp.getField();
1078 } else {
1079 MetaBeanProperty newmp = new MetaBeanProperty(mbp.getName(),mbp.getType(),mbp.getGetter(),null);
1080 if (field) newmp.setField(mbp.getField());
1081 mp = newmp;
1082 }
1083 } else if (setter && !getter) {
1084 if (mbp.getSetter()==null) {
1085 mp = mbp.getField();
1086 } else {
1087 MetaBeanProperty newmp = new MetaBeanProperty(mbp.getName(),mbp.getType(),null,mbp.getSetter());
1088 if (field) newmp.setField(mbp.getField());
1089 mp = newmp;
1090 }
1091 } else if (field) {
1092 mp = mbp.getField();
1093 }
1094 } else {
1095 continue; // ignore all other types
1096 }
1097 if (mp==null) continue;
1098 staticPropertyIndex.put(entry.getKey(),mp);
1099 }
1100
1101 }
1102
1103 private void copyClassPropertyIndexForSuper() {
1104 for (Iterator iter = classPropertyIndex.entrySet().iterator(); iter.hasNext();) {
1105 Map.Entry entry = (Map.Entry) iter.next();
1106 HashMap newVal = new HashMap((Map)entry.getValue());
1107 classPropertyIndexForSuper.put(entry.getKey(),newVal);
1108 }
1109 }
1110
1111 private Map getMap2MapNotNull(Map m, Object key) {
1112 Map ret = (Map) m.get(key);
1113 if (ret==null) {
1114 ret = new HashMap();
1115 m.put(key,ret);
1116 }
1117 return ret;
1118 }
1119
1120 private void inheritStaticInterfaceFields(LinkedList superClasses, Set interfaces) {
1121 for (Iterator interfaceIter = interfaces.iterator(); interfaceIter.hasNext();) {
1122 Class iclass = (Class) interfaceIter.next();
1123 Map iPropertyIndex = getMap2MapNotNull(classPropertyIndex,iclass);
1124 addFields(iclass,iPropertyIndex);
1125 for (Iterator classIter = superClasses.iterator(); classIter.hasNext();) {
1126 Class sclass = (Class) classIter.next();
1127 if (! iclass.isAssignableFrom(sclass)) continue;
1128 Map sPropertyIndex = getMap2MapNotNull(classPropertyIndex,sclass);
1129 copyNonPrivateFields(iPropertyIndex,sPropertyIndex);
1130 }
1131 }
1132 }
1133
1134 private void inheritFields(LinkedList superClasses) {
1135 Map last = null;
1136 for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
1137 Class klass = (Class) iter.next();
1138 Map propertyIndex = getMap2MapNotNull(classPropertyIndex,klass);
1139 if (last != null) {
1140 copyNonPrivateFields(last,propertyIndex);
1141 }
1142 last = propertyIndex;
1143 addFields(klass,propertyIndex);
1144 }
1145 }
1146
1147 private void addFields(final Class klass, Map propertyIndex) {
1148 Field[] fields = (Field[]) AccessController.doPrivileged(new PrivilegedAction() {
1149 public Object run() {
1150 return klass.getDeclaredFields();
1151 }
1152 });
1153 for(int i = 0; i < fields.length; i++) {
1154 MetaFieldProperty mfp = new MetaFieldProperty(fields[i]);
1155 propertyIndex.put(fields[i].getName(), mfp);
1156 }
1157 }
1158
1159 private void copyNonPrivateFields(Map from, Map to) {
1160 for (Iterator iter = from.entrySet().iterator(); iter.hasNext();) {
1161 Map.Entry entry = (Map.Entry) iter.next();
1162 MetaFieldProperty mfp = (MetaFieldProperty) entry.getValue();
1163 if (!Modifier.isPublic(mfp.getModifiers()) && !Modifier.isProtected(mfp.getModifiers())) continue;
1164 to.put(entry.getKey(),mfp);
1165 }
1166 }
1167
1168 private void applyStrayPropertyMethods(LinkedList superClasses, Map classMethodIndex, Map classPropertyIndex) {
1169 // now look for any stray getters that may be used to define a property
1170 for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
1171 Class klass = (Class) iter.next();
1172 Map methodIndex = (Map) classMethodIndex.get(klass);
1173 Map propertyIndex = getMap2MapNotNull(classPropertyIndex,klass);
1174 for (Iterator nameMethodIterator = methodIndex.entrySet().iterator(); nameMethodIterator.hasNext();) {
1175 Map.Entry entry = (Map.Entry) nameMethodIterator.next();
1176 String methodName = (String) entry.getKey();
1177 // name too sort?
1178 if (methodName.length() < 4) continue;
1179 //possible getter/setter
1180 boolean isGetter = methodName.startsWith("get");
1181 boolean isSetter = methodName.startsWith("set");
1182 if (!isGetter && !isSetter) continue;
1183
1184 // get the name of the property
1185 String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);
1186 MetaMethod propertyMethod = findPropertyMethod((List) entry.getValue(), isGetter);
1187 if (propertyMethod==null) continue;
1188
1189 createMetaBeanProperty(propertyIndex, propName, isGetter, propertyMethod);
1190 }
1191 }
1192 }
1193
1194 private void createMetaBeanProperty(Map propertyIndex, String propName, boolean isGetter, MetaMethod propertyMethod){
1195 // is this property already accounted for?
1196 MetaProperty mp = (MetaProperty) propertyIndex.get(propName);
1197 if (mp == null) {
1198 if (isGetter) {
1199 mp = new MetaBeanProperty(propName,
1200 propertyMethod.getReturnType(),
1201 propertyMethod, null);
1202 } else {
1203 //isSetter
1204 mp = new MetaBeanProperty(propName,
1205 propertyMethod.getParameterTypes()[0],
1206 null, propertyMethod);
1207 }
1208 } else {
1209 MetaBeanProperty mbp;
1210 MetaFieldProperty mfp;
1211 if (mp instanceof MetaBeanProperty) {
1212 mbp = (MetaBeanProperty) mp;
1213 mfp = mbp.getField();
1214 } else if (mp instanceof MetaFieldProperty){
1215 mfp = (MetaFieldProperty) mp;
1216 mbp = new MetaBeanProperty(propName,
1217 mfp.getType(),
1218 null, null);
1219 } else {
1220 throw new GroovyBugError("unknown MetaProperty class used. Class is "+mp.getClass());
1221 }
1222 // we may have already found one for this name
1223 if (isGetter && mbp.getGetter()==null) {
1224 mbp.setGetter(propertyMethod);
1225 } else if (!isGetter && mbp.getSetter()==null) {
1226 mbp.setSetter(propertyMethod);
1227 }
1228 mbp.setField(mfp);
1229 mp = mbp;
1230 }
1231 propertyIndex.put(propName, mp);
1232 }
1233
1234 private void applyPropertyDescriptors(PropertyDescriptor[] propertyDescriptors) {
1235 Map propertyMap = (Map) classPropertyIndex.get(theClass);
1236 // now iterate over the map of property descriptors and generate
1237 // MetaBeanProperty objects
1238 for(int i=0; i<propertyDescriptors.length; i++) {
1239 PropertyDescriptor pd = propertyDescriptors[i];
1240
1241 // skip if the property type is unknown (this seems to be the case if the
1242 // property descriptor is based on a setX() method that has two parameters,
1243 // which is not a valid property)
1244 if(pd.getPropertyType() == null)
1245 continue;
1246
1247 // get the getter method
1248 Method method = pd.getReadMethod();
1249 MetaMethod getter;
1250 if(method != null)
1251 getter = findMethod(method);
1252 else
1253 getter = null;
1254
1255 // get the setter method
1256 MetaMethod setter;
1257 method = pd.getWriteMethod();
1258 if(method != null)
1259 setter = findMethod(method);
1260 else
1261 setter = null;
1262
1263 // now create the MetaProperty object
1264 MetaBeanProperty mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter);
1265
1266 //keep field
1267 MetaFieldProperty field = null;
1268 MetaProperty old = (MetaProperty) propertyMap.get(pd.getName());
1269 if (old!=null) {
1270 if (old instanceof MetaBeanProperty) {
1271 field = ((MetaBeanProperty) old).getField();
1272 } else {
1273 field = (MetaFieldProperty) old;
1274 }
1275 mp.setField(field);
1276 }
1277
1278 // put it in the list
1279 // this will overwrite a possible field property
1280 propertyMap.put(pd.getName(), mp);
1281 }
1282 }
1283
1284 /**
1285 * Sets the property value on an object
1286 */
1287 public void setProperty(Class sender,Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) {
1288 checkInitalised();
1289
1290 //----------------------------------------------------------------------
1291 // handling of static
1292 //----------------------------------------------------------------------
1293 boolean isStatic = theClass != Class.class && object instanceof Class;
1294 if (isStatic && object != theClass) {
1295 MetaClass mc = registry.getMetaClass((Class) object);
1296 mc.getProperty(sender,object,name,useSuper,fromInsideClass);
1297 return;
1298 }
1299
1300 //----------------------------------------------------------------------
1301 // Unwrap wrapped values fo now - the new MOP will handle them properly
1302 //----------------------------------------------------------------------
1303 if (newValue instanceof Wrapper) newValue = ((Wrapper)newValue).unwrap();
1304
1305
1306
1307 MetaMethod method = null;
1308 Object[] arguments = null;
1309
1310 //----------------------------------------------------------------------
1311 // setter
1312 //----------------------------------------------------------------------
1313 MetaProperty mp = getMetaProperty(sender,name,useSuper, isStatic);
1314 MetaProperty field = null;
1315 if (mp != null) {
1316 if (mp instanceof MetaBeanProperty) {
1317 MetaBeanProperty mbp = (MetaBeanProperty) mp;
1318 method = mbp.getSetter();
1319 if (method!=null) arguments = new Object[] { newValue };
1320 field = mbp.getField();
1321 } else {
1322 field = mp;
1323 }
1324 }
1325
1326 // check for a category method named like a setter
1327 if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInAnyThread()) {
1328 String getterName = "set"+MetaClassHelper.capitalize(name);
1329 method = getCategoryMethodSetter(sender,getterName,false);
1330 if (method!=null) arguments = new Object[] { newValue };
1331 }
1332
1333 //----------------------------------------------------------------------
1334 // listener method
1335 //----------------------------------------------------------------------
1336 boolean ambigousListener = false;
1337 boolean usesProxy = false;
1338 if (method==null) {
1339 method = (MetaMethod) listeners.get(name);
1340 ambigousListener = method == AMBIGOUS_LISTENER_METHOD;
1341 if ( method != null &&
1342 !ambigousListener &&
1343 newValue instanceof Closure)
1344 {
1345 // lets create a dynamic proxy
1346 Object proxy =
1347 MetaClassHelper.createListenerProxy(method.getParameterTypes()[0], name, (Closure) newValue);
1348 arguments = new Object[] { proxy };
1349 newValue = proxy;
1350 usesProxy = true;
1351 } else {
1352 method = null;
1353 }
1354 }
1355
1356 //----------------------------------------------------------------------
1357 // field
1358 //----------------------------------------------------------------------
1359 if (method==null && field!=null) {
1360 field.setProperty(object,newValue);
1361 return;
1362 }
1363
1364 //----------------------------------------------------------------------
1365 // generic set method
1366 //----------------------------------------------------------------------
1367 // check for a generic get method provided through a category
1368 if (method==null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInAnyThread()) {
1369 method = getCategoryMethodSetter(sender,"set",true);
1370 if (method!=null) arguments = new Object[]{name,newValue};
1371 }
1372
1373 // the generic method is valid, if available (!=null), if static or
1374 // if it is not static and we do no static access
1375 if (method==null && genericSetMethod != null && !(!genericSetMethod.isStatic() && isStatic)) {
1376 arguments = new Object[]{ name, newValue };
1377 method = genericSetMethod;
1378 }
1379
1380 //----------------------------------------------------------------------
1381 // executing the getter method
1382 //----------------------------------------------------------------------
1383 if (method!=null) {
1384 if (arguments.length==1) {
1385 newValue = DefaultTypeTransformation.castToType(
1386 newValue,
1387 method.getParameterTypes()[0]);
1388 arguments[0] = newValue;
1389 } else {
1390 newValue = DefaultTypeTransformation.castToType(
1391 newValue,
1392 method.getParameterTypes()[1]);
1393 arguments[1] = newValue;
1394 }
1395 MetaClassHelper.doMethodInvoke(object,method,arguments);
1396 return;
1397 }
1398
1399 //----------------------------------------------------------------------
1400 // error due to missing method/field
1401 //----------------------------------------------------------------------
1402 if (ambigousListener){
1403 throw new GroovyRuntimeException("There are multiple listeners for the property "+name+". Please do not use the bean short form to access this listener.");
1404 }
1405 throw new MissingPropertyException(name, theClass);
1406 }
1407
1408 private MetaProperty getMetaProperty(Class clazz, String name, boolean useSuper, boolean useStatic) {
1409 Map propertyMap;
1410 if (useStatic) {
1411 propertyMap = staticPropertyIndex;
1412 } else if (useSuper){
1413 propertyMap = (Map) classPropertyIndexForSuper.get(clazz);
1414 } else {
1415 propertyMap = (Map) classPropertyIndex.get(clazz);
1416 }
1417 if (propertyMap==null) {
1418 if (clazz!=theClass) {
1419 return getMetaProperty(theClass,name,useSuper, useStatic);
1420 } else {
1421 return null;
1422 }
1423 }
1424 return (MetaProperty) propertyMap.get(name);
1425 }
1426
1427
1428 /**
1429 * Looks up the given attribute (field) on the given object
1430 */
1431 public Object getAttribute(Class sender, Object object, String attribute, boolean useSuper, boolean fromInsideClass) {
1432 checkInitalised();
1433
1434 boolean isStatic = theClass != Class.class && object instanceof Class;
1435 if (isStatic && object != theClass) {
1436 MetaClass mc = registry.getMetaClass((Class) object);
1437 return mc.getAttribute(sender,object,attribute,useSuper);
1438 }
1439
1440 MetaProperty mp = getMetaProperty(sender,attribute,useSuper, isStatic);
1441
1442 if (mp != null) {
1443 if (mp instanceof MetaBeanProperty) {
1444 MetaBeanProperty mbp = (MetaBeanProperty) mp;
1445 mp = mbp.getField();
1446 }
1447 try {
1448 // delegate the get operation to the metaproperty
1449 if (mp != null) return mp.getProperty(object);
1450 } catch(Exception e) {
1451 throw new GroovyRuntimeException("Cannot read field: " + attribute,e);
1452 }
1453 }
1454
1455 throw new MissingFieldException(attribute, theClass);
1456 }
1457
1458 /**
1459 * Sets the given attribute (field) on the given object
1460 */
1461 public void setAttribute(Class sender, Object object, String attribute, Object newValue, boolean useSuper, boolean fromInsideClass) {
1462 checkInitalised();
1463
1464 boolean isStatic = theClass != Class.class && object instanceof Class;
1465 if (isStatic && object != theClass) {
1466 MetaClass mc = registry.getMetaClass((Class) object);
1467 mc.setAttribute(sender,object,attribute,newValue,useSuper,fromInsideClass);
1468 return;
1469 }
1470
1471 MetaProperty mp = getMetaProperty(sender,attribute,useSuper, isStatic);
1472
1473 if (mp != null) {
1474 if (mp instanceof MetaBeanProperty) {
1475 MetaBeanProperty mbp = (MetaBeanProperty) mp;
1476 mp = mbp.getField();
1477 }
1478 if (mp != null) {
1479 mp.setProperty(object,newValue);
1480 return;
1481 }
1482 }
1483
1484 throw new MissingFieldException(attribute, theClass);
1485 }
1486
1487 public ClassNode getClassNode() {
1488 if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) {
1489 // lets try load it from the classpath
1490 String className = theClass.getName();
1491 String groovyFile = className;
1492 int idx = groovyFile.indexOf('$');
1493 if (idx > 0) {
1494 groovyFile = groovyFile.substring(0, idx);
1495 }
1496 groovyFile = groovyFile.replace('.', '/') + ".groovy";
1497
1498 //System.out.println("Attempting to load: " + groovyFile);
1499 URL url = theClass.getClassLoader().getResource(groovyFile);
1500 if (url == null) {
1501 url = Thread.currentThread().getContextClassLoader().getResource(groovyFile);
1502 }
1503 if (url != null) {
1504 try {
1505
1506 /**
1507 * todo there is no CompileUnit in scope so class name
1508 * checking won't work but that mostly affects the bytecode
1509 * generation rather than viewing the AST
1510 */
1511 CompilationUnit.ClassgenCallback search = new CompilationUnit.ClassgenCallback() {
1512 public void call( ClassVisitor writer, ClassNode node ) {
1513 if( node.getName().equals(theClass.getName()) ) {
1514 MetaClassImpl.this.classNode = node;
1515 }
1516 }
1517 };
1518
1519 final ClassLoader parent = theClass.getClassLoader();
1520 GroovyClassLoader gcl = (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
1521 public Object run() {
1522 return new GroovyClassLoader(parent);
1523 }
1524 });
1525 CompilationUnit unit = new CompilationUnit( );
1526 unit.setClassgenCallback( search );
1527 unit.addSource( url );
1528 unit.compile( Phases.CLASS_GENERATION );
1529 }
1530 catch (Exception e) {
1531 throw new GroovyRuntimeException("Exception thrown parsing: " + groovyFile + ". Reason: " + e, e);
1532 }
1533 }
1534
1535 }
1536 return classNode;
1537 }
1538
1539 public String toString() {
1540 return super.toString() + "[" + theClass + "]";
1541 }
1542
1543 // Implementation methods
1544 //-------------------------------------------------------------------------
1545
1546 /**
1547 * Adds all the methods declared in the given class to the metaclass
1548 * ignoring any matching methods already defined by a derived class
1549 *
1550 * @param theClass
1551 */
1552 private void addMethods(final Class theClass) {
1553 Map methodIndex = (Map) classMethodIndex.get(theClass);
1554 if (methodIndex==null) {
1555 methodIndex = new HashMap();
1556 classMethodIndex.put(theClass,methodIndex);
1557 }
1558 // add methods directly declared in the class
1559 Method[] methodArray = (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
1560 public Object run() {
1561 return theClass.getDeclaredMethods();
1562 }
1563 });
1564 for (int i = 0; i < methodArray.length; i++) {
1565 Method reflectionMethod = methodArray[i];
1566 if ( reflectionMethod.getName().indexOf('+') >= 0 ) {
1567 // Skip Synthetic methods inserted by JDK 1.5 compilers and later
1568 continue;
1569 } else if (Modifier.isAbstract(reflectionMethod.getModifiers())) {
1570 continue;
1571 }
1572 MetaMethod method = createMetaMethod(reflectionMethod);
1573 addMetaMethod(method);
1574 }
1575 // add methods declared by DGM
1576 List methods = registry.getInstanceMethods();
1577 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1578 Method element = (Method) iter.next();
1579 if (element.getParameterTypes()[0]!=theClass) continue;
1580 addNewInstanceMethod(element);
1581 }
1582 // add static methods declared by DGM
1583 methods = registry.getStaticMethods();
1584 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1585 Method element = (Method) iter.next();
1586 if (element.getParameterTypes()[0]!=theClass) continue;
1587 addNewStaticMethod(element);
1588 }
1589 }
1590
1591 private void addToClassMethodIndex(MetaMethod method, Map classMethodIndex) {
1592 Map methodIndex = (Map) classMethodIndex.get(method.getDeclaringClass());
1593 if (methodIndex==null) {
1594 methodIndex = new HashMap();
1595 classMethodIndex.put(method.getDeclaringClass(),methodIndex);
1596 }
1597 String name = method.getName();
1598 List list = (List) methodIndex.get(name);
1599 if (list == null) {
1600 list = new ArrayList();
1601 methodIndex.put(name, list);
1602 list.add(method);
1603 } else {
1604 addMethodToList(list,method);
1605 }
1606 }
1607
1608 /**
1609 * adds a MetaMethod to this class. WARNING: this method will not
1610 * do the neccessary steps for multimethod logic and using this
1611 * method doesn't mean, that a method added here is replacing another
1612 * method from a parent class completely. These steps are usually done
1613 * by initalize, which means if you need these steps, you have to add
1614 * the method before running initialize the first time.
1615 * @see #initialize()
1616 * @param method the MetaMethod
1617 */
1618 protected void addMetaMethod(MetaMethod method) {
1619 if (isInitialized()) {
1620 throw new RuntimeException("Already initialized, cannot add new method: " + method);
1621 }
1622 if (isGenericGetMethod(method) && genericGetMethod == null) {
1623 genericGetMethod = method;
1624 }
1625 else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) {
1626 genericSetMethod = method;
1627 }
1628 if (method.isStatic()) {
1629 addToClassMethodIndex(method,classStaticMethodIndex);
1630 }
1631 addToClassMethodIndex(method,classMethodIndex);
1632 }
1633
1634 protected boolean isInitialized(){
1635 return initialized;
1636 }
1637
1638 private void addMethodToList(List list, MetaMethod method) {
1639 MetaMethod match = removeMatchingMethod(list,method);
1640 if (match==null) {
1641 list.add(method);
1642 } else if (match.isPrivate()){
1643 // do not overwrite private methods
1644 // Note: private methods from parent classes are not shown here,
1645 // but when doing the multimethod connection step, we overwrite
1646 // methods of the parent class with methods of a subclass and
1647 // in that case we want to keep the private methods
1648 list.add(match);
1649 } else {
1650 Class methodC = method.getDeclaringClass();
1651 Class matchC = match.getDeclaringClass();
1652 if (methodC == matchC){
1653 if (method instanceof NewInstanceMetaMethod) {
1654 // let DGM replace existing methods
1655 list.add(method);
1656 } else {
1657 list.add(match);
1658 }
1659 } else if (MetaClassHelper.isAssignableFrom(methodC,matchC)){
1660 list.add(match);
1661 } else {
1662 list.add(method);
1663 }
1664 }
1665 }
1666
1667 /**
1668 * remove a method of the same matching prototype was found in the list
1669 */
1670 private MetaMethod removeMatchingMethod(List list, MetaMethod method) {
1671 for (Iterator iter = list.iterator(); iter.hasNext();) {
1672 MetaMethod aMethod = (MetaMethod) iter.next();
1673 Class[] params1 = aMethod.getParameterTypes();
1674 Class[] params2 = method.getParameterTypes();
1675 if (params1.length == params2.length) {
1676 boolean matches = true;
1677 for (int i = 0; i < params1.length; i++) {
1678 if (params1[i] != params2[i]) {
1679 matches = false;
1680 break;
1681 }
1682 }
1683 if (matches) {
1684 iter.remove();
1685 return (MetaMethod) aMethod;
1686 }
1687 }
1688 }
1689 return null;
1690 }
1691
1692 /**
1693 * @return the matching method which should be found
1694 */
1695 private MetaMethod findMethod(Method aMethod) {
1696 List methods = getMethods(theClass,aMethod.getName(),false);
1697 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1698 MetaMethod method = (MetaMethod) iter.next();
1699 if (method.isMethod(aMethod)) {
1700 return method;
1701 }
1702 }
1703 //log.warning("Creating reflection based dispatcher for: " + aMethod);
1704 return new ReflectionMetaMethod(aMethod);
1705 }
1706
1707 /**
1708 * @return the getter method for the given object
1709 */
1710 private MetaMethod findGetter(Object object, String name) {
1711 List methods = getMethods(theClass,name,false);
1712 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1713 MetaMethod method = (MetaMethod) iter.next();
1714 if (method.getParameterTypes().length == 0) {
1715 return method;
1716 }
1717 }
1718 return null;
1719 }
1720
1721 /**
1722 * @return the Method of the given name with no parameters or null
1723 */
1724 private MetaMethod findStaticGetter(Class type, String name) {
1725 List methods = getStaticMethods(type, name);
1726 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1727 MetaMethod method = (MetaMethod) iter.next();
1728 if (method.getParameterTypes().length == 0) {
1729 return method;
1730 }
1731 }
1732
1733 /** todo dirty hack - don't understand why this code is necessary - all methods should be in the allMethods list! */
1734 try {
1735 Method method = type.getMethod(name, MetaClassHelper.EMPTY_TYPE_ARRAY);
1736 if ((method.getModifiers() & Modifier.STATIC) != 0) {
1737 return findMethod(method);
1738 }
1739 else {
1740 return null;
1741 }
1742 }
1743 catch (Exception e) {
1744 return null;
1745 }
1746 }
1747
1748 private static Object doConstructorInvoke(final Class at, Constructor constructor, Object[] argumentArray, boolean setAccessible) {
1749 if (log.isLoggable(Level.FINER)) {
1750 MetaClassHelper.logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray);
1751 }
1752
1753 if (setAccessible) {
1754 // To fix JIRA 435
1755 // Every constructor should be opened to the accessible classes.
1756 final boolean accessible = MetaClassHelper.accessibleToConstructor(at, constructor);
1757 final Constructor ctor = constructor;
1758 AccessController.doPrivileged(new PrivilegedAction() {
1759 public Object run() {
1760 ctor.setAccessible(accessible);
1761 return null;
1762 }
1763 });
1764 }
1765 return MetaClassHelper.doConstructorInvoke(constructor,argumentArray);
1766 }
1767
1768 /**
1769 * Chooses the correct method to use from a list of methods which match by
1770 * name.
1771 *
1772 * @param methods
1773 * the possible methods to choose from
1774 * @param arguments
1775 * the original argument to the method
1776 */
1777 private Object chooseMethod(String methodName, List methods, Class[] arguments, boolean coerce) {
1778 int methodCount = methods.size();
1779 if (methodCount <= 0) {
1780 return null;
1781 }
1782 else if (methodCount == 1) {
1783 Object method = methods.get(0);
1784 if (MetaClassHelper.isValidMethod(method, arguments, coerce)) {
1785 return method;
1786 }
1787 return null;
1788 }
1789 Object answer = null;
1790 if (arguments == null || arguments.length == 0) {
1791 answer = MetaClassHelper.chooseEmptyMethodParams(methods);
1792 }
1793 else if (arguments.length == 1 && arguments[0] == null) {
1794 answer = MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods);
1795 }
1796 else {
1797 List matchingMethods = new ArrayList();
1798
1799 for (Iterator iter = methods.iterator(); iter.hasNext();) {
1800 Object method = iter.next();
1801
1802 // making this false helps find matches
1803 if (MetaClassHelper.isValidMethod(method, arguments, coerce)) {
1804 matchingMethods.add(method);
1805 }
1806 }
1807 if (matchingMethods.isEmpty()) {
1808 return null;
1809 }
1810 else if (matchingMethods.size() == 1) {
1811 return matchingMethods.get(0);
1812 }
1813 return chooseMostSpecificParams(methodName, matchingMethods, arguments);
1814
1815 }
1816 if (answer != null) {
1817 return answer;
1818 }
1819 throw new GroovyRuntimeException(
1820 "Could not find which method to invoke from this list: "
1821 + methods
1822 + " for arguments: "
1823 + InvokerHelper.toString(arguments));
1824 }
1825
1826 private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) {
1827
1828 long matchesDistance = -1;
1829 LinkedList matches = new LinkedList();
1830 for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) {
1831 Object method = iter.next();
1832 Class[] paramTypes = MetaClassHelper.getParameterTypes(method);
1833 if (!MetaClassHelper.parametersAreCompatible(arguments, paramTypes)) continue;
1834 long dist = MetaClassHelper.calculateParameterDistance(arguments, paramTypes);
1835 if (dist==0) return method;
1836 if (matches.size()==0) {
1837 matches.add(method);
1838 matchesDistance = dist;
1839 } else if (dist<matchesDistance) {
1840 matchesDistance=dist;
1841 matches.clear();
1842 matches.add(method);
1843 } else if (dist==matchesDistance) {
1844 matches.add(method);
1845 }
1846
1847 }
1848 if (matches.size()==1) {
1849 return matches.getFirst();
1850 }
1851 if (matches.size()==0) {
1852 return null;
1853 }
1854
1855 //more than one matching method found --> ambigous!
1856 String msg = "Ambiguous method overloading for method ";
1857 msg+= theClass.getName()+"#"+name;
1858 msg+= ".\nCannot resolve which method to invoke for ";
1859 msg+= InvokerHelper.toString(arguments);
1860 msg+= " due to overlapping prototypes between:";
1861 for (Iterator iter = matches.iterator(); iter.hasNext();) {
1862 Class[] types=MetaClassHelper.getParameterTypes(iter.next());
1863 msg+= "\n\t"+InvokerHelper.toString(types);
1864 }
1865 throw new GroovyRuntimeException(msg);
1866 }
1867
1868 private boolean isGenericGetMethod(MetaMethod method) {
1869 if (method.getName().equals("get")) {
1870 Class[] parameterTypes = method.getParameterTypes();
1871 return parameterTypes.length == 1 && parameterTypes[0] == String.class;
1872 }
1873 return false;
1874 }
1875
1876 /**
1877 * Call this method when any mutation method is called, such as adding a new
1878 * method to this MetaClass so that any caching or bytecode generation can be
1879 * regenerated.
1880 */
1881 private synchronized void onMethodChange() {
1882 reflector = null;
1883 }
1884
1885
1886 public synchronized void initialize() {
1887 if (!isInitialized()) {
1888 fillMethodIndex();
1889 addProperties();
1890 initialized = true;
1891 }
1892 if (reflector == null) {
1893 generateReflector();
1894 }
1895 }
1896
1897 private void addProperties() {
1898 BeanInfo info;
1899 // introspect
1900 try {
1901 info =(BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1902 public Object run() throws IntrospectionException {
1903 return Introspector.getBeanInfo(theClass);
1904 }
1905 });
1906 } catch (PrivilegedActionException pae) {
1907 throw new GroovyRuntimeException("exception while bean introspection",pae.getException());
1908 }
1909 PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
1910
1911 // build up the metaproperties based on the public fields, property descriptors,
1912 // and the getters and setters
1913 setupProperties(descriptors);
1914
1915 EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors();
1916 for (int i = 0; i < eventDescriptors.length; i++) {
1917 EventSetDescriptor descriptor = eventDescriptors[i];
1918 Method[] listenerMethods = descriptor.getListenerMethods();
1919 for (int j = 0; j < listenerMethods.length; j++) {
1920 Method listenerMethod = listenerMethods[j];
1921 MetaMethod metaMethod = createMetaMethod(descriptor.getAddListenerMethod());
1922 String name = listenerMethod.getName();
1923 if (listeners.containsKey(name)) {
1924 listeners.put(name, AMBIGOUS_LISTENER_METHOD);
1925 } else{
1926 listeners.put(name, metaMethod);
1927 }
1928 }
1929 }
1930 }
1931
1932 private MetaMethod createMetaMethod(final Method method) {
1933 if (registry.useAccessible()) {
1934 AccessController.doPrivileged(new PrivilegedAction() {
1935 public Object run() {
1936 method.setAccessible(true);
1937 return null;
1938 }
1939 });
1940 }
1941
1942 MetaMethod answer = new MetaMethod(method);
1943 if (isValidReflectorMethod(answer)) {
1944 allMethods.add(answer);
1945 answer.setMethodIndex(allMethods.size());
1946 }
1947 else {
1948 //log.warning("Creating reflection based dispatcher for: " + method);
1949 answer = new ReflectionMetaMethod(method);
1950 }
1951
1952 if (useReflection) {
1953 //log.warning("Creating reflection based dispatcher for: " + method);
1954 return new ReflectionMetaMethod(method);
1955 }
1956
1957 return answer;
1958 }
1959
1960 private boolean isValidReflectorMethod(MetaMethod method) {
1961 // We cannot use a reflector if the method is private, protected, or package accessible only.
1962 if (!method.isPublic()) {
1963 return false;
1964 }
1965 // lets see if this method is implemented on an interface
1966 List interfaceMethods = getInterfaceMethods();
1967 for (Iterator iter = interfaceMethods.iterator(); iter.hasNext();) {
1968 MetaMethod aMethod = (MetaMethod) iter.next();
1969 if (method.isSame(aMethod)) {
1970 method.setInterfaceClass(aMethod.getCallClass());
1971 return true;
1972 }
1973 }
1974 // it's no interface method, so try to find the highest class
1975 // in hierarchy defining this method
1976 Class declaringClass = method.getCallClass();
1977 for (Class clazz=declaringClass; clazz!=null; clazz=clazz.getSuperclass()) {
1978 try {
1979 final Class klazz = clazz;
1980 final String mName = method.getName();
1981 final Class[] parms = method.getParameterTypes();
1982 try {
1983 Method m = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1984 public Object run() throws NoSuchMethodException {
1985 return klazz.getDeclaredMethod(mName, parms);
1986 }
1987 });
1988 if (!Modifier.isPublic(clazz.getModifiers())) continue;
1989 if (!Modifier.isPublic(m.getModifiers())) continue;
1990 declaringClass = clazz;
1991 } catch (PrivilegedActionException pae) {
1992 if (pae.getException() instanceof NoSuchMethodException) {
1993 throw (NoSuchMethodException) pae.getException();
1994 } else {
1995 throw new RuntimeException(pae.getException());
1996 }
1997 }
1998 } catch (SecurityException e) {
1999 continue;
2000 } catch (NoSuchMethodException e) {
2001 continue;
2002 }
2003 }
2004 if (!Modifier.isPublic(declaringClass.getModifiers())) return false;
2005 method.setCallClass(declaringClass);
2006
2007 return true;
2008 }
2009
2010 private void generateReflector() {
2011 reflector = registry.loadReflector(theClass, allMethods);
2012 if (reflector == null) {
2013 throw new RuntimeException("Should have a reflector for "+theClass.getName());
2014 }
2015 // lets set the reflector on all the methods
2016 for (Iterator iter = allMethods.iterator(); iter.hasNext();) {
2017 MetaMethod metaMethod = (MetaMethod) iter.next();
2018 metaMethod.setReflector(reflector);
2019 }
2020 }
2021
2022 public List getMethods() {
2023 return allMethods;
2024 }
2025
2026 public List getMetaMethods() {
2027 return new ArrayList(newGroovyMethodsList);
2028 }
2029
2030 private synchronized List getInterfaceMethods() {
2031 if (interfaceMethods == null) {
2032 interfaceMethods = new ArrayList();
2033 Class type = theClass;
2034 while (type != null) {
2035 Class[] interfaces = type.getInterfaces();
2036 for (int i = 0; i < interfaces.length; i++) {
2037 Class iface = interfaces[i];
2038 Method[] methods = iface.getMethods();
2039 addInterfaceMethods(interfaceMethods, methods);
2040 }
2041 type = type.getSuperclass();
2042 }
2043 }
2044 return interfaceMethods;
2045 }
2046
2047 private void addInterfaceMethods(List list, Method[] methods) {
2048 for (int i = 0; i < methods.length; i++) {
2049 list.add(createMetaMethod(methods[i]));
2050 }
2051 }
2052
2053 private static class MethodIndexAction {
2054 public void iterate(Map classMethodIndex){
2055 for (Iterator iter = classMethodIndex.entrySet().iterator(); iter.hasNext();) {
2056 Map.Entry classEntry = (Map.Entry) iter.next();
2057 Map methodIndex = (Map) classEntry.getValue();
2058 Class clazz = (Class) classEntry.getKey();
2059 if (skipClass(clazz)) continue;
2060 for (Iterator iterator = methodIndex.entrySet().iterator(); iterator.hasNext();) {
2061 Map.Entry nameEntry = (Map.Entry) iterator.next();
2062 String name = (String) nameEntry.getKey();
2063 List oldList = (List) nameEntry.getValue();
2064 List newList = methodNameAction(clazz, name, oldList);
2065 if (replaceMethodList()) nameEntry.setValue(newList);
2066 }
2067 }
2068 }
2069 public List methodNameAction(Class clazz, String methodName, List methods) {
2070 List newList = new ArrayList(methods.size());
2071 for (Iterator methodIter = methods.iterator(); methodIter.hasNext();) {
2072 MetaMethod method = (MetaMethod) methodIter.next();
2073 methodListAction(clazz,methodName,method,methods,newList);
2074 }
2075 return newList;
2076 }
2077 public boolean skipClass(Class clazz) {return false;}
2078 public void methodListAction(Class clazz, String methodName, MetaMethod method, List oldList, List newList) {}
2079 public boolean replaceMethodList(){return true;}
2080 }
2081
2082 /**
2083 * @deprecated
2084 */
2085 public Object getProperty(Object object, String property) {
2086 return getProperty(theClass,object,property,false,false);
2087 }
2088
2089 /**
2090 * @deprecated
2091 */
2092 public void setProperty(Object object, String property, Object newValue) {
2093 setProperty(theClass,object,property,newValue,false,false);
2094 }
2095
2096 /**
2097 * @deprecated
2098 */
2099 public Object getAttribute(Object object, String attribute) {
2100 return getAttribute(theClass,object,attribute,false,false);
2101 }
2102
2103 /**
2104 * @deprecated
2105 */
2106 public void setAttribute(Object object, String attribute, Object newValue) {
2107 setAttribute(theClass,object,attribute,newValue,false,false);
2108 }
2109
2110 public MetaMethod pickMethod(String methodName, Class[] arguments) {
2111 return pickMethod(theClass,methodName,arguments,false);
2112 }
2113
2114 protected MetaMethod retrieveMethod(String methodName, Class[] arguments) {
2115 return retrieveMethod(theClass,methodName,arguments,false);
2116 }
2117
2118 /**
2119 * remove all method call cache entries. This should be done if a
2120 * method is added during runtime, but not by using a category.
2121 */
2122 protected void clearInvocationCaches() {
2123 staticMethodCache.clear();
2124 methodCache.clear();
2125 }
2126 }