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.Field;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Modifier;
023: import java.util.Arrays;
024: import java.util.LinkedList;
025: import java.util.List;
026:
027: /**
028: * Simple utility class for handling reflection exceptions.
029: * Only intended for internal use.
030: *
031: * @author Juergen Hoeller
032: * @author Rob Harrop
033: * @author Rod Johnson
034: * @author Costin Leau
035: * @since 1.2.2
036: */
037: public abstract class ReflectionUtils {
038:
039: /**
040: * Attempt to find a {@link Method} on the supplied type with the supplied name
041: * and parameter types. Searches all superclasses up to <code>Object</code>.
042: * <p>Returns <code>null</code> if no {@link Method} can be found.
043: * @param clazz the class to introspect
044: * @param name the name of the method
045: * @param paramTypes the parameter types of the method
046: * @return the Method object, or <code>null</code> if none found
047: */
048: public static Method findMethod(Class clazz, String name,
049: Class[] paramTypes) {
050: Assert.notNull(clazz, "Class must not be null");
051: Assert.notNull(name, "Method name must not be null");
052: Class searchType = clazz;
053: while (!Object.class.equals(searchType) && searchType != null) {
054: Method[] methods = (searchType.isInterface() ? searchType
055: .getMethods() : searchType.getDeclaredMethods());
056: for (int i = 0; i < methods.length; i++) {
057: Method method = methods[i];
058: if (name.equals(method.getName())
059: && Arrays.equals(paramTypes, method
060: .getParameterTypes())) {
061: return method;
062: }
063: }
064: searchType = searchType.getSuperclass();
065: }
066: return null;
067: }
068:
069: /**
070: * Invoke the specified {@link Method} against the supplied target object
071: * with no arguments. The target object can be <code>null</code> when
072: * invoking a static {@link Method}.
073: * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
074: * @param method the method to invoke
075: * @param target the target object to invoke the method on
076: * @return the invocation result, if any
077: * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
078: */
079: public static Object invokeMethod(Method method, Object target) {
080: return invokeMethod(method, target, null);
081: }
082:
083: /**
084: * Invoke the specified {@link Method} against the supplied target object
085: * with the supplied arguments. The target object can be <code>null</code>
086: * when invoking a static {@link Method}.
087: * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
088: * @param method the method to invoke
089: * @param target the target object to invoke the method on
090: * @param args the invocation arguments (may be <code>null</code>)
091: * @return the invocation result, if any
092: * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
093: */
094: public static Object invokeMethod(Method method, Object target,
095: Object[] args) {
096: try {
097: return method.invoke(target, args);
098: } catch (IllegalAccessException ex) {
099: handleReflectionException(ex);
100: throw new IllegalStateException(
101: "Unexpected reflection exception - "
102: + ex.getClass().getName() + ": "
103: + ex.getMessage());
104: } catch (InvocationTargetException ex) {
105: handleReflectionException(ex);
106: throw new IllegalStateException(
107: "Unexpected reflection exception - "
108: + ex.getClass().getName() + ": "
109: + ex.getMessage());
110: }
111: }
112:
113: /**
114: * Handle the given reflection exception. Should only be called if
115: * no checked exception is expected to be thrown by the target method.
116: * <p>Throws the underlying RuntimeException or Error in case of an
117: * InvocationTargetException with such a root cause. Throws an
118: * IllegalStateException with an appropriate message else.
119: * @param ex the reflection exception to handle
120: */
121: public static void handleReflectionException(Exception ex) {
122: if (ex instanceof NoSuchMethodException) {
123: throw new IllegalStateException("Method not found: "
124: + ex.getMessage());
125: }
126: if (ex instanceof IllegalAccessException) {
127: throw new IllegalStateException("Could not access method: "
128: + ex.getMessage());
129: }
130: if (ex instanceof InvocationTargetException) {
131: handleInvocationTargetException((InvocationTargetException) ex);
132: }
133: throw new IllegalStateException(
134: "Unexpected reflection exception - "
135: + ex.getClass().getName() + ": "
136: + ex.getMessage());
137: }
138:
139: /**
140: * Handle the given invocation target exception. Should only be called if
141: * no checked exception is expected to be thrown by the target method.
142: * <p>Throws the underlying RuntimeException or Error in case of such
143: * a root cause. Throws an IllegalStateException else.
144: * @param ex the invocation target exception to handle
145: */
146: public static void handleInvocationTargetException(
147: InvocationTargetException ex) {
148: if (ex.getTargetException() instanceof RuntimeException) {
149: throw (RuntimeException) ex.getTargetException();
150: }
151: if (ex.getTargetException() instanceof Error) {
152: throw (Error) ex.getTargetException();
153: }
154: throw new IllegalStateException(
155: "Unexpected exception thrown by method - "
156: + ex.getTargetException().getClass().getName()
157: + ": " + ex.getTargetException().getMessage());
158: }
159:
160: /**
161: * Determine whether the given method explicitly declares the given exception
162: * or one of its superclasses, which means that an exception of that type
163: * can be propagated as-is within a reflective invocation.
164: * @param method the declaring method
165: * @param exceptionType the exception to throw
166: * @return <code>true</code> if the exception can be thrown as-is;
167: * <code>false</code> if it needs to be wrapped
168: */
169: public static boolean declaresException(Method method,
170: Class exceptionType) {
171: Assert.notNull(method, "Method must not be null");
172: Class[] declaredExceptions = method.getExceptionTypes();
173: for (int i = 0; i < declaredExceptions.length; i++) {
174: Class declaredException = declaredExceptions[i];
175: if (declaredException.isAssignableFrom(exceptionType)) {
176: return true;
177: }
178: }
179: return false;
180: }
181:
182: /**
183: * Determine whether the given field is a "public static final" constant.
184: * @param field the field to check
185: */
186: public static boolean isPublicStaticFinal(Field field) {
187: int modifiers = field.getModifiers();
188: return (Modifier.isPublic(modifiers)
189: && Modifier.isStatic(modifiers) && Modifier
190: .isFinal(modifiers));
191: }
192:
193: /**
194: * Make the given field accessible, explicitly setting it accessible if necessary.
195: * The <code>setAccessible(true)</code> method is only called when actually necessary,
196: * to avoid unnecessary conflicts with a JVM SecurityManager (if active).
197: * @param field the field to make accessible
198: * @see java.lang.reflect.Field#setAccessible
199: */
200: public static void makeAccessible(Field field) {
201: if (!Modifier.isPublic(field.getModifiers())
202: || !Modifier.isPublic(field.getDeclaringClass()
203: .getModifiers())) {
204: field.setAccessible(true);
205: }
206: }
207:
208: /**
209: * Perform the given callback operation on all matching methods of the
210: * given class and superclasses.
211: * <p>The same named method occurring on subclass and superclass will
212: * appear twice, unless excluded by a {@link MethodFilter}.
213: * @param targetClass class to start looking at
214: * @param mc the callback to invoke for each method
215: * @see #doWithMethods(Class, MethodCallback, MethodFilter)
216: */
217: public static void doWithMethods(Class targetClass,
218: MethodCallback mc) throws IllegalArgumentException {
219: doWithMethods(targetClass, mc, null);
220: }
221:
222: /**
223: * Perform the given callback operation on all matching methods of the
224: * given class and superclasses.
225: * <p>The same named method occurring on subclass and superclass will
226: * appear twice, unless excluded by the specified {@link MethodFilter}.
227: * @param targetClass class to start looking at
228: * @param mc the callback to invoke for each method
229: * @param mf the filter that determines the methods to apply the callback to
230: */
231: public static void doWithMethods(Class targetClass,
232: MethodCallback mc, MethodFilter mf)
233: throws IllegalArgumentException {
234:
235: // Keep backing up the inheritance hierarchy.
236: do {
237: Method[] methods = targetClass.getDeclaredMethods();
238: for (int i = 0; i < methods.length; i++) {
239: if (mf != null && !mf.matches(methods[i])) {
240: continue;
241: }
242: try {
243: mc.doWith(methods[i]);
244: } catch (IllegalAccessException ex) {
245: throw new IllegalStateException(
246: "Shouldn't be illegal to access method '"
247: + methods[i].getName() + "': " + ex);
248: }
249: }
250: targetClass = targetClass.getSuperclass();
251: } while (targetClass != null);
252: }
253:
254: /**
255: * Get all declared methods on the leaf class and all superclasses.
256: * Leaf class methods are included first.
257: */
258: public static Method[] getAllDeclaredMethods(Class leafClass)
259: throws IllegalArgumentException {
260: final List list = new LinkedList();
261: doWithMethods(leafClass, new MethodCallback() {
262: public void doWith(Method m) {
263: list.add(m);
264: }
265: });
266: return (Method[]) list.toArray(new Method[list.size()]);
267: }
268:
269: /**
270: * Invoke the given callback on all fields in the target class,
271: * going up the class hierarchy to get all declared fields.
272: * @param targetClass the target class to analyze
273: * @param fc the callback to invoke for each field
274: */
275: public static void doWithFields(Class targetClass, FieldCallback fc)
276: throws IllegalArgumentException {
277: doWithFields(targetClass, fc, null);
278: }
279:
280: /**
281: * Invoke the given callback on all fields in the target class,
282: * going up the class hierarchy to get all declared fields.
283: * @param targetClass the target class to analyze
284: * @param fc the callback to invoke for each field
285: * @param ff the filter that determines the fields to apply the callback to
286: */
287: public static void doWithFields(Class targetClass,
288: FieldCallback fc, FieldFilter ff)
289: throws IllegalArgumentException {
290:
291: // Keep backing up the inheritance hierarchy.
292: do {
293: // Copy each field declared on this class unless it's static or file.
294: Field[] fields = targetClass.getDeclaredFields();
295: for (int i = 0; i < fields.length; i++) {
296: // Skip static and final fields.
297: if (ff != null && !ff.matches(fields[i])) {
298: continue;
299: }
300: try {
301: fc.doWith(fields[i]);
302: } catch (IllegalAccessException ex) {
303: throw new IllegalStateException(
304: "Shouldn't be illegal to access field '"
305: + fields[i].getName() + "': " + ex);
306: }
307: }
308: targetClass = targetClass.getSuperclass();
309: } while (targetClass != null && targetClass != Object.class);
310: }
311:
312: /**
313: * Given the source object and the destination, which must be the same class
314: * or a subclass, copy all fields, including inherited fields. Designed to
315: * work on objects with public no-arg constructors.
316: * @throws IllegalArgumentException if the arguments are incompatible
317: */
318: public static void shallowCopyFieldState(final Object src,
319: final Object dest) throws IllegalArgumentException {
320: if (src == null) {
321: throw new IllegalArgumentException(
322: "Source for field copy cannot be null");
323: }
324: if (dest == null) {
325: throw new IllegalArgumentException(
326: "Destination for field copy cannot be null");
327: }
328: if (!src.getClass().isAssignableFrom(dest.getClass())) {
329: throw new IllegalArgumentException("Destination class ["
330: + dest.getClass().getName()
331: + "] must be same or subclass as source class ["
332: + src.getClass().getName() + "]");
333: }
334: doWithFields(src.getClass(),
335: new ReflectionUtils.FieldCallback() {
336: public void doWith(Field field)
337: throws IllegalArgumentException,
338: IllegalAccessException {
339: makeAccessible(field);
340: Object srcValue = field.get(src);
341: field.set(dest, srcValue);
342: }
343: }, ReflectionUtils.COPYABLE_FIELDS);
344: }
345:
346: /**
347: * Action to take on each method.
348: */
349: public static interface MethodCallback {
350:
351: /**
352: * Perform an operation using the given method.
353: * @param method the method which will have been made accessible before this invocation
354: */
355: void doWith(Method method) throws IllegalArgumentException,
356: IllegalAccessException;
357: }
358:
359: /**
360: * Callback optionally used to method fields to be operated on by a method callback.
361: */
362: public static interface MethodFilter {
363:
364: /**
365: * Determine whether the given method matches.
366: * @param method the method to check
367: */
368: boolean matches(Method method);
369: }
370:
371: /**
372: * Callback interface invoked on each field in the hierarchy.
373: */
374: public static interface FieldCallback {
375:
376: /**
377: * Perform an operation using the given field.
378: * @param field the field which will have been made accessible before this invocation
379: */
380: void doWith(Field field) throws IllegalArgumentException,
381: IllegalAccessException;
382: }
383:
384: /**
385: * Callback optionally used to filter fields to be operated on by a field callback.
386: */
387: public static interface FieldFilter {
388:
389: /**
390: * Determine whether the given field matches.
391: * @param field the field to check
392: */
393: boolean matches(Field field);
394: }
395:
396: /**
397: * Pre-built FieldFilter that matches all non-static, non-final fields.
398: */
399: public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
400: public boolean matches(Field field) {
401: return !(Modifier.isStatic(field.getModifiers()) || Modifier
402: .isFinal(field.getModifiers()));
403: }
404: };
405:
406: }
|