001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.ws.util.spring; 015 016import java.lang.reflect.Constructor; 017import java.lang.reflect.Field; 018import java.lang.reflect.InvocationTargetException; 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021import java.util.Arrays; 022import java.util.LinkedList; 023import java.util.List; 024 025/** 026 * Simple utility class for working with the reflection API and handling 027 * reflection exceptions. 028 * <p>Only intended for internal use. 029 * 030 * @author Juergen Hoeller 031 * @author Rob Harrop 032 * @author Rod Johnson 033 * @author Costin Leau 034 * @author Sam Brannen 035 * @since 1.2.2 036 */ 037public abstract class ReflectionUtils { 038 039 /** 040 * Attempt to find a {@link Field field} on the supplied {@link Class} with 041 * the supplied <code>name</code> and {@link Class type}. Searches all 042 * superclasses up to {@link Object}. 043 * 044 * @param clazz the class to introspect 045 * @param name the name of the field 046 * @param type the type of the field 047 * 048 * @return the corresponding Field object, or <code>null</code> if not found 049 * 050 * @throws IllegalArgumentException if the class or field type is 051 * <code>null</code> or if the field name is <em>empty</em> 052 */ 053 public static Field findField(final Class clazz, final String name, final Class type) { 054 Assert.notNull(clazz, "The 'class to introspect' supplied to findField() can not be null."); 055 Assert.hasText(name, "The field name supplied to findField() can not be empty."); 056 Assert.notNull(type, "The field type supplied to findField() can not be null."); 057 Class searchType = clazz; 058 while (!Object.class.equals(searchType) && searchType != null) { 059 final Field[] fields = searchType.getDeclaredFields(); 060 for (int i = 0; i < fields.length; i++) { 061 Field field = fields[i]; 062 if (name.equals(field.getName()) && type.equals(field.getType())) { 063 return field; 064 } 065 } 066 searchType = searchType.getSuperclass(); 067 } 068 return null; 069 } 070 071 /** 072 * Set the field represented by the supplied {@link Field field object} on 073 * the specified {@link Object target object} to the specified 074 * <code>value</code>. In accordance with 075 * {@link Field#set(Object, Object)} semantics, the new value is 076 * automatically unwrapped if the underlying field has a primitive type. 077 * <p>Thrown exceptions are handled via a call to 078 * {@link #handleReflectionException(Exception)}. 079 * 080 * @param field the field to set 081 * @param target the target object on which to set the field 082 * @param value the value to set; may be <code>null</code> 083 */ 084 public static void setField(Field field, Object target, Object value) { 085 try { 086 field.set(target, value); 087 } catch (IllegalAccessException ex) { 088 handleReflectionException(ex); 089 throw new IllegalStateException( 090 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 091 } 092 } 093 094 /** 095 * Attempt to find a {@link Method} on the supplied class with the supplied name 096 * and no parameters. Searches all superclasses up to <code>Object</code>. 097 * <p>Returns <code>null</code> if no {@link Method} can be found. 098 * 099 * @param clazz the class to introspect 100 * @param name the name of the method 101 * 102 * @return the Method object, or <code>null</code> if none found 103 */ 104 public static Method findMethod(Class clazz, String name) { 105 return findMethod(clazz, name, new Class[0]); 106 } 107 108 /** 109 * Attempt to find a {@link Method} on the supplied class with the supplied name 110 * and parameter types. Searches all superclasses up to <code>Object</code>. 111 * <p>Returns <code>null</code> if no {@link Method} can be found. 112 * 113 * @param clazz the class to introspect 114 * @param name the name of the method 115 * @param paramTypes the parameter types of the method 116 * 117 * @return the Method object, or <code>null</code> if none found 118 */ 119 public static Method findMethod(Class clazz, String name, Class[] paramTypes) { 120 Assert.notNull(clazz, "Class must not be null"); 121 Assert.notNull(name, "Method name must not be null"); 122 Class searchType = clazz; 123 while (!Object.class.equals(searchType) && searchType != null) { 124 Method[] methods = 125 (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods()); 126 for (int i = 0; i < methods.length; i++) { 127 Method method = methods[i]; 128 if (name.equals(method.getName()) 129 && Arrays.equals(paramTypes, method.getParameterTypes())) { 130 return method; 131 } 132 } 133 searchType = searchType.getSuperclass(); 134 } 135 return null; 136 } 137 138 /** 139 * Invoke the specified {@link Method} against the supplied target object 140 * with no arguments. The target object can be <code>null</code> when 141 * invoking a static {@link Method}. 142 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 143 * 144 * @param method the method to invoke 145 * @param target the target object to invoke the method on 146 * 147 * @return the invocation result, if any 148 * 149 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 150 */ 151 public static Object invokeMethod(Method method, Object target) { 152 return invokeMethod(method, target, null); 153 } 154 155 /** 156 * Invoke the specified {@link Method} against the supplied target object 157 * with the supplied arguments. The target object can be <code>null</code> 158 * when invoking a static {@link Method}. 159 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 160 * 161 * @param method the method to invoke 162 * @param target the target object to invoke the method on 163 * @param args the invocation arguments (may be <code>null</code>) 164 * 165 * @return the invocation result, if any 166 * 167 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 168 */ 169 public static Object invokeMethod(Method method, Object target, Object[] args) { 170 try { 171 return method.invoke(target, args); 172 } catch (IllegalAccessException ex) { 173 handleReflectionException(ex); 174 throw new IllegalStateException( 175 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 176 } catch (InvocationTargetException ex) { 177 handleReflectionException(ex); 178 throw new IllegalStateException( 179 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 180 } 181 } 182 183 /** 184 * Handle the given reflection exception. Should only be called if 185 * no checked exception is expected to be thrown by the target method. 186 * <p>Throws the underlying RuntimeException or Error in case of an 187 * InvocationTargetException with such a root cause. Throws an 188 * IllegalStateException with an appropriate message else. 189 * 190 * @param ex the reflection exception to handle 191 */ 192 public static void handleReflectionException(Exception ex) { 193 if (ex instanceof NoSuchMethodException) { 194 throw new IllegalStateException("Method not found: " + ex.getMessage()); 195 } 196 if (ex instanceof IllegalAccessException) { 197 throw new IllegalStateException("Could not access method: " + ex.getMessage()); 198 } 199 if (ex instanceof InvocationTargetException) { 200 handleInvocationTargetException((InvocationTargetException) ex); 201 } 202 throw new IllegalStateException( 203 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 204 } 205 206 /** 207 * Handle the given invocation target exception. Should only be called if 208 * no checked exception is expected to be thrown by the target method. 209 * <p>Throws the underlying RuntimeException or Error in case of such 210 * a root cause. Throws an IllegalStateException else. 211 * 212 * @param ex the invocation target exception to handle 213 */ 214 public static void handleInvocationTargetException(InvocationTargetException ex) { 215 if (ex.getTargetException() instanceof RuntimeException) { 216 throw (RuntimeException) ex.getTargetException(); 217 } 218 if (ex.getTargetException() instanceof Error) { 219 throw (Error) ex.getTargetException(); 220 } 221 throw new IllegalStateException( 222 "Unexpected exception thrown by method - " 223 + ex.getTargetException().getClass().getName() 224 + ": " 225 + ex.getTargetException().getMessage()); 226 } 227 228 /** 229 * Rethrow the given {@link Throwable exception}, which is presumably the 230 * <em>target exception</em> of an {@link InvocationTargetException}. 231 * Should only be called if no checked exception is expected to be thrown by 232 * the target method. 233 * <p> Rethrows the underlying exception cast to an {@link Exception} or 234 * {@link Error} if appropriate; otherwise, throws an 235 * {@link IllegalStateException}. 236 * 237 * @param ex the exception to rethrow 238 * 239 * @throws Exception the rethrown exception (in case of a checked exception) 240 */ 241 public static void rethrowException(Throwable ex) throws Exception { 242 if (ex instanceof Exception) { 243 throw (Exception) ex; 244 } 245 if (ex instanceof Error) { 246 throw (Error) ex; 247 } 248 throw new IllegalStateException( 249 "Unexpected exception thrown by method - " 250 + ex.getClass().getName() 251 + ": " 252 + ex.getMessage()); 253 } 254 255 /** 256 * Determine whether the given method explicitly declares the given exception 257 * or one of its superclasses, which means that an exception of that type 258 * can be propagated as-is within a reflective invocation. 259 * 260 * @param method the declaring method 261 * @param exceptionType the exception to throw 262 * 263 * @return <code>true</code> if the exception can be thrown as-is; 264 * <code>false</code> if it needs to be wrapped 265 */ 266 public static boolean declaresException(Method method, Class exceptionType) { 267 Assert.notNull(method, "Method must not be null"); 268 Class[] declaredExceptions = method.getExceptionTypes(); 269 for (int i = 0; i < declaredExceptions.length; i++) { 270 Class declaredException = declaredExceptions[i]; 271 if (declaredException.isAssignableFrom(exceptionType)) { 272 return true; 273 } 274 } 275 return false; 276 } 277 278 /** 279 * Determine whether the given field is a "public static final" constant. 280 * 281 * @param field the field to check 282 */ 283 public static boolean isPublicStaticFinal(Field field) { 284 int modifiers = field.getModifiers(); 285 return (Modifier.isPublic(modifiers) 286 && Modifier.isStatic(modifiers) 287 && Modifier.isFinal(modifiers)); 288 } 289 290 /** 291 * Make the given field accessible, explicitly setting it accessible if necessary. 292 * The <code>setAccessible(true)</code> method is only called when actually necessary, 293 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 294 * 295 * @param field the field to make accessible 296 * 297 * @see java.lang.reflect.Field#setAccessible 298 */ 299 public static void makeAccessible(Field field) { 300 if (!Modifier.isPublic(field.getModifiers()) 301 || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) { 302 field.setAccessible(true); 303 } 304 } 305 306 /** 307 * Make the given method accessible, explicitly setting it accessible if necessary. 308 * The <code>setAccessible(true)</code> method is only called when actually necessary, 309 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 310 * 311 * @param method the method to make accessible 312 * 313 * @see java.lang.reflect.Method#setAccessible 314 */ 315 public static void makeAccessible(Method method) { 316 if (!Modifier.isPublic(method.getModifiers()) 317 || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 318 method.setAccessible(true); 319 } 320 } 321 322 /** 323 * Make the given constructor accessible, explicitly setting it accessible if necessary. 324 * The <code>setAccessible(true)</code> method is only called when actually necessary, 325 * to avoid unnecessary conflicts with a JVM SecurityManager (if active). 326 * 327 * @param ctor the constructor to make accessible 328 * 329 * @see java.lang.reflect.Constructor#setAccessible 330 */ 331 public static void makeAccessible(Constructor ctor) { 332 if (!Modifier.isPublic(ctor.getModifiers()) 333 || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) { 334 ctor.setAccessible(true); 335 } 336 } 337 338 /** 339 * Perform the given callback operation on all matching methods of the 340 * given class and superclasses. 341 * <p>The same named method occurring on subclass and superclass will 342 * appear twice, unless excluded by a {@link MethodFilter}. 343 * 344 * @param targetClass class to start looking at 345 * @param mc the callback to invoke for each method 346 * 347 * @see #doWithMethods(Class, MethodCallback, MethodFilter) 348 */ 349 public static void doWithMethods(Class targetClass, MethodCallback mc) 350 throws IllegalArgumentException { 351 doWithMethods(targetClass, mc, null); 352 } 353 354 /** 355 * Perform the given callback operation on all matching methods of the 356 * given class and superclasses. 357 * <p>The same named method occurring on subclass and superclass will 358 * appear twice, unless excluded by the specified {@link MethodFilter}. 359 * 360 * @param targetClass class to start looking at 361 * @param mc the callback to invoke for each method 362 * @param mf the filter that determines the methods to apply the callback to 363 */ 364 public static void doWithMethods(Class targetClass, MethodCallback mc, MethodFilter mf) 365 throws IllegalArgumentException { 366 367 // Keep backing up the inheritance hierarchy. 368 do { 369 Method[] methods = targetClass.getDeclaredMethods(); 370 for (int i = 0; i < methods.length; i++) { 371 if (mf != null && !mf.matches(methods[i])) { 372 continue; 373 } 374 try { 375 mc.doWith(methods[i]); 376 } catch (IllegalAccessException ex) { 377 throw new IllegalStateException( 378 "Shouldn't be illegal to access method '" + methods[i].getName() + "': " + ex); 379 } 380 } 381 targetClass = targetClass.getSuperclass(); 382 } while (targetClass != null); 383 } 384 385 /** 386 * Get all declared methods on the leaf class and all superclasses. 387 * Leaf class methods are included first. 388 */ 389 public static Method[] getAllDeclaredMethods(Class leafClass) throws IllegalArgumentException { 390 final List list = new LinkedList(); 391 doWithMethods( 392 leafClass, 393 new MethodCallback() { 394 @Override 395 public void doWith(Method m) { 396 list.add(m); 397 } 398 }); 399 return (Method[]) list.toArray(new Method[list.size()]); 400 } 401 402 /** 403 * Invoke the given callback on all fields in the target class, 404 * going up the class hierarchy to get all declared fields. 405 * 406 * @param targetClass the target class to analyze 407 * @param fc the callback to invoke for each field 408 */ 409 public static void doWithFields(Class targetClass, FieldCallback fc) 410 throws IllegalArgumentException { 411 doWithFields(targetClass, fc, null); 412 } 413 414 /** 415 * Invoke the given callback on all fields in the target class, 416 * going up the class hierarchy to get all declared fields. 417 * 418 * @param targetClass the target class to analyze 419 * @param fc the callback to invoke for each field 420 * @param ff the filter that determines the fields to apply the callback to 421 */ 422 public static void doWithFields(Class targetClass, FieldCallback fc, FieldFilter ff) 423 throws IllegalArgumentException { 424 425 // Keep backing up the inheritance hierarchy. 426 do { 427 // Copy each field declared on this class unless it's static or file. 428 Field[] fields = targetClass.getDeclaredFields(); 429 for (int i = 0; i < fields.length; i++) { 430 // Skip static and final fields. 431 if (ff != null && !ff.matches(fields[i])) { 432 continue; 433 } 434 try { 435 fc.doWith(fields[i]); 436 } catch (IllegalAccessException ex) { 437 throw new IllegalStateException( 438 "Shouldn't be illegal to access field '" + fields[i].getName() + "': " + ex); 439 } 440 } 441 targetClass = targetClass.getSuperclass(); 442 } while (targetClass != null && targetClass != Object.class); 443 } 444 445 /** 446 * Given the source object and the destination, which must be the same class 447 * or a subclass, copy all fields, including inherited fields. Designed to 448 * work on objects with public no-arg constructors. 449 * 450 * @throws IllegalArgumentException if the arguments are incompatible 451 */ 452 public static void shallowCopyFieldState(final Object src, final Object dest) 453 throws IllegalArgumentException { 454 if (src == null) { 455 throw new IllegalArgumentException("Source for field copy cannot be null"); 456 } 457 if (dest == null) { 458 throw new IllegalArgumentException("Destination for field copy cannot be null"); 459 } 460 if (!src.getClass().isAssignableFrom(dest.getClass())) { 461 throw new IllegalArgumentException( 462 "Destination class [" 463 + dest.getClass().getName() 464 + "] must be same or subclass as source class [" 465 + src.getClass().getName() 466 + "]"); 467 } 468 doWithFields( 469 src.getClass(), 470 new ReflectionUtils.FieldCallback() { 471 @Override 472 public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { 473 makeAccessible(field); 474 Object srcValue = field.get(src); 475 field.set(dest, srcValue); 476 } 477 }, 478 ReflectionUtils.COPYABLE_FIELDS); 479 } 480 481 /** 482 * Action to take on each method. 483 */ 484 public static interface MethodCallback { 485 486 /** 487 * Perform an operation using the given method. 488 * 489 * @param method the method to operate on 490 */ 491 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; 492 } 493 494 /** 495 * Callback optionally used to method fields to be operated on by a method callback. 496 */ 497 public static interface MethodFilter { 498 499 /** 500 * Determine whether the given method matches. 501 * 502 * @param method the method to check 503 */ 504 boolean matches(Method method); 505 } 506 507 /** 508 * Callback interface invoked on each field in the hierarchy. 509 */ 510 public static interface FieldCallback { 511 512 /** 513 * Perform an operation using the given field. 514 * 515 * @param field the field to operate on 516 */ 517 void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; 518 } 519 520 /** 521 * Callback optionally used to filter fields to be operated on by a field callback. 522 */ 523 public static interface FieldFilter { 524 525 /** 526 * Determine whether the given field matches. 527 * 528 * @param field the field to check 529 */ 530 boolean matches(Field field); 531 } 532 533 /** 534 * Pre-built FieldFilter that matches all non-static, non-final fields. 535 */ 536 public static FieldFilter COPYABLE_FIELDS = 537 new FieldFilter() { 538 @Override 539 public boolean matches(Field field) { 540 return !(Modifier.isStatic(field.getModifiers()) 541 || Modifier.isFinal(field.getModifiers())); 542 } 543 }; 544}