001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.beanutils; 019 020 021 import java.lang.ref.Reference; 022 import java.lang.ref.WeakReference; 023 import java.lang.reflect.InvocationTargetException; 024 import java.lang.reflect.Method; 025 import java.lang.reflect.Modifier; 026 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 030 031 /** 032 * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p> 033 * 034 * <h3>Known Limitations</h3> 035 * <h4>Accessing Public Methods In A Default Access Superclass</h4> 036 * <p>There is an issue when invoking public methods contained in a default access superclass. 037 * Reflection locates these methods fine and correctly assigns them as public. 038 * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p> 039 * 040 * <p><code>MethodUtils</code> contains a workaround for this situation. 041 * It will attempt to call <code>setAccessible</code> on this method. 042 * If this call succeeds, then the method can be invoked as normal. 043 * This call will only succeed when the application has sufficient security privilages. 044 * If this call fails then a warning will be logged and the method may fail.</p> 045 * 046 * @author Craig R. McClanahan 047 * @author Ralph Schaer 048 * @author Chris Audley 049 * @author Rey François 050 * @author Gregor Raýman 051 * @author Jan Sorensen 052 * @author Robert Burrell Donkin 053 */ 054 055 public class MethodUtils { 056 057 // --------------------------------------------------------- Private Methods 058 059 /** 060 * Only log warning about accessibility work around once. 061 * <p> 062 * Note that this is broken when this class is deployed via a shared 063 * classloader in a container, as the warning message will be emitted 064 * only once, not once per webapp. However making the warning appear 065 * once per webapp means having a map keyed by context classloader 066 * which introduces nasty memory-leak problems. As this warning is 067 * really optional we can ignore this problem; only one of the webapps 068 * will get the warning in its logs but that should be good enough. 069 */ 070 private static boolean loggedAccessibleWarning = false; 071 072 /** 073 * Indicates whether methods should be cached for improved performance. 074 * <p> 075 * Note that when this class is deployed via a shared classloader in 076 * a container, this will affect all webapps. However making this 077 * configurable per webapp would mean having a map keyed by context classloader 078 * which may introduce memory-leak problems. 079 */ 080 private static boolean CACHE_METHODS = true; 081 082 /** An empty class array */ 083 private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0]; 084 /** An empty object array */ 085 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 086 087 /** 088 * Stores a cache of MethodDescriptor -> Method in a WeakHashMap. 089 * <p> 090 * The keys into this map only ever exist as temporary variables within 091 * methods of this class, and are never exposed to users of this class. 092 * This means that the WeakHashMap is used only as a mechanism for 093 * limiting the size of the cache, ie a way to tell the garbage collector 094 * that the contents of the cache can be completely garbage-collected 095 * whenever it needs the memory. Whether this is a good approach to 096 * this problem is doubtful; something like the commons-collections 097 * LRUMap may be more appropriate (though of course selecting an 098 * appropriate size is an issue). 099 * <p> 100 * This static variable is safe even when this code is deployed via a 101 * shared classloader because it is keyed via a MethodDescriptor object 102 * which has a Class as one of its members and that member is used in 103 * the MethodDescriptor.equals method. So two components that load the same 104 * class via different classloaders will generate non-equal MethodDescriptor 105 * objects and hence end up with different entries in the map. 106 */ 107 private static final WeakFastHashMap cache = new WeakFastHashMap(); 108 109 // --------------------------------------------------------- Public Methods 110 111 static { 112 cache.setFast(true); 113 } 114 115 /** 116 * Set whether methods should be cached for greater performance or not, 117 * default is <code>true</code>. 118 * 119 * @param cacheMethods <code>true</code> if methods should be 120 * cached for greater performance, otherwise <code>false</code> 121 */ 122 public static synchronized void setCacheMethods(boolean cacheMethods) { 123 CACHE_METHODS = cacheMethods; 124 if (!CACHE_METHODS) { 125 clearCache(); 126 } 127 } 128 129 /** 130 * Clear the method cache. 131 * @return the number of cached methods cleared 132 */ 133 public static synchronized int clearCache() { 134 int size = cache.size(); 135 cache.clear(); 136 return size; 137 } 138 139 /** 140 * <p>Invoke a named method whose parameter type matches the object type.</p> 141 * 142 * <p>The behaviour of this method is less deterministic 143 * than <code>invokeExactMethod()</code>. 144 * It loops through all methods with names that match 145 * and then executes the first it finds with compatable parameters.</p> 146 * 147 * <p>This method supports calls to methods taking primitive parameters 148 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 149 * would match a <code>boolean</code> primitive.</p> 150 * 151 * <p> This is a convenient wrapper for 152 * {@link #invokeMethod(Object object,String methodName,Object [] args)}. 153 * </p> 154 * 155 * @param object invoke method on this object 156 * @param methodName get method with this name 157 * @param arg use this argument 158 * @return The value returned by the invoked method 159 * 160 * @throws NoSuchMethodException if there is no such accessible method 161 * @throws InvocationTargetException wraps an exception thrown by the 162 * method invoked 163 * @throws IllegalAccessException if the requested method is not accessible 164 * via reflection 165 */ 166 public static Object invokeMethod( 167 Object object, 168 String methodName, 169 Object arg) 170 throws 171 NoSuchMethodException, 172 IllegalAccessException, 173 InvocationTargetException { 174 175 Object[] args = {arg}; 176 return invokeMethod(object, methodName, args); 177 178 } 179 180 181 /** 182 * <p>Invoke a named method whose parameter type matches the object type.</p> 183 * 184 * <p>The behaviour of this method is less deterministic 185 * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. 186 * It loops through all methods with names that match 187 * and then executes the first it finds with compatable parameters.</p> 188 * 189 * <p>This method supports calls to methods taking primitive parameters 190 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 191 * would match a <code>boolean</code> primitive.</p> 192 * 193 * <p> This is a convenient wrapper for 194 * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 195 * </p> 196 * 197 * @param object invoke method on this object 198 * @param methodName get method with this name 199 * @param args use these arguments - treat null as empty array 200 * @return The value returned by the invoked method 201 * 202 * @throws NoSuchMethodException if there is no such accessible method 203 * @throws InvocationTargetException wraps an exception thrown by the 204 * method invoked 205 * @throws IllegalAccessException if the requested method is not accessible 206 * via reflection 207 */ 208 public static Object invokeMethod( 209 Object object, 210 String methodName, 211 Object[] args) 212 throws 213 NoSuchMethodException, 214 IllegalAccessException, 215 InvocationTargetException { 216 217 if (args == null) { 218 args = EMPTY_OBJECT_ARRAY; 219 } 220 int arguments = args.length; 221 Class[] parameterTypes = new Class[arguments]; 222 for (int i = 0; i < arguments; i++) { 223 parameterTypes[i] = args[i].getClass(); 224 } 225 return invokeMethod(object, methodName, args, parameterTypes); 226 227 } 228 229 230 /** 231 * <p>Invoke a named method whose parameter type matches the object type.</p> 232 * 233 * <p>The behaviour of this method is less deterministic 234 * than {@link 235 * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 236 * It loops through all methods with names that match 237 * and then executes the first it finds with compatable parameters.</p> 238 * 239 * <p>This method supports calls to methods taking primitive parameters 240 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 241 * would match a <code>boolean</code> primitive.</p> 242 * 243 * 244 * @param object invoke method on this object 245 * @param methodName get method with this name 246 * @param args use these arguments - treat null as empty array 247 * @param parameterTypes match these parameters - treat null as empty array 248 * @return The value returned by the invoked method 249 * 250 * @throws NoSuchMethodException if there is no such accessible method 251 * @throws InvocationTargetException wraps an exception thrown by the 252 * method invoked 253 * @throws IllegalAccessException if the requested method is not accessible 254 * via reflection 255 */ 256 public static Object invokeMethod( 257 Object object, 258 String methodName, 259 Object[] args, 260 Class[] parameterTypes) 261 throws 262 NoSuchMethodException, 263 IllegalAccessException, 264 InvocationTargetException { 265 266 if (parameterTypes == null) { 267 parameterTypes = EMPTY_CLASS_PARAMETERS; 268 } 269 if (args == null) { 270 args = EMPTY_OBJECT_ARRAY; 271 } 272 273 Method method = getMatchingAccessibleMethod( 274 object.getClass(), 275 methodName, 276 parameterTypes); 277 if (method == null) { 278 throw new NoSuchMethodException("No such accessible method: " + 279 methodName + "() on object: " + object.getClass().getName()); 280 } 281 return method.invoke(object, args); 282 } 283 284 285 /** 286 * <p>Invoke a method whose parameter type matches exactly the object 287 * type.</p> 288 * 289 * <p> This is a convenient wrapper for 290 * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. 291 * </p> 292 * 293 * @param object invoke method on this object 294 * @param methodName get method with this name 295 * @param arg use this argument 296 * @return The value returned by the invoked method 297 * 298 * @throws NoSuchMethodException if there is no such accessible method 299 * @throws InvocationTargetException wraps an exception thrown by the 300 * method invoked 301 * @throws IllegalAccessException if the requested method is not accessible 302 * via reflection 303 */ 304 public static Object invokeExactMethod( 305 Object object, 306 String methodName, 307 Object arg) 308 throws 309 NoSuchMethodException, 310 IllegalAccessException, 311 InvocationTargetException { 312 313 Object[] args = {arg}; 314 return invokeExactMethod(object, methodName, args); 315 316 } 317 318 319 /** 320 * <p>Invoke a method whose parameter types match exactly the object 321 * types.</p> 322 * 323 * <p> This uses reflection to invoke the method obtained from a call to 324 * <code>getAccessibleMethod()</code>.</p> 325 * 326 * @param object invoke method on this object 327 * @param methodName get method with this name 328 * @param args use these arguments - treat null as empty array 329 * @return The value returned by the invoked method 330 * 331 * @throws NoSuchMethodException if there is no such accessible method 332 * @throws InvocationTargetException wraps an exception thrown by the 333 * method invoked 334 * @throws IllegalAccessException if the requested method is not accessible 335 * via reflection 336 */ 337 public static Object invokeExactMethod( 338 Object object, 339 String methodName, 340 Object[] args) 341 throws 342 NoSuchMethodException, 343 IllegalAccessException, 344 InvocationTargetException { 345 if (args == null) { 346 args = EMPTY_OBJECT_ARRAY; 347 } 348 int arguments = args.length; 349 Class[] parameterTypes = new Class[arguments]; 350 for (int i = 0; i < arguments; i++) { 351 parameterTypes[i] = args[i].getClass(); 352 } 353 return invokeExactMethod(object, methodName, args, parameterTypes); 354 355 } 356 357 358 /** 359 * <p>Invoke a method whose parameter types match exactly the parameter 360 * types given.</p> 361 * 362 * <p>This uses reflection to invoke the method obtained from a call to 363 * <code>getAccessibleMethod()</code>.</p> 364 * 365 * @param object invoke method on this object 366 * @param methodName get method with this name 367 * @param args use these arguments - treat null as empty array 368 * @param parameterTypes match these parameters - treat null as empty array 369 * @return The value returned by the invoked method 370 * 371 * @throws NoSuchMethodException if there is no such accessible method 372 * @throws InvocationTargetException wraps an exception thrown by the 373 * method invoked 374 * @throws IllegalAccessException if the requested method is not accessible 375 * via reflection 376 */ 377 public static Object invokeExactMethod( 378 Object object, 379 String methodName, 380 Object[] args, 381 Class[] parameterTypes) 382 throws 383 NoSuchMethodException, 384 IllegalAccessException, 385 InvocationTargetException { 386 387 if (args == null) { 388 args = EMPTY_OBJECT_ARRAY; 389 } 390 391 if (parameterTypes == null) { 392 parameterTypes = EMPTY_CLASS_PARAMETERS; 393 } 394 395 Method method = getAccessibleMethod( 396 object.getClass(), 397 methodName, 398 parameterTypes); 399 if (method == null) { 400 throw new NoSuchMethodException("No such accessible method: " + 401 methodName + "() on object: " + object.getClass().getName()); 402 } 403 return method.invoke(object, args); 404 405 } 406 407 /** 408 * <p>Invoke a static method whose parameter types match exactly the parameter 409 * types given.</p> 410 * 411 * <p>This uses reflection to invoke the method obtained from a call to 412 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 413 * 414 * @param objectClass invoke static method on this class 415 * @param methodName get method with this name 416 * @param args use these arguments - treat null as empty array 417 * @param parameterTypes match these parameters - treat null as empty array 418 * @return The value returned by the invoked method 419 * 420 * @throws NoSuchMethodException if there is no such accessible method 421 * @throws InvocationTargetException wraps an exception thrown by the 422 * method invoked 423 * @throws IllegalAccessException if the requested method is not accessible 424 * via reflection 425 */ 426 public static Object invokeExactStaticMethod( 427 Class objectClass, 428 String methodName, 429 Object[] args, 430 Class[] parameterTypes) 431 throws 432 NoSuchMethodException, 433 IllegalAccessException, 434 InvocationTargetException { 435 436 if (args == null) { 437 args = EMPTY_OBJECT_ARRAY; 438 } 439 440 if (parameterTypes == null) { 441 parameterTypes = EMPTY_CLASS_PARAMETERS; 442 } 443 444 Method method = getAccessibleMethod( 445 objectClass, 446 methodName, 447 parameterTypes); 448 if (method == null) { 449 throw new NoSuchMethodException("No such accessible method: " + 450 methodName + "() on class: " + objectClass.getName()); 451 } 452 return method.invoke(null, args); 453 454 } 455 456 /** 457 * <p>Invoke a named static method whose parameter type matches the object type.</p> 458 * 459 * <p>The behaviour of this method is less deterministic 460 * than {@link #invokeExactMethod(Object, String, Object[], Class[])}. 461 * It loops through all methods with names that match 462 * and then executes the first it finds with compatable parameters.</p> 463 * 464 * <p>This method supports calls to methods taking primitive parameters 465 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 466 * would match a <code>boolean</code> primitive.</p> 467 * 468 * <p> This is a convenient wrapper for 469 * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}. 470 * </p> 471 * 472 * @param objectClass invoke static method on this class 473 * @param methodName get method with this name 474 * @param arg use this argument 475 * @return The value returned by the invoked method 476 * 477 * @throws NoSuchMethodException if there is no such accessible method 478 * @throws InvocationTargetException wraps an exception thrown by the 479 * method invoked 480 * @throws IllegalAccessException if the requested method is not accessible 481 * via reflection 482 */ 483 public static Object invokeStaticMethod( 484 Class objectClass, 485 String methodName, 486 Object arg) 487 throws 488 NoSuchMethodException, 489 IllegalAccessException, 490 InvocationTargetException { 491 492 Object[] args = {arg}; 493 return invokeStaticMethod (objectClass, methodName, args); 494 495 } 496 497 498 /** 499 * <p>Invoke a named static method whose parameter type matches the object type.</p> 500 * 501 * <p>The behaviour of this method is less deterministic 502 * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. 503 * It loops through all methods with names that match 504 * and then executes the first it finds with compatable parameters.</p> 505 * 506 * <p>This method supports calls to methods taking primitive parameters 507 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 508 * would match a <code>boolean</code> primitive.</p> 509 * 510 * <p> This is a convenient wrapper for 511 * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. 512 * </p> 513 * 514 * @param objectClass invoke static method on this class 515 * @param methodName get method with this name 516 * @param args use these arguments - treat null as empty array 517 * @return The value returned by the invoked method 518 * 519 * @throws NoSuchMethodException if there is no such accessible method 520 * @throws InvocationTargetException wraps an exception thrown by the 521 * method invoked 522 * @throws IllegalAccessException if the requested method is not accessible 523 * via reflection 524 */ 525 public static Object invokeStaticMethod( 526 Class objectClass, 527 String methodName, 528 Object[] args) 529 throws 530 NoSuchMethodException, 531 IllegalAccessException, 532 InvocationTargetException { 533 534 if (args == null) { 535 args = EMPTY_OBJECT_ARRAY; 536 } 537 int arguments = args.length; 538 Class[] parameterTypes = new Class[arguments]; 539 for (int i = 0; i < arguments; i++) { 540 parameterTypes[i] = args[i].getClass(); 541 } 542 return invokeStaticMethod (objectClass, methodName, args, parameterTypes); 543 544 } 545 546 547 /** 548 * <p>Invoke a named static method whose parameter type matches the object type.</p> 549 * 550 * <p>The behaviour of this method is less deterministic 551 * than {@link 552 * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. 553 * It loops through all methods with names that match 554 * and then executes the first it finds with compatable parameters.</p> 555 * 556 * <p>This method supports calls to methods taking primitive parameters 557 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 558 * would match a <code>boolean</code> primitive.</p> 559 * 560 * 561 * @param objectClass invoke static method on this class 562 * @param methodName get method with this name 563 * @param args use these arguments - treat null as empty array 564 * @param parameterTypes match these parameters - treat null as empty array 565 * @return The value returned by the invoked method 566 * 567 * @throws NoSuchMethodException if there is no such accessible method 568 * @throws InvocationTargetException wraps an exception thrown by the 569 * method invoked 570 * @throws IllegalAccessException if the requested method is not accessible 571 * via reflection 572 */ 573 public static Object invokeStaticMethod( 574 Class objectClass, 575 String methodName, 576 Object[] args, 577 Class[] parameterTypes) 578 throws 579 NoSuchMethodException, 580 IllegalAccessException, 581 InvocationTargetException { 582 583 if (parameterTypes == null) { 584 parameterTypes = EMPTY_CLASS_PARAMETERS; 585 } 586 if (args == null) { 587 args = EMPTY_OBJECT_ARRAY; 588 } 589 590 Method method = getMatchingAccessibleMethod( 591 objectClass, 592 methodName, 593 parameterTypes); 594 if (method == null) { 595 throw new NoSuchMethodException("No such accessible method: " + 596 methodName + "() on class: " + objectClass.getName()); 597 } 598 return method.invoke(null, args); 599 } 600 601 602 /** 603 * <p>Invoke a static method whose parameter type matches exactly the object 604 * type.</p> 605 * 606 * <p> This is a convenient wrapper for 607 * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}. 608 * </p> 609 * 610 * @param objectClass invoke static method on this class 611 * @param methodName get method with this name 612 * @param arg use this argument 613 * @return The value returned by the invoked method 614 * 615 * @throws NoSuchMethodException if there is no such accessible method 616 * @throws InvocationTargetException wraps an exception thrown by the 617 * method invoked 618 * @throws IllegalAccessException if the requested method is not accessible 619 * via reflection 620 */ 621 public static Object invokeExactStaticMethod( 622 Class objectClass, 623 String methodName, 624 Object arg) 625 throws 626 NoSuchMethodException, 627 IllegalAccessException, 628 InvocationTargetException { 629 630 Object[] args = {arg}; 631 return invokeExactStaticMethod (objectClass, methodName, args); 632 633 } 634 635 636 /** 637 * <p>Invoke a static method whose parameter types match exactly the object 638 * types.</p> 639 * 640 * <p> This uses reflection to invoke the method obtained from a call to 641 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 642 * 643 * @param objectClass invoke static method on this class 644 * @param methodName get method with this name 645 * @param args use these arguments - treat null as empty array 646 * @return The value returned by the invoked method 647 * 648 * @throws NoSuchMethodException if there is no such accessible method 649 * @throws InvocationTargetException wraps an exception thrown by the 650 * method invoked 651 * @throws IllegalAccessException if the requested method is not accessible 652 * via reflection 653 */ 654 public static Object invokeExactStaticMethod( 655 Class objectClass, 656 String methodName, 657 Object[] args) 658 throws 659 NoSuchMethodException, 660 IllegalAccessException, 661 InvocationTargetException { 662 if (args == null) { 663 args = EMPTY_OBJECT_ARRAY; 664 } 665 int arguments = args.length; 666 Class[] parameterTypes = new Class[arguments]; 667 for (int i = 0; i < arguments; i++) { 668 parameterTypes[i] = args[i].getClass(); 669 } 670 return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes); 671 672 } 673 674 675 /** 676 * <p>Return an accessible method (that is, one that can be invoked via 677 * reflection) with given name and a single parameter. If no such method 678 * can be found, return <code>null</code>. 679 * Basically, a convenience wrapper that constructs a <code>Class</code> 680 * array for you.</p> 681 * 682 * @param clazz get method from this class 683 * @param methodName get method with this name 684 * @param parameterType taking this type of parameter 685 * @return The accessible method 686 */ 687 public static Method getAccessibleMethod( 688 Class clazz, 689 String methodName, 690 Class parameterType) { 691 692 Class[] parameterTypes = {parameterType}; 693 return getAccessibleMethod(clazz, methodName, parameterTypes); 694 695 } 696 697 698 /** 699 * <p>Return an accessible method (that is, one that can be invoked via 700 * reflection) with given name and parameters. If no such method 701 * can be found, return <code>null</code>. 702 * This is just a convenient wrapper for 703 * {@link #getAccessibleMethod(Method method)}.</p> 704 * 705 * @param clazz get method from this class 706 * @param methodName get method with this name 707 * @param parameterTypes with these parameters types 708 * @return The accessible method 709 */ 710 public static Method getAccessibleMethod( 711 Class clazz, 712 String methodName, 713 Class[] parameterTypes) { 714 715 try { 716 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true); 717 // Check the cache first 718 Method method = getCachedMethod(md); 719 if (method != null) { 720 return method; 721 } 722 723 method = getAccessibleMethod 724 (clazz, clazz.getMethod(methodName, parameterTypes)); 725 cacheMethod(md, method); 726 return method; 727 } catch (NoSuchMethodException e) { 728 return (null); 729 } 730 731 } 732 733 734 /** 735 * <p>Return an accessible method (that is, one that can be invoked via 736 * reflection) that implements the specified Method. If no such method 737 * can be found, return <code>null</code>.</p> 738 * 739 * @param method The method that we wish to call 740 * @return The accessible method 741 */ 742 public static Method getAccessibleMethod(Method method) { 743 744 // Make sure we have a method to check 745 if (method == null) { 746 return (null); 747 } 748 749 return getAccessibleMethod(method.getDeclaringClass(), method); 750 751 } 752 753 754 755 /** 756 * <p>Return an accessible method (that is, one that can be invoked via 757 * reflection) that implements the specified Method. If no such method 758 * can be found, return <code>null</code>.</p> 759 * 760 * @param clazz The class of the object 761 * @param method The method that we wish to call 762 * @return The accessible method 763 */ 764 public static Method getAccessibleMethod(Class clazz, Method method) { 765 766 // Make sure we have a method to check 767 if (method == null) { 768 return (null); 769 } 770 771 // If the requested method is not public we cannot call it 772 if (!Modifier.isPublic(method.getModifiers())) { 773 return (null); 774 } 775 776 boolean sameClass = true; 777 if (clazz == null) { 778 clazz = method.getDeclaringClass(); 779 } else { 780 sameClass = clazz.equals(method.getDeclaringClass()); 781 if (!method.getDeclaringClass().isAssignableFrom(clazz)) { 782 throw new IllegalArgumentException(clazz.getName() + 783 " is not assignable from " + method.getDeclaringClass().getName()); 784 } 785 } 786 787 // If the class is public, we are done 788 if (Modifier.isPublic(clazz.getModifiers())) { 789 if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 790 setMethodAccessible(method); // Default access superclass workaround 791 } 792 return (method); 793 } 794 795 String methodName = method.getName(); 796 Class[] parameterTypes = method.getParameterTypes(); 797 798 // Check the implemented interfaces and subinterfaces 799 method = 800 getAccessibleMethodFromInterfaceNest(clazz, 801 methodName, 802 parameterTypes); 803 804 // Check the superclass chain 805 if (method == null) { 806 method = getAccessibleMethodFromSuperclass(clazz, 807 methodName, 808 parameterTypes); 809 } 810 811 return (method); 812 813 } 814 815 816 // -------------------------------------------------------- Private Methods 817 818 /** 819 * <p>Return an accessible method (that is, one that can be invoked via 820 * reflection) by scanning through the superclasses. If no such method 821 * can be found, return <code>null</code>.</p> 822 * 823 * @param clazz Class to be checked 824 * @param methodName Method name of the method we wish to call 825 * @param parameterTypes The parameter type signatures 826 */ 827 private static Method getAccessibleMethodFromSuperclass 828 (Class clazz, String methodName, Class[] parameterTypes) { 829 830 Class parentClazz = clazz.getSuperclass(); 831 while (parentClazz != null) { 832 if (Modifier.isPublic(parentClazz.getModifiers())) { 833 try { 834 return parentClazz.getMethod(methodName, parameterTypes); 835 } catch (NoSuchMethodException e) { 836 return null; 837 } 838 } 839 parentClazz = parentClazz.getSuperclass(); 840 } 841 return null; 842 } 843 844 /** 845 * <p>Return an accessible method (that is, one that can be invoked via 846 * reflection) that implements the specified method, by scanning through 847 * all implemented interfaces and subinterfaces. If no such method 848 * can be found, return <code>null</code>.</p> 849 * 850 * <p> There isn't any good reason why this method must be private. 851 * It is because there doesn't seem any reason why other classes should 852 * call this rather than the higher level methods.</p> 853 * 854 * @param clazz Parent class for the interfaces to be checked 855 * @param methodName Method name of the method we wish to call 856 * @param parameterTypes The parameter type signatures 857 */ 858 private static Method getAccessibleMethodFromInterfaceNest 859 (Class clazz, String methodName, Class[] parameterTypes) { 860 861 Method method = null; 862 863 // Search up the superclass chain 864 for (; clazz != null; clazz = clazz.getSuperclass()) { 865 866 // Check the implemented interfaces of the parent class 867 Class[] interfaces = clazz.getInterfaces(); 868 for (int i = 0; i < interfaces.length; i++) { 869 870 // Is this interface public? 871 if (!Modifier.isPublic(interfaces[i].getModifiers())) { 872 continue; 873 } 874 875 // Does the method exist on this interface? 876 try { 877 method = interfaces[i].getDeclaredMethod(methodName, 878 parameterTypes); 879 } catch (NoSuchMethodException e) { 880 /* Swallow, if no method is found after the loop then this 881 * method returns null. 882 */ 883 } 884 if (method != null) { 885 return method; 886 } 887 888 // Recursively check our parent interfaces 889 method = 890 getAccessibleMethodFromInterfaceNest(interfaces[i], 891 methodName, 892 parameterTypes); 893 if (method != null) { 894 return method; 895 } 896 897 } 898 899 } 900 901 // If we found a method return it 902 if (method != null) { 903 return (method); 904 } 905 906 // We did not find anything 907 return (null); 908 909 } 910 911 /** 912 * <p>Find an accessible method that matches the given name and has compatible parameters. 913 * Compatible parameters mean that every method parameter is assignable from 914 * the given parameters. 915 * In other words, it finds a method with the given name 916 * that will take the parameters given.<p> 917 * 918 * <p>This method is slightly undeterminstic since it loops 919 * through methods names and return the first matching method.</p> 920 * 921 * <p>This method is used by 922 * {@link 923 * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 924 * 925 * <p>This method can match primitive parameter by passing in wrapper classes. 926 * For example, a <code>Boolean</code> will match a primitive <code>boolean</code> 927 * parameter. 928 * 929 * @param clazz find method in this class 930 * @param methodName find method with this name 931 * @param parameterTypes find method with compatible parameters 932 * @return The accessible method 933 */ 934 public static Method getMatchingAccessibleMethod( 935 Class clazz, 936 String methodName, 937 Class[] parameterTypes) { 938 // trace logging 939 Log log = LogFactory.getLog(MethodUtils.class); 940 if (log.isTraceEnabled()) { 941 log.trace("Matching name=" + methodName + " on " + clazz); 942 } 943 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false); 944 945 // see if we can find the method directly 946 // most of the time this works and it's much faster 947 try { 948 // Check the cache first 949 Method method = getCachedMethod(md); 950 if (method != null) { 951 return method; 952 } 953 954 method = clazz.getMethod(methodName, parameterTypes); 955 if (log.isTraceEnabled()) { 956 log.trace("Found straight match: " + method); 957 log.trace("isPublic:" + Modifier.isPublic(method.getModifiers())); 958 } 959 960 setMethodAccessible(method); // Default access superclass workaround 961 962 cacheMethod(md, method); 963 return method; 964 965 } catch (NoSuchMethodException e) { /* SWALLOW */ } 966 967 // search through all methods 968 int paramSize = parameterTypes.length; 969 Method bestMatch = null; 970 Method[] methods = clazz.getMethods(); 971 float bestMatchCost = Float.MAX_VALUE; 972 float myCost = Float.MAX_VALUE; 973 for (int i = 0, size = methods.length; i < size ; i++) { 974 if (methods[i].getName().equals(methodName)) { 975 // log some trace information 976 if (log.isTraceEnabled()) { 977 log.trace("Found matching name:"); 978 log.trace(methods[i]); 979 } 980 981 // compare parameters 982 Class[] methodsParams = methods[i].getParameterTypes(); 983 int methodParamSize = methodsParams.length; 984 if (methodParamSize == paramSize) { 985 boolean match = true; 986 for (int n = 0 ; n < methodParamSize; n++) { 987 if (log.isTraceEnabled()) { 988 log.trace("Param=" + parameterTypes[n].getName()); 989 log.trace("Method=" + methodsParams[n].getName()); 990 } 991 if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { 992 if (log.isTraceEnabled()) { 993 log.trace(methodsParams[n] + " is not assignable from " 994 + parameterTypes[n]); 995 } 996 match = false; 997 break; 998 } 999 } 1000 1001 if (match) { 1002 // get accessible version of method 1003 Method method = getAccessibleMethod(clazz, methods[i]); 1004 if (method != null) { 1005 if (log.isTraceEnabled()) { 1006 log.trace(method + " accessible version of " 1007 + methods[i]); 1008 } 1009 setMethodAccessible(method); // Default access superclass workaround 1010 myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes()); 1011 if ( myCost < bestMatchCost ) { 1012 bestMatch = method; 1013 bestMatchCost = myCost; 1014 } 1015 } 1016 1017 log.trace("Couldn't find accessible method."); 1018 } 1019 } 1020 } 1021 } 1022 if ( bestMatch != null ){ 1023 cacheMethod(md, bestMatch); 1024 } else { 1025 // didn't find a match 1026 log.trace("No match found."); 1027 } 1028 1029 return bestMatch; 1030 } 1031 1032 /** 1033 * Try to make the method accessible 1034 * @param method The source arguments 1035 */ 1036 private static void setMethodAccessible(Method method) { 1037 try { 1038 // 1039 // XXX Default access superclass workaround 1040 // 1041 // When a public class has a default access superclass 1042 // with public methods, these methods are accessible. 1043 // Calling them from compiled code works fine. 1044 // 1045 // Unfortunately, using reflection to invoke these methods 1046 // seems to (wrongly) to prevent access even when the method 1047 // modifer is public. 1048 // 1049 // The following workaround solves the problem but will only 1050 // work from sufficiently privilages code. 1051 // 1052 // Better workarounds would be greatfully accepted. 1053 // 1054 method.setAccessible(true); 1055 1056 } catch (SecurityException se) { 1057 // log but continue just in case the method.invoke works anyway 1058 Log log = LogFactory.getLog(MethodUtils.class); 1059 if (!loggedAccessibleWarning) { 1060 boolean vulnerableJVM = false; 1061 try { 1062 String specVersion = System.getProperty("java.specification.version"); 1063 if (specVersion.charAt(0) == '1' && 1064 (specVersion.charAt(2) == '0' || 1065 specVersion.charAt(2) == '1' || 1066 specVersion.charAt(2) == '2' || 1067 specVersion.charAt(2) == '3')) { 1068 1069 vulnerableJVM = true; 1070 } 1071 } catch (SecurityException e) { 1072 // don't know - so display warning 1073 vulnerableJVM = true; 1074 } 1075 if (vulnerableJVM) { 1076 log.warn( 1077 "Current Security Manager restricts use of workarounds for reflection bugs " 1078 + " in pre-1.4 JVMs."); 1079 } 1080 loggedAccessibleWarning = true; 1081 } 1082 log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se); 1083 } 1084 } 1085 1086 /** 1087 * Returns the sum of the object transformation cost for each class in the source 1088 * argument list. 1089 * @param srcArgs The source arguments 1090 * @param destArgs The destination arguments 1091 * @return The total transformation cost 1092 */ 1093 private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) { 1094 1095 float totalCost = 0.0f; 1096 for (int i = 0; i < srcArgs.length; i++) { 1097 Class srcClass, destClass; 1098 srcClass = srcArgs[i]; 1099 destClass = destArgs[i]; 1100 totalCost += getObjectTransformationCost(srcClass, destClass); 1101 } 1102 1103 return totalCost; 1104 } 1105 1106 /** 1107 * Gets the number of steps required needed to turn the source class into the 1108 * destination class. This represents the number of steps in the object hierarchy 1109 * graph. 1110 * @param srcClass The source class 1111 * @param destClass The destination class 1112 * @return The cost of transforming an object 1113 */ 1114 private static float getObjectTransformationCost(Class srcClass, Class destClass) { 1115 float cost = 0.0f; 1116 while (destClass != null && !destClass.equals(srcClass)) { 1117 if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) { 1118 // slight penalty for interface match. 1119 // we still want an exact match to override an interface match, but 1120 // an interface match should override anything where we have to get a 1121 // superclass. 1122 cost += 0.25f; 1123 break; 1124 } 1125 cost++; 1126 destClass = destClass.getSuperclass(); 1127 } 1128 1129 /* 1130 * If the destination class is null, we've travelled all the way up to 1131 * an Object match. We'll penalize this by adding 1.5 to the cost. 1132 */ 1133 if (destClass == null) { 1134 cost += 1.5f; 1135 } 1136 1137 return cost; 1138 } 1139 1140 1141 /** 1142 * <p>Determine whether a type can be used as a parameter in a method invocation. 1143 * This method handles primitive conversions correctly.</p> 1144 * 1145 * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>, 1146 * a <code>Long</code> to a <code>long</code>, 1147 * a <code>Float</code> to a <code>float</code>, 1148 * a <code>Integer</code> to a <code>int</code>, 1149 * and a <code>Double</code> to a <code>double</code>. 1150 * Now logic widening matches are allowed. 1151 * For example, a <code>Long</code> will not match a <code>int</code>. 1152 * 1153 * @param parameterType the type of parameter accepted by the method 1154 * @param parameterization the type of parameter being tested 1155 * 1156 * @return true if the assignement is compatible. 1157 */ 1158 public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) { 1159 // try plain assignment 1160 if (parameterType.isAssignableFrom(parameterization)) { 1161 return true; 1162 } 1163 1164 if (parameterType.isPrimitive()) { 1165 // this method does *not* do widening - you must specify exactly 1166 // is this the right behaviour? 1167 Class parameterWrapperClazz = getPrimitiveWrapper(parameterType); 1168 if (parameterWrapperClazz != null) { 1169 return parameterWrapperClazz.equals(parameterization); 1170 } 1171 } 1172 1173 return false; 1174 } 1175 1176 /** 1177 * Gets the wrapper object class for the given primitive type class. 1178 * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code> 1179 * @param primitiveType the primitive type class for which a match is to be found 1180 * @return the wrapper type associated with the given primitive 1181 * or null if no match is found 1182 */ 1183 public static Class getPrimitiveWrapper(Class primitiveType) { 1184 // does anyone know a better strategy than comparing names? 1185 if (boolean.class.equals(primitiveType)) { 1186 return Boolean.class; 1187 } else if (float.class.equals(primitiveType)) { 1188 return Float.class; 1189 } else if (long.class.equals(primitiveType)) { 1190 return Long.class; 1191 } else if (int.class.equals(primitiveType)) { 1192 return Integer.class; 1193 } else if (short.class.equals(primitiveType)) { 1194 return Short.class; 1195 } else if (byte.class.equals(primitiveType)) { 1196 return Byte.class; 1197 } else if (double.class.equals(primitiveType)) { 1198 return Double.class; 1199 } else if (char.class.equals(primitiveType)) { 1200 return Character.class; 1201 } else { 1202 1203 return null; 1204 } 1205 } 1206 1207 /** 1208 * Gets the class for the primitive type corresponding to the primitive wrapper class given. 1209 * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>. 1210 * @param wrapperType the 1211 * @return the primitive type class corresponding to the given wrapper class, 1212 * null if no match is found 1213 */ 1214 public static Class getPrimitiveType(Class wrapperType) { 1215 // does anyone know a better strategy than comparing names? 1216 if (Boolean.class.equals(wrapperType)) { 1217 return boolean.class; 1218 } else if (Float.class.equals(wrapperType)) { 1219 return float.class; 1220 } else if (Long.class.equals(wrapperType)) { 1221 return long.class; 1222 } else if (Integer.class.equals(wrapperType)) { 1223 return int.class; 1224 } else if (Short.class.equals(wrapperType)) { 1225 return short.class; 1226 } else if (Byte.class.equals(wrapperType)) { 1227 return byte.class; 1228 } else if (Double.class.equals(wrapperType)) { 1229 return double.class; 1230 } else if (Character.class.equals(wrapperType)) { 1231 return char.class; 1232 } else { 1233 Log log = LogFactory.getLog(MethodUtils.class); 1234 if (log.isDebugEnabled()) { 1235 log.debug("Not a known primitive wrapper class: " + wrapperType); 1236 } 1237 return null; 1238 } 1239 } 1240 1241 /** 1242 * Find a non primitive representation for given primitive class. 1243 * 1244 * @param clazz the class to find a representation for, not null 1245 * @return the original class if it not a primitive. Otherwise the wrapper class. Not null 1246 */ 1247 public static Class toNonPrimitiveClass(Class clazz) { 1248 if (clazz.isPrimitive()) { 1249 Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz); 1250 // the above method returns 1251 if (primitiveClazz != null) { 1252 return primitiveClazz; 1253 } else { 1254 return clazz; 1255 } 1256 } else { 1257 return clazz; 1258 } 1259 } 1260 1261 1262 /** 1263 * Return the method from the cache, if present. 1264 * 1265 * @param md The method descriptor 1266 * @return The cached method 1267 */ 1268 private static Method getCachedMethod(MethodDescriptor md) { 1269 if (CACHE_METHODS) { 1270 Reference methodRef = (Reference)cache.get(md); 1271 if (methodRef != null) { 1272 return (Method)methodRef.get(); 1273 } 1274 } 1275 return null; 1276 } 1277 1278 /** 1279 * Add a method to the cache. 1280 * 1281 * @param md The method descriptor 1282 * @param method The method to cache 1283 */ 1284 private static void cacheMethod(MethodDescriptor md, Method method) { 1285 if (CACHE_METHODS) { 1286 if (method != null) { 1287 cache.put(md, new WeakReference(method)); 1288 } 1289 } 1290 } 1291 1292 /** 1293 * Represents the key to looking up a Method by reflection. 1294 */ 1295 private static class MethodDescriptor { 1296 private Class cls; 1297 private String methodName; 1298 private Class[] paramTypes; 1299 private boolean exact; 1300 private int hashCode; 1301 1302 /** 1303 * The sole constructor. 1304 * 1305 * @param cls the class to reflect, must not be null 1306 * @param methodName the method name to obtain 1307 * @param paramTypes the array of classes representing the paramater types 1308 * @param exact whether the match has to be exact. 1309 */ 1310 public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) { 1311 if (cls == null) { 1312 throw new IllegalArgumentException("Class cannot be null"); 1313 } 1314 if (methodName == null) { 1315 throw new IllegalArgumentException("Method Name cannot be null"); 1316 } 1317 if (paramTypes == null) { 1318 paramTypes = EMPTY_CLASS_PARAMETERS; 1319 } 1320 1321 this.cls = cls; 1322 this.methodName = methodName; 1323 this.paramTypes = paramTypes; 1324 this.exact= exact; 1325 1326 this.hashCode = methodName.length(); 1327 } 1328 /** 1329 * Checks for equality. 1330 * @param obj object to be tested for equality 1331 * @return true, if the object describes the same Method. 1332 */ 1333 public boolean equals(Object obj) { 1334 if (!(obj instanceof MethodDescriptor)) { 1335 return false; 1336 } 1337 MethodDescriptor md = (MethodDescriptor)obj; 1338 1339 return ( 1340 exact == md.exact && 1341 methodName.equals(md.methodName) && 1342 cls.equals(md.cls) && 1343 java.util.Arrays.equals(paramTypes, md.paramTypes) 1344 ); 1345 } 1346 /** 1347 * Returns the string length of method name. I.e. if the 1348 * hashcodes are different, the objects are different. If the 1349 * hashcodes are the same, need to use the equals method to 1350 * determine equality. 1351 * @return the string length of method name. 1352 */ 1353 public int hashCode() { 1354 return hashCode; 1355 } 1356 } 1357 }