View Javadoc

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 }