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 // We did not find anything 911 return (null); 912 913 } 914 915 /** 916 * <p>Find an accessible method that matches the given name and has compatible parameters. 917 * Compatible parameters mean that every method parameter is assignable from 918 * the given parameters. 919 * In other words, it finds a method with the given name 920 * that will take the parameters given.<p> 921 * 922 * <p>This method is slightly undeterminstic since it loops 923 * through methods names and return the first matching method.</p> 924 * 925 * <p>This method is used by 926 * {@link 927 * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 928 * 929 * <p>This method can match primitive parameter by passing in wrapper classes. 930 * For example, a <code>Boolean</code> will match a primitive <code>boolean</code> 931 * parameter. 932 * 933 * @param clazz find method in this class 934 * @param methodName find method with this name 935 * @param parameterTypes find method with compatible parameters 936 * @return The accessible method 937 */ 938 public static Method getMatchingAccessibleMethod( 939 Class clazz, 940 String methodName, 941 Class[] parameterTypes) { 942 // trace logging 943 Log log = LogFactory.getLog(MethodUtils.class); 944 if (log.isTraceEnabled()) { 945 log.trace("Matching name=" + methodName + " on " + clazz); 946 } 947 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false); 948 949 // see if we can find the method directly 950 // most of the time this works and it's much faster 951 try { 952 // Check the cache first 953 Method method = getCachedMethod(md); 954 if (method != null) { 955 return method; 956 } 957 958 method = clazz.getMethod(methodName, parameterTypes); 959 if (log.isTraceEnabled()) { 960 log.trace("Found straight match: " + method); 961 log.trace("isPublic:" + Modifier.isPublic(method.getModifiers())); 962 } 963 964 setMethodAccessible(method); // Default access superclass workaround 965 966 cacheMethod(md, method); 967 return method; 968 969 } catch (NoSuchMethodException e) { /* SWALLOW */ } 970 971 // search through all methods 972 int paramSize = parameterTypes.length; 973 Method bestMatch = null; 974 Method[] methods = clazz.getMethods(); 975 float bestMatchCost = Float.MAX_VALUE; 976 float myCost = Float.MAX_VALUE; 977 for (int i = 0, size = methods.length; i < size ; i++) { 978 if (methods[i].getName().equals(methodName)) { 979 // log some trace information 980 if (log.isTraceEnabled()) { 981 log.trace("Found matching name:"); 982 log.trace(methods[i]); 983 } 984 985 // compare parameters 986 Class[] methodsParams = methods[i].getParameterTypes(); 987 int methodParamSize = methodsParams.length; 988 if (methodParamSize == paramSize) { 989 boolean match = true; 990 for (int n = 0 ; n < methodParamSize; n++) { 991 if (log.isTraceEnabled()) { 992 log.trace("Param=" + parameterTypes[n].getName()); 993 log.trace("Method=" + methodsParams[n].getName()); 994 } 995 if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { 996 if (log.isTraceEnabled()) { 997 log.trace(methodsParams[n] + " is not assignable from " 998 + parameterTypes[n]); 999 } 1000 match = false; 1001 break; 1002 } 1003 } 1004 1005 if (match) { 1006 // get accessible version of method 1007 Method method = getAccessibleMethod(clazz, methods[i]); 1008 if (method != null) { 1009 if (log.isTraceEnabled()) { 1010 log.trace(method + " accessible version of " 1011 + methods[i]); 1012 } 1013 setMethodAccessible(method); // Default access superclass workaround 1014 myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes()); 1015 if ( myCost < bestMatchCost ) { 1016 bestMatch = method; 1017 bestMatchCost = myCost; 1018 } 1019 } 1020 1021 log.trace("Couldn't find accessible method."); 1022 } 1023 } 1024 } 1025 } 1026 if ( bestMatch != null ){ 1027 cacheMethod(md, bestMatch); 1028 } else { 1029 // didn't find a match 1030 log.trace("No match found."); 1031 } 1032 1033 return bestMatch; 1034 } 1035 1036 /** 1037 * Try to make the method accessible 1038 * @param method The source arguments 1039 */ 1040 private static void setMethodAccessible(Method method) { 1041 try { 1042 // 1043 // XXX Default access superclass workaround 1044 // 1045 // When a public class has a default access superclass 1046 // with public methods, these methods are accessible. 1047 // Calling them from compiled code works fine. 1048 // 1049 // Unfortunately, using reflection to invoke these methods 1050 // seems to (wrongly) to prevent access even when the method 1051 // modifer is public. 1052 // 1053 // The following workaround solves the problem but will only 1054 // work from sufficiently privilages code. 1055 // 1056 // Better workarounds would be greatfully accepted. 1057 // 1058 if (!method.isAccessible()) { 1059 method.setAccessible(true); 1060 } 1061 1062 } catch (SecurityException se) { 1063 // log but continue just in case the method.invoke works anyway 1064 Log log = LogFactory.getLog(MethodUtils.class); 1065 if (!loggedAccessibleWarning) { 1066 boolean vulnerableJVM = false; 1067 try { 1068 String specVersion = System.getProperty("java.specification.version"); 1069 if (specVersion.charAt(0) == '1' && 1070 (specVersion.charAt(2) == '0' || 1071 specVersion.charAt(2) == '1' || 1072 specVersion.charAt(2) == '2' || 1073 specVersion.charAt(2) == '3')) { 1074 1075 vulnerableJVM = true; 1076 } 1077 } catch (SecurityException e) { 1078 // don't know - so display warning 1079 vulnerableJVM = true; 1080 } 1081 if (vulnerableJVM) { 1082 log.warn( 1083 "Current Security Manager restricts use of workarounds for reflection bugs " 1084 + " in pre-1.4 JVMs."); 1085 } 1086 loggedAccessibleWarning = true; 1087 } 1088 log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se); 1089 } 1090 } 1091 1092 /** 1093 * Returns the sum of the object transformation cost for each class in the source 1094 * argument list. 1095 * @param srcArgs The source arguments 1096 * @param destArgs The destination arguments 1097 * @return The total transformation cost 1098 */ 1099 private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) { 1100 1101 float totalCost = 0.0f; 1102 for (int i = 0; i < srcArgs.length; i++) { 1103 Class srcClass, destClass; 1104 srcClass = srcArgs[i]; 1105 destClass = destArgs[i]; 1106 totalCost += getObjectTransformationCost(srcClass, destClass); 1107 } 1108 1109 return totalCost; 1110 } 1111 1112 /** 1113 * Gets the number of steps required needed to turn the source class into the 1114 * destination class. This represents the number of steps in the object hierarchy 1115 * graph. 1116 * @param srcClass The source class 1117 * @param destClass The destination class 1118 * @return The cost of transforming an object 1119 */ 1120 private static float getObjectTransformationCost(Class srcClass, Class destClass) { 1121 float cost = 0.0f; 1122 while (destClass != null && !destClass.equals(srcClass)) { 1123 if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) { 1124 // slight penalty for interface match. 1125 // we still want an exact match to override an interface match, but 1126 // an interface match should override anything where we have to get a 1127 // superclass. 1128 cost += 0.25f; 1129 break; 1130 } 1131 cost++; 1132 destClass = destClass.getSuperclass(); 1133 } 1134 1135 /* 1136 * If the destination class is null, we've travelled all the way up to 1137 * an Object match. We'll penalize this by adding 1.5 to the cost. 1138 */ 1139 if (destClass == null) { 1140 cost += 1.5f; 1141 } 1142 1143 return cost; 1144 } 1145 1146 1147 /** 1148 * <p>Determine whether a type can be used as a parameter in a method invocation. 1149 * This method handles primitive conversions correctly.</p> 1150 * 1151 * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>, 1152 * a <code>Long</code> to a <code>long</code>, 1153 * a <code>Float</code> to a <code>float</code>, 1154 * a <code>Integer</code> to a <code>int</code>, 1155 * and a <code>Double</code> to a <code>double</code>. 1156 * Now logic widening matches are allowed. 1157 * For example, a <code>Long</code> will not match a <code>int</code>. 1158 * 1159 * @param parameterType the type of parameter accepted by the method 1160 * @param parameterization the type of parameter being tested 1161 * 1162 * @return true if the assignement is compatible. 1163 */ 1164 public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) { 1165 // try plain assignment 1166 if (parameterType.isAssignableFrom(parameterization)) { 1167 return true; 1168 } 1169 1170 if (parameterType.isPrimitive()) { 1171 // this method does *not* do widening - you must specify exactly 1172 // is this the right behaviour? 1173 Class parameterWrapperClazz = getPrimitiveWrapper(parameterType); 1174 if (parameterWrapperClazz != null) { 1175 return parameterWrapperClazz.equals(parameterization); 1176 } 1177 } 1178 1179 return false; 1180 } 1181 1182 /** 1183 * Gets the wrapper object class for the given primitive type class. 1184 * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code> 1185 * @param primitiveType the primitive type class for which a match is to be found 1186 * @return the wrapper type associated with the given primitive 1187 * or null if no match is found 1188 */ 1189 public static Class getPrimitiveWrapper(Class primitiveType) { 1190 // does anyone know a better strategy than comparing names? 1191 if (boolean.class.equals(primitiveType)) { 1192 return Boolean.class; 1193 } else if (float.class.equals(primitiveType)) { 1194 return Float.class; 1195 } else if (long.class.equals(primitiveType)) { 1196 return Long.class; 1197 } else if (int.class.equals(primitiveType)) { 1198 return Integer.class; 1199 } else if (short.class.equals(primitiveType)) { 1200 return Short.class; 1201 } else if (byte.class.equals(primitiveType)) { 1202 return Byte.class; 1203 } else if (double.class.equals(primitiveType)) { 1204 return Double.class; 1205 } else if (char.class.equals(primitiveType)) { 1206 return Character.class; 1207 } else { 1208 1209 return null; 1210 } 1211 } 1212 1213 /** 1214 * Gets the class for the primitive type corresponding to the primitive wrapper class given. 1215 * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>. 1216 * @param wrapperType the 1217 * @return the primitive type class corresponding to the given wrapper class, 1218 * null if no match is found 1219 */ 1220 public static Class getPrimitiveType(Class wrapperType) { 1221 // does anyone know a better strategy than comparing names? 1222 if (Boolean.class.equals(wrapperType)) { 1223 return boolean.class; 1224 } else if (Float.class.equals(wrapperType)) { 1225 return float.class; 1226 } else if (Long.class.equals(wrapperType)) { 1227 return long.class; 1228 } else if (Integer.class.equals(wrapperType)) { 1229 return int.class; 1230 } else if (Short.class.equals(wrapperType)) { 1231 return short.class; 1232 } else if (Byte.class.equals(wrapperType)) { 1233 return byte.class; 1234 } else if (Double.class.equals(wrapperType)) { 1235 return double.class; 1236 } else if (Character.class.equals(wrapperType)) { 1237 return char.class; 1238 } else { 1239 Log log = LogFactory.getLog(MethodUtils.class); 1240 if (log.isDebugEnabled()) { 1241 log.debug("Not a known primitive wrapper class: " + wrapperType); 1242 } 1243 return null; 1244 } 1245 } 1246 1247 /** 1248 * Find a non primitive representation for given primitive class. 1249 * 1250 * @param clazz the class to find a representation for, not null 1251 * @return the original class if it not a primitive. Otherwise the wrapper class. Not null 1252 */ 1253 public static Class toNonPrimitiveClass(Class clazz) { 1254 if (clazz.isPrimitive()) { 1255 Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz); 1256 // the above method returns 1257 if (primitiveClazz != null) { 1258 return primitiveClazz; 1259 } else { 1260 return clazz; 1261 } 1262 } else { 1263 return clazz; 1264 } 1265 } 1266 1267 1268 /** 1269 * Return the method from the cache, if present. 1270 * 1271 * @param md The method descriptor 1272 * @return The cached method 1273 */ 1274 private static Method getCachedMethod(MethodDescriptor md) { 1275 if (CACHE_METHODS) { 1276 Reference methodRef = (Reference)cache.get(md); 1277 if (methodRef != null) { 1278 return (Method)methodRef.get(); 1279 } 1280 } 1281 return null; 1282 } 1283 1284 /** 1285 * Add a method to the cache. 1286 * 1287 * @param md The method descriptor 1288 * @param method The method to cache 1289 */ 1290 private static void cacheMethod(MethodDescriptor md, Method method) { 1291 if (CACHE_METHODS) { 1292 if (method != null) { 1293 cache.put(md, new WeakReference(method)); 1294 } 1295 } 1296 } 1297 1298 /** 1299 * Represents the key to looking up a Method by reflection. 1300 */ 1301 private static class MethodDescriptor { 1302 private Class cls; 1303 private String methodName; 1304 private Class[] paramTypes; 1305 private boolean exact; 1306 private int hashCode; 1307 1308 /** 1309 * The sole constructor. 1310 * 1311 * @param cls the class to reflect, must not be null 1312 * @param methodName the method name to obtain 1313 * @param paramTypes the array of classes representing the paramater types 1314 * @param exact whether the match has to be exact. 1315 */ 1316 public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) { 1317 if (cls == null) { 1318 throw new IllegalArgumentException("Class cannot be null"); 1319 } 1320 if (methodName == null) { 1321 throw new IllegalArgumentException("Method Name cannot be null"); 1322 } 1323 if (paramTypes == null) { 1324 paramTypes = EMPTY_CLASS_PARAMETERS; 1325 } 1326 1327 this.cls = cls; 1328 this.methodName = methodName; 1329 this.paramTypes = paramTypes; 1330 this.exact= exact; 1331 1332 this.hashCode = methodName.length(); 1333 } 1334 /** 1335 * Checks for equality. 1336 * @param obj object to be tested for equality 1337 * @return true, if the object describes the same Method. 1338 */ 1339 public boolean equals(Object obj) { 1340 if (!(obj instanceof MethodDescriptor)) { 1341 return false; 1342 } 1343 MethodDescriptor md = (MethodDescriptor)obj; 1344 1345 return ( 1346 exact == md.exact && 1347 methodName.equals(md.methodName) && 1348 cls.equals(md.cls) && 1349 java.util.Arrays.equals(paramTypes, md.paramTypes) 1350 ); 1351 } 1352 /** 1353 * Returns the string length of method name. I.e. if the 1354 * hashcodes are different, the objects are different. If the 1355 * hashcodes are the same, need to use the equals method to 1356 * determine equality. 1357 * @return the string length of method name. 1358 */ 1359 public int hashCode() { 1360 return hashCode; 1361 } 1362 } 1363 }