001 /* 002 $Id: MetaClassImpl.java,v 1.9 2005/11/13 16:42:09 blackdrag Exp $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package groovy.lang; 047 048 import java.beans.BeanInfo; 049 import java.beans.EventSetDescriptor; 050 import java.beans.IntrospectionException; 051 import java.beans.Introspector; 052 import java.beans.PropertyDescriptor; 053 import java.lang.reflect.Array; 054 import java.lang.reflect.Constructor; 055 import java.lang.reflect.Field; 056 import java.lang.reflect.InvocationTargetException; 057 import java.lang.reflect.Method; 058 import java.lang.reflect.Modifier; 059 import java.net.URL; 060 import java.security.AccessController; 061 import java.security.PrivilegedAction; 062 import java.security.PrivilegedActionException; 063 import java.security.PrivilegedExceptionAction; 064 import java.util.ArrayList; 065 import java.util.Arrays; 066 import java.util.Collection; 067 import java.util.Collections; 068 import java.util.HashMap; 069 import java.util.Iterator; 070 import java.util.LinkedList; 071 import java.util.List; 072 import java.util.Map; 073 import java.util.logging.Level; 074 075 import org.codehaus.groovy.ast.ClassNode; 076 import org.codehaus.groovy.classgen.ReflectorGenerator; 077 import org.codehaus.groovy.control.CompilationUnit; 078 import org.codehaus.groovy.control.CompilerConfiguration; 079 import org.codehaus.groovy.control.Phases; 080 import org.codehaus.groovy.runtime.CurriedClosure; 081 import org.codehaus.groovy.runtime.DefaultGroovyMethods; 082 import org.codehaus.groovy.runtime.GroovyCategorySupport; 083 import org.codehaus.groovy.runtime.InvokerHelper; 084 import org.codehaus.groovy.runtime.InvokerInvocationException; 085 import org.codehaus.groovy.runtime.MetaClassHelper; 086 import org.codehaus.groovy.runtime.MethodClosure; 087 import org.codehaus.groovy.runtime.MethodKey; 088 import org.codehaus.groovy.runtime.NewInstanceMetaMethod; 089 import org.codehaus.groovy.runtime.NewStaticMetaMethod; 090 import org.codehaus.groovy.runtime.ReflectionMetaMethod; 091 import org.codehaus.groovy.runtime.Reflector; 092 import org.codehaus.groovy.runtime.TemporaryMethodKey; 093 import org.codehaus.groovy.runtime.TransformMetaMethod; 094 import org.objectweb.asm.ClassVisitor; 095 import org.objectweb.asm.ClassWriter; 096 097 /** 098 * Allows methods to be dynamically added to existing classes at runtime 099 * 100 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 101 * @author Guillaume Laforge 102 * @author Jochen Theodorou 103 * @version $Revision: 1.9 $ 104 */ 105 public class MetaClassImpl extends MetaClass { 106 107 protected MetaClassRegistry registry; 108 private ClassNode classNode; 109 private Map methodIndex = new HashMap(); 110 private Map staticMethodIndex = new HashMap(); 111 //private Map propertyDescriptors = Collections.synchronizedMap(new HashMap()); 112 private Map propertyMap = Collections.synchronizedMap(new HashMap()); 113 private Map listeners = new HashMap(); 114 private Map methodCache = Collections.synchronizedMap(new HashMap()); 115 private Map staticMethodCache = Collections.synchronizedMap(new HashMap()); 116 private MetaMethod genericGetMethod; 117 private MetaMethod genericSetMethod; 118 private List constructors; 119 private List allMethods = new ArrayList(); 120 private List interfaceMethods; 121 private Reflector reflector; 122 private boolean initialised; 123 // we only need one of these that can be reused over and over. 124 private MetaProperty arrayLengthProperty = new MetaArrayLengthProperty(); 125 126 public MetaClassImpl(MetaClassRegistry registry, final Class theClass) throws IntrospectionException { 127 super(theClass); 128 this.registry = registry; 129 130 constructors = (List) AccessController.doPrivileged(new PrivilegedAction() { 131 public Object run() { 132 return Arrays.asList (theClass.getDeclaredConstructors()); 133 } 134 }); 135 136 addMethods(theClass,true); 137 138 // introspect 139 BeanInfo info = null; 140 try { 141 info =(BeanInfo) AccessController.doPrivileged(new PrivilegedExceptionAction() { 142 public Object run() throws IntrospectionException { 143 return Introspector.getBeanInfo(theClass); 144 } 145 }); 146 } catch (PrivilegedActionException pae) { 147 if (pae.getException() instanceof IntrospectionException) { 148 throw (IntrospectionException) pae.getException(); 149 } else { 150 throw new RuntimeException(pae.getException()); 151 } 152 } 153 154 PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); 155 156 // build up the metaproperties based on the public fields, property descriptors, 157 // and the getters and setters 158 setupProperties(descriptors); 159 160 /* old code 161 for (int i = 0; i < descriptors.length; i++) { 162 PropertyDescriptor descriptor = descriptors[i]; 163 propertyDescriptors.put(descriptor.getName(), descriptor); 164 } 165 */ 166 167 EventSetDescriptor[] eventDescriptors = info.getEventSetDescriptors(); 168 for (int i = 0; i < eventDescriptors.length; i++) { 169 EventSetDescriptor descriptor = eventDescriptors[i]; 170 Method[] listenerMethods = descriptor.getListenerMethods(); 171 for (int j = 0; j < listenerMethods.length; j++) { 172 Method listenerMethod = listenerMethods[j]; 173 MetaMethod metaMethod = createMetaMethod(descriptor.getAddListenerMethod()); 174 listeners.put(listenerMethod.getName(), metaMethod); 175 } 176 } 177 } 178 179 private void addInheritedMethods() { 180 LinkedList superClasses = new LinkedList(); 181 for (Class c = theClass.getSuperclass(); c!=Object.class && c!= null; c = c.getSuperclass()) { 182 superClasses.addFirst(c); 183 } 184 // lets add all the base class methods 185 for (Iterator iter = superClasses.iterator(); iter.hasNext();) { 186 Class c = (Class) iter.next(); 187 addMethods(c,true); 188 addNewStaticMethodsFrom(c); 189 } 190 191 // now lets see if there are any methods on one of my interfaces 192 Class[] interfaces = theClass.getInterfaces(); 193 for (int i = 0; i < interfaces.length; i++) { 194 addNewStaticMethodsFrom(interfaces[i]); 195 } 196 197 // lets add Object methods after interfaces, as all interfaces derive from Object. 198 // this ensures List and Collection methods come before Object etc 199 if (theClass != Object.class) { 200 addMethods(Object.class, false); 201 addNewStaticMethodsFrom(Object.class); 202 } 203 204 if (theClass.isArray() && !theClass.equals(Object[].class)) { 205 addNewStaticMethodsFrom(Object[].class); 206 } 207 } 208 209 /** 210 * @return all the normal instance methods avaiable on this class for the 211 * given name 212 */ 213 private List getMethods(String name) { 214 List answer = (List) methodIndex.get(name); 215 List used = GroovyCategorySupport.getCategoryMethods(theClass, name); 216 if (used != null) { 217 if (answer != null) { 218 used.addAll(answer); 219 } 220 answer = used; 221 } 222 if (answer == null) { 223 answer = Collections.EMPTY_LIST; 224 } 225 return answer; 226 } 227 228 /** 229 * @return all the normal static methods avaiable on this class for the 230 * given name 231 */ 232 private List getStaticMethods(String name) { 233 List answer = (List) staticMethodIndex.get(name); 234 if (answer == null) { 235 return Collections.EMPTY_LIST; 236 } 237 return answer; 238 } 239 240 /** 241 * Allows static method definitions to be added to a meta class as if it 242 * was an instance method 243 * 244 * @param method 245 */ 246 protected void addNewInstanceMethod(Method method) { 247 if (initialised) { 248 throw new RuntimeException("Already initialized, cannot add new method: " + method); 249 } 250 else { 251 NewInstanceMetaMethod newMethod = new NewInstanceMetaMethod(createMetaMethod(method)); 252 if (! newGroovyMethodsList.contains(newMethod)){ 253 newGroovyMethodsList.add(newMethod); 254 addMethod(newMethod,false); 255 } 256 } 257 } 258 259 protected void addNewStaticMethod(Method method) { 260 if (initialised) { 261 throw new RuntimeException("Already initialized, cannot add new method: " + method); 262 } 263 else { 264 NewStaticMetaMethod newMethod = new NewStaticMetaMethod(createMetaMethod(method)); 265 if (! newGroovyMethodsList.contains(newMethod)){ 266 newGroovyMethodsList.add(newMethod); 267 addMethod(newMethod,false); 268 } 269 } 270 } 271 272 /** 273 * Invokes the given method on the object. 274 * 275 */ 276 public Object invokeMethod(Object object, String methodName, Object[] arguments) { 277 if (object == null) { 278 throw new NullPointerException("Cannot invoke method: " + methodName + " on null object"); 279 } 280 if (log.isLoggable(Level.FINER)){ 281 MetaClassHelper.logMethodCall(object, methodName, arguments); 282 } 283 284 MetaMethod method = retrieveMethod(object, methodName, arguments); 285 286 boolean isClosure = object instanceof Closure; 287 if (isClosure) { 288 Closure closure = (Closure) object; 289 Object delegate = closure.getDelegate(); 290 Object owner = closure.getOwner(); 291 292 if ("call".equals(methodName) || "doCall".equals(methodName)) { 293 if (object.getClass()==MethodClosure.class) { 294 MethodClosure mc = (MethodClosure) object; 295 methodName = mc.getMethod(); 296 MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass()); 297 return ownerMetaClass.invokeMethod(owner,methodName,arguments); 298 } else if (object.getClass()==CurriedClosure.class) { 299 CurriedClosure cc = (CurriedClosure) object; 300 // change the arguments for an uncurried call 301 arguments = cc.getUncurriedArguments(arguments); 302 MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass()); 303 return ownerMetaClass.invokeMethod(owner,methodName,arguments); 304 } 305 } else if ("curry".equals(methodName)) { 306 return closure.curry(arguments); 307 } 308 309 if (method==null && owner!=closure) { 310 MetaClass ownerMetaClass = registry.getMetaClass(owner.getClass()); 311 method = ownerMetaClass.retrieveMethod(owner,methodName,arguments); 312 if (method!=null) return ownerMetaClass.invokeMethod(owner,methodName,arguments); 313 } 314 if (method==null && delegate!=closure && delegate!=null) { 315 MetaClass delegateMetaClass = registry.getMetaClass(delegate.getClass()); 316 method = delegateMetaClass.retrieveMethod(delegate,methodName,arguments); 317 if (method!=null) return delegateMetaClass.invokeMethod(delegate,methodName,arguments); 318 } 319 if (method==null) { 320 // still no methods found, test if delegate or owner are GroovyObjects 321 // and invoke the method on them if so. 322 MissingMethodException last = null; 323 if (delegate!=closure && (delegate instanceof GroovyObject)) { 324 try { 325 GroovyObject go = (GroovyObject) delegate; 326 return go.invokeMethod(methodName,arguments); 327 } catch (MissingMethodException mme) { 328 last = mme; 329 } 330 } 331 if (owner!=closure && (owner instanceof GroovyObject)) { 332 try { 333 GroovyObject go = (GroovyObject) owner; 334 return go.invokeMethod(methodName,arguments); 335 } catch (MissingMethodException mme) { 336 if (last==null) last = mme; 337 } 338 } 339 if (last!=null) throw last; 340 } 341 342 } 343 344 if (method != null) { 345 return MetaClassHelper.doMethodInvoke(object, method, arguments); 346 } else { 347 // if no method was found, try to find a closure defined as a field of the class and run it 348 try { 349 Object value = this.getProperty(object, methodName); 350 if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this 351 Closure closure = (Closure) value; 352 MetaClass delegateMetaClass = registry.getMetaClass(closure.getClass()); 353 return delegateMetaClass.invokeMethod(closure,"doCall",arguments); 354 } 355 } catch (MissingPropertyException mpe) {} 356 357 throw new MissingMethodException(methodName, theClass, arguments); 358 } 359 } 360 361 public MetaMethod retrieveMethod(Object owner, String methodName, Object[] arguments) { 362 // lets try use the cache to find the method 363 MethodKey methodKey = new TemporaryMethodKey(methodName, arguments); 364 MetaMethod method = (MetaMethod) methodCache.get(methodKey); 365 if (method == null) { 366 method = pickMethod(owner, methodName, arguments); 367 if (method != null && method.isCacheable()) { 368 methodCache.put(methodKey.createCopy(), method); 369 } 370 } 371 return method; 372 } 373 374 public MetaMethod retrieveMethod(String methodName, Class[] arguments) { 375 // lets try use the cache to find the method 376 MethodKey methodKey = new TemporaryMethodKey(methodName, arguments); 377 MetaMethod method = (MetaMethod) methodCache.get(methodKey); 378 if (method == null) { 379 method = pickMethod(methodName, arguments); // todo shall call pickStaticMethod also? 380 if (method != null && method.isCacheable()) { 381 methodCache.put(methodKey.createCopy(), method); 382 } 383 } 384 return method; 385 } 386 387 public Constructor retrieveConstructor(Class[] arguments) { 388 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, arguments, false); 389 if (constructor != null) { 390 return constructor; 391 } 392 else { 393 constructor = (Constructor) chooseMethod("<init>", constructors, arguments, true); 394 if (constructor != null) { 395 return constructor; 396 } 397 } 398 return null; 399 } 400 401 public MetaMethod retrieveStaticMethod(String methodName, Class[] arguments) { 402 MethodKey methodKey = new TemporaryMethodKey(methodName, arguments); 403 MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey); 404 if (method == null) { 405 method = pickStaticMethod(methodName, arguments); 406 if (method != null) { 407 staticMethodCache.put(methodKey.createCopy(), method); 408 } 409 } 410 return method; 411 } 412 /** 413 * Picks which method to invoke for the given object, method name and arguments 414 */ 415 protected MetaMethod pickMethod(Object object, String methodName, Object[] arguments) { 416 MetaMethod method = null; 417 List methods = getMethods(methodName); 418 if (!methods.isEmpty()) { 419 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); 420 method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true); 421 if (method == null) { 422 int size = (arguments != null) ? arguments.length : 0; 423 if (size == 1) { 424 Object firstArgument = arguments[0]; 425 if (firstArgument instanceof List) { 426 // lets coerce the list arguments into an array of 427 // arguments 428 // e.g. calling JFrame.setLocation( [100, 100] ) 429 430 List list = (List) firstArgument; 431 arguments = list.toArray(); 432 argClasses = MetaClassHelper.convertToTypeArray(arguments); 433 method = (MetaMethod) chooseMethod(methodName, methods, argClasses, true); 434 if (method==null) return null; 435 return new TransformMetaMethod(method) { 436 public Object invoke(Object object, Object[] arguments) throws Exception { 437 Object firstArgument = arguments[0]; 438 List list = (List) firstArgument; 439 arguments = list.toArray(); 440 return super.invoke(object, arguments); 441 } 442 }; 443 } 444 } 445 } 446 } 447 return method; 448 } 449 450 /** 451 * pick a method in a strict manner, i.e., without reinterpreting the first List argument. 452 * this method is used only by ClassGenerator for static binding 453 * @param methodName 454 * @param arguments 455 * @return 456 */ 457 protected MetaMethod pickMethod(String methodName, Class[] arguments) { 458 MetaMethod method = null; 459 List methods = getMethods(methodName); 460 if (!methods.isEmpty()) { 461 method = (MetaMethod) chooseMethod(methodName, methods, arguments, false); 462 //no coersion at classgen time. 463 // if (method == null) { 464 // method = (MetaMethod) chooseMethod(methodName, methods, arguments, true); 465 // } 466 } 467 return method; 468 } 469 470 public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { 471 if (log.isLoggable(Level.FINER)){ 472 MetaClassHelper.logMethodCall(object, methodName, arguments); 473 } 474 // lets try use the cache to find the method 475 MethodKey methodKey = new TemporaryMethodKey(methodName, arguments); 476 MetaMethod method = (MetaMethod) staticMethodCache.get(methodKey); 477 if (method == null) { 478 method = pickStaticMethod(object, methodName, arguments); 479 if (method != null) { 480 staticMethodCache.put(methodKey.createCopy(), method); 481 } 482 } 483 484 if (method != null) { 485 return MetaClassHelper.doMethodInvoke(object, method, arguments); 486 } 487 /* 488 List methods = getStaticMethods(methodName); 489 490 if (!methods.isEmpty()) { 491 MetaMethod method = (MetaMethod) chooseMethod(methodName, methods, arguments, false); 492 if (method != null) { 493 return doMethodInvoke(theClass, method, arguments); 494 } 495 } 496 497 if (theClass != Class.class) { 498 try { 499 return registry.getMetaClass(Class.class).invokeMethod(object, methodName, arguments); 500 } 501 catch (GroovyRuntimeException e) { 502 // throw our own exception 503 } 504 } 505 */ 506 throw new MissingMethodException(methodName, theClass, arguments); 507 } 508 509 private MetaMethod pickStaticMethod(Object object, String methodName, Object[] arguments) { 510 MetaMethod method = null; 511 List methods = getStaticMethods(methodName); 512 513 if (!methods.isEmpty()) { 514 method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments), false); 515 } 516 517 if (method == null && theClass != Class.class) { 518 MetaClass classMetaClass = registry.getMetaClass(Class.class); 519 method = classMetaClass.pickMethod(object, methodName, arguments); 520 } 521 if (method == null) { 522 method = (MetaMethod) chooseMethod(methodName, methods, MetaClassHelper.convertToTypeArray(arguments), true); 523 } 524 return method; 525 } 526 527 private MetaMethod pickStaticMethod(String methodName, Class[] arguments) { 528 MetaMethod method = null; 529 List methods = getStaticMethods(methodName); 530 531 if (!methods.isEmpty()) { 532 method = (MetaMethod) chooseMethod(methodName, methods, arguments, false); 533 //disabled to keep consistant with the original version of pickStatciMethod 534 // if (method == null) { 535 // method = (MetaMethod) chooseMethod(methodName, methods, arguments, true); 536 // } 537 } 538 539 if (method == null && theClass != Class.class) { 540 MetaClass classMetaClass = registry.getMetaClass(Class.class); 541 method = classMetaClass.pickMethod(methodName, arguments); 542 } 543 return method; 544 } 545 546 public Object invokeConstructor(Object[] arguments) { 547 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); 548 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false); 549 if (constructor != null) { 550 return MetaClassHelper.doConstructorInvoke(constructor, arguments); 551 } 552 else { 553 constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true); 554 if (constructor != null) { 555 return MetaClassHelper.doConstructorInvoke(constructor, arguments); 556 } 557 } 558 559 if (arguments.length == 1) { 560 Object firstArgument = arguments[0]; 561 if (firstArgument instanceof Map) { 562 constructor = (Constructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false); 563 if (constructor != null) { 564 Object bean = MetaClassHelper.doConstructorInvoke(constructor, MetaClassHelper.EMPTY_ARRAY); 565 setProperties(bean, ((Map) firstArgument)); 566 return bean; 567 } 568 } 569 } 570 throw new GroovyRuntimeException( 571 "Could not find matching constructor for: " 572 + theClass.getName() 573 + "("+InvokerHelper.toTypeString(arguments)+")"); 574 } 575 576 public Object invokeConstructorAt(Class at, Object[] arguments) { 577 Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments); 578 Constructor constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, false); 579 if (constructor != null) { 580 return doConstructorInvokeAt(at, constructor, arguments); 581 } 582 else { 583 constructor = (Constructor) chooseMethod("<init>", constructors, argClasses, true); 584 if (constructor != null) { 585 return doConstructorInvokeAt(at, constructor, arguments); 586 } 587 } 588 589 if (arguments.length == 1) { 590 Object firstArgument = arguments[0]; 591 if (firstArgument instanceof Map) { 592 constructor = (Constructor) chooseMethod("<init>", constructors, MetaClassHelper.EMPTY_TYPE_ARRAY, false); 593 if (constructor != null) { 594 Object bean = doConstructorInvokeAt(at, constructor, MetaClassHelper.EMPTY_ARRAY); 595 setProperties(bean, ((Map) firstArgument)); 596 return bean; 597 } 598 } 599 } 600 throw new GroovyRuntimeException( 601 "Could not find matching constructor for: " 602 + theClass.getName() 603 + "("+InvokerHelper.toTypeString(arguments)+")"); 604 } 605 606 /** 607 * Sets a number of bean properties from the given Map where the keys are 608 * the String names of properties and the values are the values of the 609 * properties to set 610 */ 611 public void setProperties(Object bean, Map map) { 612 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { 613 Map.Entry entry = (Map.Entry) iter.next(); 614 String key = entry.getKey().toString(); 615 616 // do we have this property? 617 if(propertyMap.get(key) == null) 618 continue; 619 620 Object value = entry.getValue(); 621 try { 622 setProperty(bean, key, value); 623 } 624 catch (GroovyRuntimeException e) { 625 // lets ignore missing properties 626 /** todo should replace this code with a getMetaProperty(key) != null check 627 i.e. don't try and set a non-existent property 628 */ 629 } 630 } 631 } 632 633 /** 634 * @return the given property's value on the object 635 */ 636 public Object getProperty(final Object object, final String property) { 637 // look for the property in our map 638 MetaProperty mp = (MetaProperty) propertyMap.get(property); 639 if (mp != null) { 640 try { 641 //System.out.println("we found a metaproperty for " + theClass.getName() + 642 // "." + property); 643 // delegate the get operation to the metaproperty 644 return mp.getProperty(object); 645 } 646 catch(Exception e) { 647 throw new GroovyRuntimeException("Cannot read property: " + property); 648 } 649 } 650 651 if (genericGetMethod == null) { 652 // Make sure there isn't a generic method in the "use" cases 653 List possibleGenericMethods = getMethods("get"); 654 if (possibleGenericMethods != null) { 655 for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) { 656 MetaMethod mmethod = (MetaMethod) i.next(); 657 Class[] paramTypes = mmethod.getParameterTypes(); 658 if (paramTypes.length == 1 && paramTypes[0] == String.class) { 659 Object[] arguments = {property}; 660 Object answer = MetaClassHelper.doMethodInvoke(object, mmethod, arguments); 661 return answer; 662 } 663 } 664 } 665 } 666 else { 667 Object[] arguments = { property }; 668 Object answer = MetaClassHelper.doMethodInvoke(object, genericGetMethod, arguments); 669 // jes bug? a property retrieved via a generic get() can't have a null value? 670 if (answer != null) { 671 return answer; 672 } 673 } 674 675 if (!CompilerConfiguration.isJsrGroovy()) { 676 // is the property the name of a method - in which case return a 677 // closure 678 List methods = getMethods(property); 679 if (!methods.isEmpty()) { 680 return new MethodClosure(object, property); 681 } 682 } 683 684 // lets try invoke a static getter method 685 // this case is for protected fields. I wish there was a better way... 686 Exception lastException = null; 687 try { 688 if ( !(object instanceof Class) ) { 689 MetaMethod method = findGetter(object, "get" + MetaClassHelper.capitalize(property)); 690 if (method != null) { 691 return MetaClassHelper.doMethodInvoke(object, method, MetaClassHelper.EMPTY_ARRAY); 692 } 693 } 694 } 695 catch (GroovyRuntimeException e) { 696 lastException = e; 697 } 698 699 /** todo or are we an extensible groovy class? */ 700 if (genericGetMethod != null) { 701 return null; 702 } 703 else { 704 /** todo these special cases should be special MetaClasses maybe */ 705 if (object instanceof Class) { 706 // lets try a static field 707 return getStaticProperty((Class) object, property); 708 } 709 if (object instanceof Collection) { 710 return DefaultGroovyMethods.getAt((Collection) object, property); 711 } 712 if (object instanceof Object[]) { 713 return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), property); 714 } 715 if (object instanceof Object) { 716 try { 717 return getAttribute(object,property); 718 } catch (MissingFieldException mfe) { 719 // do nothing 720 } 721 } 722 723 MetaMethod addListenerMethod = (MetaMethod) listeners.get(property); 724 if (addListenerMethod != null) { 725 /* @todo one day we could try return the previously registered Closure listener for easy removal */ 726 return null; 727 } 728 729 if (lastException == null) 730 throw new MissingPropertyException(property, theClass); 731 else 732 throw new MissingPropertyException(property, theClass, lastException); 733 } 734 } 735 736 /** 737 * Get all the properties defined for this type 738 * @return a list of MetaProperty objects 739 */ 740 public List getProperties() { 741 // simply return the values of the metaproperty map as a List 742 return new ArrayList(propertyMap.values()); 743 } 744 745 /** 746 * This will build up the property map (Map of MetaProperty objects, keyed on 747 * property name). 748 */ 749 private void setupProperties(PropertyDescriptor[] propertyDescriptors) { 750 MetaProperty mp; 751 Method method; 752 MetaMethod getter = null; 753 MetaMethod setter = null; 754 Class klass; 755 756 // first get the public fields and create MetaFieldProperty objects 757 klass = theClass; 758 while(klass != null) { 759 final Class clazz = klass; 760 Field[] fields = (Field[]) AccessController.doPrivileged(new PrivilegedAction() { 761 public Object run() { 762 return clazz.getDeclaredFields(); 763 } 764 }); 765 for(int i = 0; i < fields.length; i++) { 766 // todo: GROOVY-996 767 // we're only interested in publics and protected 768 if ((fields[i].getModifiers() & (java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.PROTECTED)) == 0) 769 continue; 770 771 // see if we already got this 772 if(propertyMap.get(fields[i].getName()) != null) 773 continue; 774 775 //System.out.println("adding field " + fields[i].getName() + 776 // " for class " + klass.getName()); 777 // stick it in there! 778 propertyMap.put(fields[i].getName(), new MetaFieldProperty(fields[i])); 779 } 780 781 // now get the super class 782 klass = klass.getSuperclass(); 783 } 784 785 // if this an Array, then add the special read-only "length" property 786 if (theClass.isArray()) { 787 propertyMap.put("length", arrayLengthProperty); 788 } 789 790 // now iterate over the map of property descriptors and generate 791 // MetaBeanProperty objects 792 for(int i=0; i<propertyDescriptors.length; i++) { 793 PropertyDescriptor pd = propertyDescriptors[i]; 794 795 // skip if the property type is unknown (this seems to be the case if the 796 // property descriptor is based on a setX() method that has two parameters, 797 // which is not a valid property) 798 if(pd.getPropertyType() == null) 799 continue; 800 801 // get the getter method 802 method = pd.getReadMethod(); 803 if(method != null) 804 getter = findMethod(method); 805 else 806 getter = null; 807 808 // get the setter method 809 method = pd.getWriteMethod(); 810 if(method != null) 811 setter = findMethod(method); 812 else 813 setter = null; 814 815 // now create the MetaProperty object 816 //System.out.println("creating a bean property for class " + 817 // theClass.getName() + ": " + pd.getName()); 818 819 mp = new MetaBeanProperty(pd.getName(), pd.getPropertyType(), getter, setter); 820 821 // put it in the list 822 // this will overwrite a possible field property 823 propertyMap.put(pd.getName(), mp); 824 } 825 826 // now look for any stray getters that may be used to define a property 827 klass = theClass; 828 while(klass != null) { 829 final Class clazz = klass; 830 Method[] methods = (Method[]) AccessController.doPrivileged(new PrivilegedAction() { 831 public Object run() { 832 return clazz.getDeclaredMethods(); 833 } 834 }); 835 for (int i = 0; i < methods.length; i++) { 836 // filter out the privates 837 if(Modifier.isPublic(methods[i].getModifiers()) == false) 838 continue; 839 840 method = methods[i]; 841 842 String methodName = method.getName(); 843 844 // is this a getter? 845 if(methodName.startsWith("get") && 846 methodName.length() > 3 && 847 method.getParameterTypes().length == 0) { 848 849 // get the name of the property 850 String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4); 851 852 // is this property already accounted for? 853 mp = (MetaProperty) propertyMap.get(propName); 854 if(mp != null) { 855 // we may have already found the setter for this 856 if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getGetter() == null) { 857 // update the getter method to this one 858 ((MetaBeanProperty) mp).setGetter(findMethod(method)); 859 } 860 } 861 else { 862 // we need to create a new property object 863 // type of the property is what the get method returns 864 MetaBeanProperty mbp = new MetaBeanProperty(propName, 865 method.getReturnType(), 866 findMethod(method), null); 867 868 // add it to the map 869 propertyMap.put(propName, mbp); 870 } 871 } 872 else if(methodName.startsWith("set") && 873 methodName.length() > 3 && 874 method.getParameterTypes().length == 1) { 875 876 // get the name of the property 877 String propName = methodName.substring(3,4).toLowerCase() + methodName.substring(4); 878 879 // did we already find the getter of this? 880 mp = (MetaProperty) propertyMap.get(propName); 881 if(mp != null) { 882 if(mp instanceof MetaBeanProperty && ((MetaBeanProperty) mp).getSetter() == null) { 883 // update the setter method to this one 884 ((MetaBeanProperty) mp).setSetter(findMethod(method)); 885 } 886 } 887 else { 888 // this is a new property to add 889 MetaBeanProperty mbp = new MetaBeanProperty(propName, 890 method.getParameterTypes()[0], 891 null, 892 findMethod(method)); 893 894 // add it to the map 895 propertyMap.put(propName, mbp); 896 } 897 } 898 } 899 900 // now get the super class 901 klass = klass.getSuperclass(); 902 } 903 } 904 905 /** 906 * Sets the property value on an object 907 */ 908 public void setProperty(Object object, String property, Object newValue) { 909 MetaProperty mp = (MetaProperty) propertyMap.get(property); 910 if(mp != null) { 911 try { 912 mp.setProperty(object, newValue); 913 return; 914 } 915 catch(ReadOnlyPropertyException e) { 916 // just rethrow it; there's nothing left to do here 917 throw e; 918 } 919 catch (TypeMismatchException e) { 920 // tried to access to mismatched object. 921 throw e; 922 } 923 catch (Exception e) { 924 // if the value is a List see if we can construct the value 925 // from a constructor 926 if (newValue == null) 927 return; 928 if (newValue instanceof List) { 929 List list = (List) newValue; 930 int params = list.size(); 931 Constructor[] constructors = mp.getType().getConstructors(); 932 for (int i = 0; i < constructors.length; i++) { 933 Constructor constructor = constructors[i]; 934 if (constructor.getParameterTypes().length == params) { 935 Object value = MetaClassHelper.doConstructorInvoke(constructor, list.toArray()); 936 mp.setProperty(object, value); 937 return; 938 } 939 } 940 941 // if value is an array 942 Class parameterType = mp.getType(); 943 if (parameterType.isArray()) { 944 Object objArray = MetaClassHelper.asPrimitiveArray(list, parameterType); 945 mp.setProperty(object, objArray); 946 return; 947 } 948 } 949 950 // if value is an multidimensional array 951 // jes currently this logic only supports metabeansproperties and 952 // not metafieldproperties. It shouldn't be too hard to support 953 // the latter... 954 if (newValue.getClass().isArray() && mp instanceof MetaBeanProperty) { 955 MetaBeanProperty mbp = (MetaBeanProperty) mp; 956 List list = Arrays.asList((Object[])newValue); 957 MetaMethod setter = mbp.getSetter(); 958 959 Class parameterType = setter.getParameterTypes()[0]; 960 Class arrayType = parameterType.getComponentType(); 961 Object objArray = Array.newInstance(arrayType, list.size()); 962 963 for (int i = 0; i < list.size(); i++) { 964 List list2 =Arrays.asList((Object[]) list.get(i)); 965 Object objArray2 = MetaClassHelper.asPrimitiveArray(list2, arrayType); 966 Array.set(objArray, i, objArray2); 967 } 968 969 MetaClassHelper.doMethodInvoke(object, setter, new Object[]{ 970 objArray 971 }); 972 return; 973 } 974 975 throw new MissingPropertyException(property, theClass, e); 976 } 977 } 978 979 try { 980 MetaMethod addListenerMethod = (MetaMethod) listeners.get(property); 981 if (addListenerMethod != null && newValue instanceof Closure) { 982 // lets create a dynamic proxy 983 Object proxy = 984 MetaClassHelper.createListenerProxy(addListenerMethod.getParameterTypes()[0], property, (Closure) newValue); 985 MetaClassHelper.doMethodInvoke(object, addListenerMethod, new Object[] { proxy }); 986 return; 987 } 988 989 if (genericSetMethod == null) { 990 // Make sure there isn't a generic method in the "use" cases 991 List possibleGenericMethods = getMethods("set"); 992 if (possibleGenericMethods != null) { 993 for (Iterator i = possibleGenericMethods.iterator(); i.hasNext(); ) { 994 MetaMethod mmethod = (MetaMethod) i.next(); 995 Class[] paramTypes = mmethod.getParameterTypes(); 996 if (paramTypes.length == 2 && paramTypes[0] == String.class) { 997 Object[] arguments = {property, newValue}; 998 Object answer = MetaClassHelper.doMethodInvoke(object, mmethod, arguments); 999 return; 1000 } 1001 } 1002 } 1003 } 1004 else { 1005 Object[] arguments = { property, newValue }; 1006 MetaClassHelper.doMethodInvoke(object, genericSetMethod, arguments); 1007 return; 1008 } 1009 1010 /** todo or are we an extensible class? */ 1011 1012 // lets try invoke the set method 1013 // this is kind of ugly: if it is a protected field, we fall 1014 // all the way down to this klunky code. Need a better 1015 // way to handle this situation... 1016 1017 String method = "set" + MetaClassHelper.capitalize(property); 1018 try { 1019 invokeMethod(object, method, new Object[] { newValue }); 1020 } 1021 catch (MissingMethodException e1) { 1022 setAttribute(object,property,newValue); 1023 } 1024 1025 } 1026 catch (GroovyRuntimeException e) { 1027 throw new MissingPropertyException(property, theClass, e); 1028 } 1029 1030 } 1031 1032 1033 /** 1034 * Looks up the given attribute (field) on the given object 1035 */ 1036 public Object getAttribute(final Object object, final String attribute) { 1037 PrivilegedActionException firstException = null; 1038 1039 final Class clazz; 1040 if (object instanceof Class) { 1041 clazz=(Class) object; 1042 } else { 1043 clazz=theClass; 1044 } 1045 1046 try { 1047 return AccessController.doPrivileged(new PrivilegedExceptionAction() { 1048 public Object run() throws NoSuchFieldException, IllegalAccessException { 1049 final Field field = clazz.getDeclaredField(attribute); 1050 1051 field.setAccessible(true); 1052 return field.get(object); 1053 } 1054 }); 1055 } catch (final PrivilegedActionException pae) { 1056 firstException = pae; 1057 } 1058 1059 try { 1060 return AccessController.doPrivileged(new PrivilegedExceptionAction() { 1061 public Object run() throws NoSuchFieldException, IllegalAccessException { 1062 final Field field = clazz.getField(attribute); 1063 1064 field.setAccessible(true); 1065 return field.get(object); 1066 } 1067 }); 1068 } catch (final PrivilegedActionException pae) { 1069 // prefere the first exception. 1070 } 1071 1072 1073 if (firstException.getException() instanceof NoSuchFieldException) { 1074 throw new MissingFieldException(attribute, theClass); 1075 } else { 1076 throw new RuntimeException(firstException.getException()); 1077 } 1078 } 1079 1080 /** 1081 * Sets the given attribute (field) on the given object 1082 */ 1083 public void setAttribute(final Object object, final String attribute, final Object newValue) { 1084 PrivilegedActionException firstException = null; 1085 1086 final Class clazz; 1087 if (object instanceof Class) { 1088 clazz=(Class) object; 1089 } else { 1090 clazz=theClass; 1091 } 1092 1093 try { 1094 AccessController.doPrivileged(new PrivilegedExceptionAction() { 1095 public Object run() throws NoSuchFieldException, IllegalAccessException { 1096 final Field field = clazz.getDeclaredField(attribute); 1097 1098 field.setAccessible(true); 1099 field.set(object,newValue); 1100 return null; 1101 } 1102 }); 1103 return; 1104 } catch (final PrivilegedActionException pae) { 1105 firstException = pae; 1106 } 1107 1108 try { 1109 AccessController.doPrivileged(new PrivilegedExceptionAction() { 1110 public Object run() throws NoSuchFieldException, IllegalAccessException { 1111 final Field field = clazz.getField(attribute); 1112 1113 field.setAccessible(true); 1114 field.set(object, newValue); 1115 return null; 1116 } 1117 }); 1118 return; 1119 } catch (final PrivilegedActionException pae) { 1120 // prefere the first exception. 1121 } 1122 1123 if (firstException.getException() instanceof NoSuchFieldException) { 1124 throw new MissingFieldException(attribute, theClass); 1125 } else { 1126 throw new RuntimeException(firstException.getException()); 1127 } 1128 } 1129 1130 public ClassNode getClassNode() { 1131 if (classNode == null && GroovyObject.class.isAssignableFrom(theClass)) { 1132 // lets try load it from the classpath 1133 String className = theClass.getName(); 1134 String groovyFile = className; 1135 int idx = groovyFile.indexOf('$'); 1136 if (idx > 0) { 1137 groovyFile = groovyFile.substring(0, idx); 1138 } 1139 groovyFile = groovyFile.replace('.', '/') + ".groovy"; 1140 1141 //System.out.println("Attempting to load: " + groovyFile); 1142 URL url = theClass.getClassLoader().getResource(groovyFile); 1143 if (url == null) { 1144 url = Thread.currentThread().getContextClassLoader().getResource(groovyFile); 1145 } 1146 if (url != null) { 1147 try { 1148 1149 /** 1150 * todo there is no CompileUnit in scope so class name 1151 * checking won't work but that mostly affects the bytecode 1152 * generation rather than viewing the AST 1153 */ 1154 CompilationUnit.ClassgenCallback search = new CompilationUnit.ClassgenCallback() { 1155 public void call( ClassVisitor writer, ClassNode node ) { 1156 if( node.getName().equals(theClass.getName()) ) { 1157 MetaClassImpl.this.classNode = node; 1158 } 1159 } 1160 }; 1161 1162 1163 CompilationUnit unit = new CompilationUnit(new GroovyClassLoader(getClass().getClassLoader()) ); 1164 unit.setClassgenCallback( search ); 1165 unit.addSource( url ); 1166 unit.compile( Phases.CLASS_GENERATION ); 1167 } 1168 catch (Exception e) { 1169 throw new GroovyRuntimeException("Exception thrown parsing: " + groovyFile + ". Reason: " + e, e); 1170 } 1171 } 1172 1173 } 1174 return classNode; 1175 } 1176 1177 public String toString() { 1178 return super.toString() + "[" + theClass + "]"; 1179 } 1180 1181 // Implementation methods 1182 //------------------------------------------------------------------------- 1183 1184 /** 1185 * Adds all the methods declared in the given class to the metaclass 1186 * ignoring any matching methods already defined by a derived class 1187 * 1188 * @param theClass 1189 */ 1190 private void addMethods(final Class theClass, boolean forceOverwrite) { 1191 // add methods directly declared in the class 1192 Method[] methodArray = (Method[]) AccessController.doPrivileged(new PrivilegedAction() { 1193 public Object run() { 1194 return theClass.getDeclaredMethods(); 1195 } 1196 }); 1197 for (int i = 0; i < methodArray.length; i++) { 1198 Method reflectionMethod = methodArray[i]; 1199 if ( reflectionMethod.getName().indexOf('+') >= 0 ) { 1200 // Skip Synthetic methods inserted by JDK 1.5 compilers and later 1201 continue; 1202 } 1203 MetaMethod method = createMetaMethod(reflectionMethod); 1204 addMethod(method,forceOverwrite); 1205 } 1206 } 1207 1208 private void addMethod(MetaMethod method, boolean forceOverwrite) { 1209 String name = method.getName(); 1210 1211 //System.out.println(theClass.getName() + " == " + name + Arrays.asList(method.getParameterTypes())); 1212 1213 if (isGenericGetMethod(method) && genericGetMethod == null) { 1214 genericGetMethod = method; 1215 } 1216 else if (MetaClassHelper.isGenericSetMethod(method) && genericSetMethod == null) { 1217 genericSetMethod = method; 1218 } 1219 if (method.isStatic()) { 1220 List list = (List) staticMethodIndex.get(name); 1221 if (list == null) { 1222 list = new ArrayList(); 1223 staticMethodIndex.put(name, list); 1224 list.add(method); 1225 } 1226 else { 1227 if (!MetaClassHelper.containsMatchingMethod(list, method)) { 1228 list.add(method); 1229 } 1230 } 1231 } 1232 1233 List list = (List) methodIndex.get(name); 1234 if (list == null) { 1235 list = new ArrayList(); 1236 methodIndex.put(name, list); 1237 list.add(method); 1238 } 1239 else { 1240 if (forceOverwrite) { 1241 removeMatchingMethod(list,method); 1242 list.add(method); 1243 } else if (!MetaClassHelper.containsMatchingMethod(list, method)) { 1244 list.add(method); 1245 } 1246 } 1247 } 1248 1249 /** 1250 * remove a method of the same matching prototype was found in the list 1251 */ 1252 private void removeMatchingMethod(List list, MetaMethod method) { 1253 for (Iterator iter = list.iterator(); iter.hasNext();) { 1254 MetaMethod aMethod = (MetaMethod) iter.next(); 1255 Class[] params1 = aMethod.getParameterTypes(); 1256 Class[] params2 = method.getParameterTypes(); 1257 if (params1.length == params2.length) { 1258 boolean matches = true; 1259 for (int i = 0; i < params1.length; i++) { 1260 if (params1[i] != params2[i]) { 1261 matches = false; 1262 break; 1263 } 1264 } 1265 if (matches) { 1266 iter.remove(); 1267 return; 1268 } 1269 } 1270 } 1271 return; 1272 } 1273 1274 1275 /** 1276 * Adds all of the newly defined methods from the given class to this 1277 * metaclass 1278 * 1279 * @param theClass 1280 */ 1281 private void addNewStaticMethodsFrom(Class theClass) { 1282 MetaClass interfaceMetaClass = registry.getMetaClass(theClass); 1283 Iterator iter = interfaceMetaClass.newGroovyMethodsList.iterator(); 1284 while (iter.hasNext()) { 1285 MetaMethod method = (MetaMethod) iter.next(); 1286 if (! newGroovyMethodsList.contains(method)){ 1287 newGroovyMethodsList.add(method); 1288 addMethod(method,false); 1289 } 1290 } 1291 } 1292 1293 /** 1294 * @return the value of the static property of the given class 1295 */ 1296 private Object getStaticProperty(Class aClass, String property) { 1297 //System.out.println("Invoking property: " + property + " on class: " 1298 // + aClass); 1299 1300 // lets try invoke a static getter method 1301 MetaMethod method = findStaticGetter(aClass, "get" + MetaClassHelper.capitalize(property)); 1302 if (method != null) { 1303 return MetaClassHelper.doMethodInvoke(aClass, method, MetaClassHelper.EMPTY_ARRAY); 1304 } 1305 1306 //no static getter found, try attribute 1307 try { 1308 return getAttribute(aClass,property); 1309 } catch (MissingFieldException mfe) { 1310 throw new MissingPropertyException(property, aClass, mfe); 1311 } 1312 } 1313 1314 /** 1315 * @return the matching method which should be found 1316 */ 1317 private MetaMethod findMethod(Method aMethod) { 1318 List methods = getMethods(aMethod.getName()); 1319 for (Iterator iter = methods.iterator(); iter.hasNext();) { 1320 MetaMethod method = (MetaMethod) iter.next(); 1321 if (method.isMethod(aMethod)) { 1322 return method; 1323 } 1324 } 1325 //log.warning("Creating reflection based dispatcher for: " + aMethod); 1326 return new ReflectionMetaMethod(aMethod); 1327 } 1328 1329 /** 1330 * @return the getter method for the given object 1331 */ 1332 private MetaMethod findGetter(Object object, String name) { 1333 List methods = getMethods(name); 1334 for (Iterator iter = methods.iterator(); iter.hasNext();) { 1335 MetaMethod method = (MetaMethod) iter.next(); 1336 if (method.getParameterTypes().length == 0) { 1337 return method; 1338 } 1339 } 1340 return null; 1341 } 1342 1343 /** 1344 * @return the Method of the given name with no parameters or null 1345 */ 1346 private MetaMethod findStaticGetter(Class type, String name) { 1347 List methods = getStaticMethods(name); 1348 for (Iterator iter = methods.iterator(); iter.hasNext();) { 1349 MetaMethod method = (MetaMethod) iter.next(); 1350 if (method.getParameterTypes().length == 0) { 1351 return method; 1352 } 1353 } 1354 1355 /** todo dirty hack - don't understand why this code is necessary - all methods should be in the allMethods list! */ 1356 try { 1357 Method method = type.getMethod(name, MetaClassHelper.EMPTY_TYPE_ARRAY); 1358 if ((method.getModifiers() & Modifier.STATIC) != 0) { 1359 return findMethod(method); 1360 } 1361 else { 1362 return null; 1363 } 1364 } 1365 catch (Exception e) { 1366 return null; 1367 } 1368 } 1369 1370 private static Object doConstructorInvokeAt(final Class at, Constructor constructor, Object[] argumentArray) { 1371 if (log.isLoggable(Level.FINER)) { 1372 MetaClassHelper.logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray); 1373 } 1374 1375 try { 1376 // To fix JIRA 435 1377 // Every constructor should be opened to the accessible classes. 1378 final boolean accessible = MetaClassHelper.accessibleToConstructor(at, constructor); 1379 1380 final Constructor ctor = constructor; 1381 AccessController.doPrivileged(new PrivilegedAction() { 1382 public Object run() { 1383 ctor.setAccessible(accessible); 1384 return null; 1385 } 1386 }); 1387 // end of patch 1388 1389 return constructor.newInstance(argumentArray); 1390 } 1391 catch (InvocationTargetException e) { 1392 /*Throwable t = e.getTargetException(); 1393 if (t instanceof Error) { 1394 Error error = (Error) t; 1395 throw error; 1396 } 1397 if (t instanceof RuntimeException) { 1398 RuntimeException runtimeEx = (RuntimeException) t; 1399 throw runtimeEx; 1400 }*/ 1401 throw new InvokerInvocationException(e); 1402 } 1403 catch (IllegalArgumentException e) { 1404 if (MetaClassHelper.coerceGStrings(argumentArray)) { 1405 try { 1406 return constructor.newInstance(argumentArray); 1407 } 1408 catch (Exception e2) { 1409 // allow fall through 1410 } 1411 } 1412 throw new GroovyRuntimeException( 1413 "failed to invoke constructor: " 1414 + constructor 1415 + " with arguments: " 1416 + InvokerHelper.toString(argumentArray) 1417 + " reason: " 1418 + e); 1419 } 1420 catch (IllegalAccessException e) { 1421 throw new GroovyRuntimeException( 1422 "could not access constructor: " 1423 + constructor 1424 + " with arguments: " 1425 + InvokerHelper.toString(argumentArray) 1426 + " reason: " 1427 + e); 1428 } 1429 catch (Exception e) { 1430 throw new GroovyRuntimeException( 1431 "failed to invoke constructor: " 1432 + constructor 1433 + " with arguments: " 1434 + InvokerHelper.toString(argumentArray) 1435 + " reason: " 1436 + e, 1437 e); 1438 } 1439 } 1440 1441 /** 1442 * Chooses the correct method to use from a list of methods which match by 1443 * name. 1444 * 1445 * @param methods 1446 * the possible methods to choose from 1447 * @param arguments 1448 * the original argument to the method 1449 * @return 1450 */ 1451 private Object chooseMethod(String methodName, List methods, Class[] arguments, boolean coerce) { 1452 int methodCount = methods.size(); 1453 if (methodCount <= 0) { 1454 return null; 1455 } 1456 else if (methodCount == 1) { 1457 Object method = methods.get(0); 1458 if (MetaClassHelper.isValidMethod(method, arguments, coerce)) { 1459 return method; 1460 } 1461 return null; 1462 } 1463 Object answer = null; 1464 if (arguments == null || arguments.length == 0) { 1465 answer = MetaClassHelper.chooseEmptyMethodParams(methods); 1466 } 1467 else if (arguments.length == 1 && arguments[0] == null) { 1468 answer = MetaClassHelper.chooseMostGeneralMethodWith1NullParam(methods); 1469 } 1470 else { 1471 List matchingMethods = new ArrayList(); 1472 1473 for (Iterator iter = methods.iterator(); iter.hasNext();) { 1474 Object method = iter.next(); 1475 Class[] paramTypes; 1476 1477 // making this false helps find matches 1478 if (MetaClassHelper.isValidMethod(method, arguments, coerce)) { 1479 matchingMethods.add(method); 1480 } 1481 } 1482 if (matchingMethods.isEmpty()) { 1483 return null; 1484 } 1485 else if (matchingMethods.size() == 1) { 1486 return matchingMethods.get(0); 1487 } 1488 return chooseMostSpecificParams(methodName, matchingMethods, arguments); 1489 1490 } 1491 if (answer != null) { 1492 return answer; 1493 } 1494 throw new GroovyRuntimeException( 1495 "Could not find which method to invoke from this list: " 1496 + methods 1497 + " for arguments: " 1498 + InvokerHelper.toString(arguments)); 1499 } 1500 1501 private Object chooseMostSpecificParams(String name, List matchingMethods, Class[] arguments) { 1502 1503 Class[] wrappedArguments = MetaClassHelper.wrap(arguments); 1504 1505 int matchesDistance = -1; 1506 LinkedList matches = new LinkedList(); 1507 for (Iterator iter = matchingMethods.iterator(); iter.hasNext();) { 1508 Object method = iter.next(); 1509 Class[] paramTypes = MetaClassHelper.getParameterTypes(method); 1510 if (!MetaClassHelper.parametersAreCompatible(arguments, paramTypes)) continue; 1511 int dist = MetaClassHelper.calculateParameterDistance(arguments, paramTypes); 1512 if (matches.size()==0) { 1513 matches.add(method); 1514 matchesDistance = dist; 1515 } else if (dist<matchesDistance) { 1516 matchesDistance=dist; 1517 matches.clear(); 1518 matches.add(method); 1519 } else if (dist==matchesDistance) { 1520 matches.add(method); 1521 } 1522 1523 } 1524 if (matches.size()==1) { 1525 return matches.getFirst(); 1526 } 1527 if (matches.size()==0) { 1528 return null; 1529 } 1530 1531 //more than one matching method found --> ambigous! 1532 String msg = "Ambiguous method overloading for method "; 1533 msg+= theClass.getName()+"#"+name; 1534 msg+= ".\nCannot resolve which method to invoke for "; 1535 msg+= InvokerHelper.toString(arguments); 1536 msg+= " due to overlapping prototypes between:"; 1537 for (Iterator iter = matches.iterator(); iter.hasNext();) { 1538 Class[] types=MetaClassHelper.getParameterTypes(iter.next()); 1539 msg+= "\n\t"+InvokerHelper.toString(types); 1540 } 1541 throw new GroovyRuntimeException(msg); 1542 } 1543 1544 private boolean isGenericGetMethod(MetaMethod method) { 1545 if (method.getName().equals("get")) { 1546 Class[] parameterTypes = method.getParameterTypes(); 1547 return parameterTypes.length == 1 && parameterTypes[0] == String.class; 1548 } 1549 return false; 1550 } 1551 1552 /** 1553 * Call this method when any mutation method is called, such as adding a new 1554 * method to this MetaClass so that any caching or bytecode generation can be 1555 * regenerated. 1556 */ 1557 private synchronized void onMethodChange() { 1558 reflector = null; 1559 } 1560 1561 protected synchronized void checkInitialised() { 1562 if (!initialised) { 1563 initialised = true; 1564 addInheritedMethods(); 1565 } 1566 if (reflector == null) { 1567 generateReflector(); 1568 } 1569 } 1570 1571 private MetaMethod createMetaMethod(final Method method) { 1572 if (registry.useAccessible()) { 1573 AccessController.doPrivileged(new PrivilegedAction() { 1574 public Object run() { 1575 method.setAccessible(true); 1576 return null; 1577 } 1578 }); 1579 } 1580 1581 MetaMethod answer = new MetaMethod(method); 1582 if (isValidReflectorMethod(answer)) { 1583 allMethods.add(answer); 1584 answer.setMethodIndex(allMethods.size()); 1585 } 1586 else { 1587 //log.warning("Creating reflection based dispatcher for: " + method); 1588 answer = new ReflectionMetaMethod(method); 1589 } 1590 1591 if (useReflection) { 1592 //log.warning("Creating reflection based dispatcher for: " + method); 1593 return new ReflectionMetaMethod(method); 1594 } 1595 1596 return answer; 1597 } 1598 1599 private boolean isValidReflectorMethod(MetaMethod method) { 1600 // We cannot use a reflector if the method is private, protected, or package accessible only. 1601 if (!method.isPublic()) { 1602 return false; 1603 } 1604 // lets see if this method is implemented on an interface 1605 List interfaceMethods = getInterfaceMethods(); 1606 for (Iterator iter = interfaceMethods.iterator(); iter.hasNext();) { 1607 MetaMethod aMethod = (MetaMethod) iter.next(); 1608 if (method.isSame(aMethod)) { 1609 method.setInterfaceClass(aMethod.getDeclaringClass()); 1610 return true; 1611 } 1612 } 1613 // it's no interface method, so try to find the highest class 1614 // in hierarchy defining this method 1615 Class declaringClass = method.getDeclaringClass(); 1616 for (Class clazz=declaringClass; clazz!=null; clazz=clazz.getSuperclass()) { 1617 try { 1618 final Class klazz = clazz; 1619 final String mName = method.getName(); 1620 final Class[] parms = method.getParameterTypes(); 1621 try { 1622 Method m = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { 1623 public Object run() throws NoSuchMethodException { 1624 return klazz.getDeclaredMethod(mName, parms); 1625 } 1626 }); 1627 if (!Modifier.isPublic(clazz.getModifiers())) continue; 1628 if (!Modifier.isPublic(m.getModifiers())) continue; 1629 declaringClass = clazz; 1630 } catch (PrivilegedActionException pae) { 1631 if (pae.getException() instanceof NoSuchMethodException) { 1632 throw (NoSuchMethodException) pae.getException(); 1633 } else { 1634 throw new RuntimeException(pae.getException()); 1635 } 1636 } 1637 } catch (SecurityException e) { 1638 continue; 1639 } catch (NoSuchMethodException e) { 1640 continue; 1641 } 1642 } 1643 if (!Modifier.isPublic(declaringClass.getModifiers())) return false; 1644 method.setDeclaringClass(declaringClass); 1645 1646 return true; 1647 } 1648 1649 private void generateReflector() { 1650 reflector = loadReflector(allMethods); 1651 if (reflector == null) { 1652 throw new RuntimeException("Should have a reflector for "+theClass.getName()); 1653 } 1654 // lets set the reflector on all the methods 1655 for (Iterator iter = allMethods.iterator(); iter.hasNext();) { 1656 MetaMethod metaMethod = (MetaMethod) iter.next(); 1657 //System.out.println("Setting reflector for method: " + metaMethod + " with index: " + metaMethod.getMethodIndex()); 1658 metaMethod.setReflector(reflector); 1659 } 1660 } 1661 1662 private String getReflectorName() { 1663 String className = theClass.getName(); 1664 String packagePrefix = "gjdk."; 1665 String name = packagePrefix + className + "_GroovyReflector"; 1666 if (theClass.isArray()) { 1667 Class clazz = theClass; 1668 name = packagePrefix; 1669 int level = 0; 1670 while (clazz.isArray()) { 1671 clazz = clazz.getComponentType(); 1672 level++; 1673 } 1674 String componentName = clazz.getName(); 1675 name = packagePrefix + componentName + "_GroovyReflectorArray"; 1676 if (level>1) name += level; 1677 } 1678 return name; 1679 } 1680 1681 private Reflector loadReflector(List methods) { 1682 ReflectorGenerator generator = new ReflectorGenerator(methods); 1683 String name = getReflectorName(); 1684 /* 1685 * Lets see if its already loaded. 1686 */ 1687 try { 1688 Class type = loadReflectorClass(name); 1689 return (Reflector) type.newInstance(); 1690 } 1691 catch (ClassNotFoundException cnfe) { 1692 /* 1693 * Lets generate it && load it. 1694 */ 1695 try { 1696 ClassWriter cw = new ClassWriter(true); 1697 generator.generate(cw, name); 1698 byte[] bytecode = cw.toByteArray(); 1699 Class type = loadReflectorClass(name, bytecode); 1700 if (Reflector.class.getClassLoader()!=type.getSuperclass().getClassLoader()) { 1701 throw new Error( 1702 name+" does have Reflector.class as superclass, "+ 1703 "Reflector.class is loaded through the loader "+ 1704 Reflector.class.getClassLoader()+ 1705 " and "+name+"'s superclass is loaded through "+ 1706 type.getSuperclass().getClassLoader()+ 1707 ". This should never happen, check your classloader configuration." 1708 ); 1709 } 1710 return (Reflector) type.newInstance(); 1711 } 1712 catch (Exception e) { 1713 e.printStackTrace(); 1714 throw new GroovyRuntimeException("Could not generate and load the reflector for class: " + name + ". Reason: " + e, e); 1715 } 1716 } catch (Error e) { 1717 throw e; 1718 } catch (Throwable t) { 1719 /* 1720 * All other exception and error types are reported at once. 1721 */ 1722 throw new GroovyRuntimeException("Could not load the reflector for class: " + name + ". Reason: " + t, t); 1723 } 1724 } 1725 1726 private Class loadReflectorClass(final String name, final byte[] bytecode) throws ClassNotFoundException { 1727 ClassLoader loader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { 1728 public Object run() { 1729 return theClass.getClassLoader(); 1730 } 1731 }); 1732 if (loader instanceof GroovyClassLoader) { 1733 final GroovyClassLoader gloader = (GroovyClassLoader) loader; 1734 return (Class) AccessController.doPrivileged(new PrivilegedAction() { 1735 public Object run() { 1736 return gloader.defineClass(name, bytecode, getClass().getProtectionDomain()); 1737 } 1738 }); 1739 } 1740 return registry.loadClass(loader, name, bytecode); 1741 } 1742 1743 private Class loadReflectorClass(String name) throws ClassNotFoundException { 1744 ClassLoader loader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { 1745 public Object run() { 1746 return theClass.getClassLoader(); 1747 } 1748 }); 1749 if (loader instanceof GroovyClassLoader) { 1750 GroovyClassLoader gloader = (GroovyClassLoader) loader; 1751 return gloader.loadClass(name); 1752 } 1753 return registry.loadClass(loader, name); 1754 } 1755 1756 public List getMethods() { 1757 return allMethods; 1758 } 1759 1760 public List getMetaMethods() { 1761 return new ArrayList(newGroovyMethodsList); 1762 } 1763 1764 private synchronized List getInterfaceMethods() { 1765 if (interfaceMethods == null) { 1766 interfaceMethods = new ArrayList(); 1767 Class type = theClass; 1768 while (type != null) { 1769 Class[] interfaces = type.getInterfaces(); 1770 for (int i = 0; i < interfaces.length; i++) { 1771 Class iface = interfaces[i]; 1772 Method[] methods = iface.getMethods(); 1773 addInterfaceMethods(interfaceMethods, methods); 1774 } 1775 type = type.getSuperclass(); 1776 } 1777 } 1778 return interfaceMethods; 1779 } 1780 1781 private void addInterfaceMethods(List list, Method[] methods) { 1782 for (int i = 0; i < methods.length; i++) { 1783 list.add(createMetaMethod(methods[i])); 1784 } 1785 } 1786 }