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