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