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