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