1 /* 2 * Copyright 2002-2008 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.roo.support.util; 18 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.sql.SQLException; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 29 /** 30 * Simple utility class for working with the reflection API and handling 31 * reflection exceptions. 32 * 33 * <p>Only intended for internal use. 34 * 35 * @author Juergen Hoeller 36 * @author Rob Harrop 37 * @author Rod Johnson 38 * @author Costin Leau 39 * @author Sam Brannen 40 * @since 1.2.2 41 */ 42 public abstract class ReflectionUtils { 43 44 /** 45 * Attempt to find a {@link Field field} on the supplied {@link Class} with 46 * the supplied <code>name</code>. Searches all superclasses up to {@link Object}. 47 * @param clazz the class to introspect 48 * @param name the name of the field 49 * @return the corresponding Field object, or <code>null</code> if not found 50 */ 51 public static Field findField(Class clazz, String name) { 52 return findField(clazz, name, null); 53 } 54 55 /** 56 * Attempt to find a {@link Field field} on the supplied {@link Class} with 57 * the supplied <code>name</code> and/or {@link Class type}. Searches all 58 * superclasses up to {@link Object}. 59 * @param clazz the class to introspect 60 * @param name the name of the field (may be <code>null</code> if type is specified) 61 * @param type the type of the field (may be <code>null</code> if name is specified) 62 * @return the corresponding Field object, or <code>null</code> if not found 63 */ 64 public static Field findField(Class clazz, String name, Class type) { 65 Assert.notNull(clazz, "Class must not be null"); 66 Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); 67 Class searchType = clazz; 68 while (!Object.class.equals(searchType) && searchType != null) { 69 Field[] fields = searchType.getDeclaredFields(); 70 for (Field field : fields) { 71 if ((name == null || name.equals(field.getName())) && 72 (type == null || type.equals(field.getType()))) { 73 return field; 74 } 75 } 76 searchType = searchType.getSuperclass(); 77 } 78 return null; 79 } 80 81 /** 82 * Set the field represented by the supplied {@link Field field object} on 83 * the specified {@link Object target object} to the specified 84 * <code>value</code>. In accordance with 85 * {@link Field#set(Object, Object)} semantics, the new value is 86 * automatically unwrapped if the underlying field has a primitive type. 87 * <p>Thrown exceptions are handled via a call to 88 * {@link #handleReflectionException(Exception)}. 89 * @param field the field to set 90 * @param target the target object on which to set the field 91 * @param value the value to set; may be <code>null</code> 92 */ 93 public static void setField(Field field, Object target, Object value) { 94 try { 95 field.set(target, value); 96 } 97 catch (IllegalAccessException ex) { 98 handleReflectionException(ex); 99 throw new IllegalStateException( 100 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 101 } 102 } 103 104 /** 105 * Get the field represented by the supplied {@link Field field object} on 106 * the specified {@link Object target object}. In accordance with 107 * {@link Field#get(Object)} semantics, the returned value is 108 * automatically wrapped if the underlying field has a primitive type. 109 * <p>Thrown exceptions are handled via a call to 110 * {@link #handleReflectionException(Exception)}. 111 * @param field the field to get 112 * @param target the target object from which to get the field 113 * @return the field's current value 114 */ 115 public static Object getField(Field field, Object target) { 116 try { 117 return field.get(target); 118 } 119 catch (IllegalAccessException ex) { 120 handleReflectionException(ex); 121 throw new IllegalStateException( 122 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 123 } 124 } 125 126 /** 127 * Attempt to find a {@link Method} on the supplied class with the supplied name 128 * and no parameters. Searches all superclasses up to <code>Object</code>. 129 * <p>Returns <code>null</code> if no {@link Method} can be found. 130 * @param clazz the class to introspect 131 * @param name the name of the method 132 * @return the Method object, or <code>null</code> if none found 133 */ 134 public static Method findMethod(Class clazz, String name) { 135 return findMethod(clazz, name, new Class[0]); 136 } 137 138 /** 139 * Attempt to find a {@link Method} on the supplied class with the supplied name 140 * and parameter types. Searches all superclasses up to <code>Object</code>. 141 * <p>Returns <code>null</code> if no {@link Method} can be found. 142 * @param clazz the class to introspect 143 * @param name the name of the method 144 * @param paramTypes the parameter types of the method 145 * (may be <code>null</code> to indicate any signature) 146 * @return the Method object, or <code>null</code> if none found 147 */ 148 public static Method findMethod(Class clazz, String name, Class[] paramTypes) { 149 Assert.notNull(clazz, "Class must not be null"); 150 Assert.notNull(name, "Method name must not be null"); 151 Class searchType = clazz; 152 while (!Object.class.equals(searchType) && searchType != null) { 153 Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods()); 154 for (Method method : methods) { 155 if (name.equals(method.getName()) && 156 (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { 157 return method; 158 } 159 } 160 searchType = searchType.getSuperclass(); 161 } 162 return null; 163 } 164 165 /** 166 * Invoke the specified {@link Method} against the supplied target object 167 * with no arguments. The target object can be <code>null</code> when 168 * invoking a static {@link Method}. 169 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 170 * @param method the method to invoke 171 * @param target the target object to invoke the method on 172 * @return the invocation result, if any 173 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 174 */ 175 public static Object invokeMethod(Method method, Object target) { 176 return invokeMethod(method, target, null); 177 } 178 179 /** 180 * Invoke the specified {@link Method} against the supplied target object 181 * with the supplied arguments. The target object can be <code>null</code> 182 * when invoking a static {@link Method}. 183 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 184 * @param method the method to invoke 185 * @param target the target object to invoke the method on 186 * @param args the invocation arguments (may be <code>null</code>) 187 * @return the invocation result, if any 188 */ 189 public static Object invokeMethod(Method method, Object target, Object[] args) { 190 try { 191 return method.invoke(target, args); 192 } 193 catch (Exception ex) { 194 handleReflectionException(ex); 195 } 196 throw new IllegalStateException("Should never get here"); 197 } 198 199 /** 200 * Invoke the specified JDBC API {@link Method} against the supplied 201 * target object with no arguments. 202 * @param method the method to invoke 203 * @param target the target object to invoke the method on 204 * @return the invocation result, if any 205 * @throws SQLException the JDBC API SQLException to rethrow (if any) 206 * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[]) 207 */ 208 public static Object invokeJdbcMethod(Method method, Object target) throws SQLException { 209 return invokeJdbcMethod(method, target, null); 210 } 211 212 /** 213 * Invoke the specified JDBC API {@link Method} against the supplied 214 * target object with the supplied arguments. 215 * @param method the method to invoke 216 * @param target the target object to invoke the method on 217 * @param args the invocation arguments (may be <code>null</code>) 218 * @return the invocation result, if any 219 * @throws SQLException the JDBC API SQLException to rethrow (if any) 220 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 221 */ 222 public static Object invokeJdbcMethod(Method method, Object target, Object[] args) throws SQLException { 223 try { 224 return method.invoke(target, args); 225 } 226 catch (IllegalAccessException ex) { 227 handleReflectionException(ex); 228 } 229 catch (InvocationTargetException ex) { 230 if (ex.getTargetException() instanceof SQLException) { 231 throw (SQLException) ex.getTargetException(); 232 } 233 handleInvocationTargetException(ex); 234 } 235 throw new IllegalStateException("Should never get here"); 236 } 237 238 /** 239 * Handle the given reflection exception. Should only be called if 240 * no checked exception is expected to be thrown by the target method. 241 * <p>Throws the underlying RuntimeException or Error in case of an 242 * InvocationTargetException with such a root cause. Throws an 243 * IllegalStateException with an appropriate message else. 244 * @param ex the reflection exception to handle 245 */ 246 public static void handleReflectionException(Exception ex) { 247 if (ex instanceof NoSuchMethodException) { 248 throw new IllegalStateException("Method not found: " + ex.getMessage()); 249 } 250 if (ex instanceof IllegalAccessException) { 251 throw new IllegalStateException("Could not access method: " + ex.getMessage()); 252 } 253 if (ex instanceof InvocationTargetException) { 254 handleInvocationTargetException((InvocationTargetException) ex); 255 } 256 if (ex instanceof RuntimeException) { 257 throw (RuntimeException) ex; 258 } 259 handleUnexpectedException(ex); 260 } 261 262 /** 263 * Handle the given invocation target exception. Should only be called if 264 * no checked exception is expected to be thrown by the target method. 265 * <p>Throws the underlying RuntimeException or Error in case of such 266 * a root cause. Throws an IllegalStateException else. 267 * @param ex the invocation target exception to handle 268 */ 269 public static void handleInvocationTargetException(InvocationTargetException ex) { 270 rethrowRuntimeException(ex.getTargetException()); 271 } 272 273 /** 274 * Rethrow the given {@link Throwable exception}, which is presumably the 275 * <em>target exception</em> of an {@link InvocationTargetException}. 276 * Should only be called if no checked exception is expected to be thrown by 277 * the target method. 278 * <p>Rethrows the underlying exception cast to an {@link RuntimeException} 279 * or {@link Error} if appropriate; otherwise, throws an 280 * {@link IllegalStateException}. 281 * @param ex the exception to rethrow 282 * @throws RuntimeException the rethrown exception 283 */ 284 public static void rethrowRuntimeException(Throwable ex) { 285 if (ex instanceof RuntimeException) { 286 throw (RuntimeException) ex; 287 } 288 if (ex instanceof Error) { 289 throw (Error) ex; 290 } 291 handleUnexpectedException(ex); 292 } 293 294 /** 295 * Rethrow the given {@link Throwable exception}, which is presumably the 296 * <em>target exception</em> of an {@link InvocationTargetException}. 297 * Should only be called if no checked exception is expected to be thrown by 298 * the target method. 299 * <p>Rethrows the underlying exception cast to an {@link Exception} or 300 * {@link Error} if appropriate; otherwise, throws an 301 * {@link IllegalStateException}. 302 * @param ex the exception to rethrow 303 * @throws Exception the rethrown exception (in case of a checked exception) 304 */ 305 public static void rethrowException(Throwable ex) throws Exception { 306 if (ex instanceof Exception) { 307 throw (Exception) ex; 308 } 309 if (ex instanceof Error) { 310 throw (Error) ex; 311 } 312 handleUnexpectedException(ex); 313 } 314 315 /** 316 * Throws an IllegalStateException with the given exception as root cause. 317 * @param ex the unexpected exception 318 */ 319 private static void handleUnexpectedException(Throwable ex) { 320 throw new IllegalStateException("Unexpected exception thrown", ex); 321 } 322 323 /** 324 * Determine whether the given method explicitly declares the given exception 325 * or one of its superclasses, which means that an exception of that type 326 * can be propagated as-is within a reflective invocation. 327 * @param method the declaring method 328 * @param exceptionType the exception to throw 329 * @return <code>true</code> if the exception can be thrown as-is; 330 * <code>false</code> if it needs to be wrapped 331 */ 332 public static boolean declaresException(Method method, Class exceptionType) { 333 Assert.notNull(method, "Method must not be null"); 334 Class[] declaredExceptions = method.getExceptionTypes(); 335 for (Class declaredException : declaredExceptions) { 336 if (declaredException.isAssignableFrom(exceptionType)) { 337 return true; 338 } 339 } 340 return false; 341 } 342 343 344 /** 345 * Determine whether the given field is a "public static final" constant. 346 * @param field the field to check 347 */ 348 public static boolean isPublicStaticFinal(Field field) { 349 int modifiers = field.getModifiers(); 350 return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); 351 } 352 353 /** 354 * Determine whether the given method is an "equals" method. 355 * @see java.lang.Object#equals 356 */ 357 public static boolean isEqualsMethod(Method method) { 358 if (method == null || !method.getName().equals("equals")) { 359 return false; 360 } 361 Class[] paramTypes = method.getParameterTypes(); 362 return (paramTypes.length == 1 && paramTypes[0] == Object.class); 363 } 364 365 /** 366 * Determine whether the given method is a "hashCode" method. 367 * @see java.lang.Object#hashCode 368 */ 369 public static boolean isHashCodeMethod(Method method) { 370 return (method != null && method.getName().equals("hashCode") && 371 method.getParameterTypes().length == 0); 372 } 373 374 /** 375 * Determine whether the given method is a "toString" method. 376 * @see java.lang.Object#toString() 377 */ 378 public static boolean isToStringMethod(Method method) { 379 return (method != null && method.getName().equals("toString") && 380 method.getParameterTypes().length == 0); 381 } 382 383 384 /** 385 * Make the given field accessible, explicitly setting it accessible if necessary. 386 * The <code>setAccessible(true)</code> method is only called when actually necessary, 387 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 388 * @param field the field to make accessible 389 * @see java.lang.reflect.Field#setAccessible 390 */ 391 public static void makeAccessible(Field field) { 392 if (!Modifier.isPublic(field.getModifiers()) || 393 !Modifier.isPublic(field.getDeclaringClass().getModifiers())) { 394 field.setAccessible(true); 395 } 396 } 397 398 /** 399 * Make the given method accessible, explicitly setting it accessible if necessary. 400 * The <code>setAccessible(true)</code> method is only called when actually necessary, 401 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 402 * @param method the method to make accessible 403 * @see java.lang.reflect.Method#setAccessible 404 */ 405 public static void makeAccessible(Method method) { 406 if (!Modifier.isPublic(method.getModifiers()) || 407 !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 408 method.setAccessible(true); 409 } 410 } 411 412 /** 413 * Make the given constructor accessible, explicitly setting it accessible if necessary. 414 * The <code>setAccessible(true)</code> method is only called when actually necessary, 415 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 416 * @param ctor the constructor to make accessible 417 * @see java.lang.reflect.Constructor#setAccessible 418 */ 419 public static void makeAccessible(Constructor ctor) { 420 if (!Modifier.isPublic(ctor.getModifiers()) || 421 !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) { 422 ctor.setAccessible(true); 423 } 424 } 425 426 427 /** 428 * Perform the given callback operation on all matching methods of the 429 * given class and superclasses. 430 * <p>The same named method occurring on subclass and superclass will 431 * appear twice, unless excluded by a {@link MethodFilter}. 432 * @param targetClass class to start looking at 433 * @param mc the callback to invoke for each method 434 * @see #doWithMethods(Class, MethodCallback, MethodFilter) 435 */ 436 public static void doWithMethods(Class targetClass, MethodCallback mc) throws IllegalArgumentException { 437 doWithMethods(targetClass, mc, null); 438 } 439 440 /** 441 * Perform the given callback operation on all matching methods of the 442 * given class and superclasses. 443 * <p>The same named method occurring on subclass and superclass will 444 * appear twice, unless excluded by the specified {@link MethodFilter}. 445 * @param targetClass class to start looking at 446 * @param mc the callback to invoke for each method 447 * @param mf the filter that determines the methods to apply the callback to 448 */ 449 public static void doWithMethods(Class targetClass, MethodCallback mc, MethodFilter mf) 450 throws IllegalArgumentException { 451 452 // Keep backing up the inheritance hierarchy. 453 do { 454 Method[] methods = targetClass.getDeclaredMethods(); 455 for (Method method : methods) { 456 if (mf != null && !mf.matches(method)) { 457 continue; 458 } 459 try { 460 mc.doWith(method); 461 } 462 catch (IllegalAccessException ex) { 463 throw new IllegalStateException( 464 "Shouldn't be illegal to access method '" + method.getName() + "': " + ex); 465 } 466 } 467 targetClass = targetClass.getSuperclass(); 468 } 469 while (targetClass != null); 470 } 471 472 /** 473 * Get all declared methods on the leaf class and all superclasses. 474 * Leaf class methods are included first. 475 */ 476 public static Method[] getAllDeclaredMethods(Class leafClass) throws IllegalArgumentException { 477 final List<Method> methods = new ArrayList<Method>(32); 478 doWithMethods(leafClass, new MethodCallback() { 479 public void doWith(Method method) { 480 methods.add(method); 481 } 482 }); 483 return methods.toArray(new Method[methods.size()]); 484 } 485 486 487 /** 488 * Invoke the given callback on all fields in the target class, 489 * going up the class hierarchy to get all declared fields. 490 * @param targetClass the target class to analyze 491 * @param fc the callback to invoke for each field 492 */ 493 public static void doWithFields(Class targetClass, FieldCallback fc) throws IllegalArgumentException { 494 doWithFields(targetClass, fc, null); 495 } 496 497 /** 498 * Invoke the given callback on all fields in the target class, 499 * going up the class hierarchy to get all declared fields. 500 * @param targetClass the target class to analyze 501 * @param fc the callback to invoke for each field 502 * @param ff the filter that determines the fields to apply the callback to 503 */ 504 public static void doWithFields(Class targetClass, FieldCallback fc, FieldFilter ff) 505 throws IllegalArgumentException { 506 507 // Keep backing up the inheritance hierarchy. 508 do { 509 // Copy each field declared on this class unless it's static or file. 510 Field[] fields = targetClass.getDeclaredFields(); 511 for (Field field : fields) { 512 // Skip static and final fields. 513 if (ff != null && !ff.matches(field)) { 514 continue; 515 } 516 try { 517 fc.doWith(field); 518 } 519 catch (IllegalAccessException ex) { 520 throw new IllegalStateException( 521 "Shouldn't be illegal to access field '" + field.getName() + "': " + ex); 522 } 523 } 524 targetClass = targetClass.getSuperclass(); 525 } 526 while (targetClass != null && targetClass != Object.class); 527 } 528 529 /** 530 * Given the source object and the destination, which must be the same class 531 * or a subclass, copy all fields, including inherited fields. Designed to 532 * work on objects with public no-arg constructors. 533 * @throws IllegalArgumentException if the arguments are incompatible 534 */ 535 public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException { 536 if (src == null) { 537 throw new IllegalArgumentException("Source for field copy cannot be null"); 538 } 539 if (dest == null) { 540 throw new IllegalArgumentException("Destination for field copy cannot be null"); 541 } 542 if (!src.getClass().isAssignableFrom(dest.getClass())) { 543 throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() + 544 "] must be same or subclass as source class [" + src.getClass().getName() + "]"); 545 } 546 doWithFields(src.getClass(), new FieldCallback() { 547 public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { 548 makeAccessible(field); 549 Object srcValue = field.get(src); 550 field.set(dest, srcValue); 551 } 552 }, COPYABLE_FIELDS); 553 } 554 555 556 /** 557 * Action to take on each method. 558 */ 559 public static interface MethodCallback { 560 561 /** 562 * Perform an operation using the given method. 563 * @param method the method to operate on 564 */ 565 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; 566 } 567 568 569 /** 570 * Callback optionally used to method fields to be operated on by a method callback. 571 */ 572 public static interface MethodFilter { 573 574 /** 575 * Determine whether the given method matches. 576 * @param method the method to check 577 */ 578 boolean matches(Method method); 579 } 580 581 582 /** 583 * Callback interface invoked on each field in the hierarchy. 584 */ 585 public static interface FieldCallback { 586 587 /** 588 * Perform an operation using the given field. 589 * @param field the field to operate on 590 */ 591 void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; 592 } 593 594 595 /** 596 * Callback optionally used to filter fields to be operated on by a field callback. 597 */ 598 public static interface FieldFilter { 599 600 /** 601 * Determine whether the given field matches. 602 * @param field the field to check 603 */ 604 boolean matches(Field field); 605 } 606 607 608 /** 609 * Pre-built FieldFilter that matches all non-static, non-final fields. 610 */ 611 public static FieldFilter COPYABLE_FIELDS = new FieldFilter() { 612 public boolean matches(Field field) { 613 return !(Modifier.isStatic(field.getModifiers()) || 614 Modifier.isFinal(field.getModifiers())); 615 } 616 }; 617 618 }