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