/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.roo.support.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.Validate;
/**
* Simple utility class for working with the reflection API and handling
* reflection exceptions.
* <p>
* Only intended for internal use.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Rod Johnson
* @author Costin Leau
* @author Sam Brannen
* @since 1.2.2
*/
@Deprecated
public abstract class ReflectionUtils {
/**
* Callback interface invoked on each field in the hierarchy.
*/
public static interface FieldCallback {
/**
* Perform an operation using the given field.
*
* @param field the field to operate on
*/
void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to filter fields to be operated on by a field
* callback.
*/
public static interface FieldFilter {
/**
* Determine whether the given field matches.
*
* @param field the field to check
*/
boolean matches(Field field);
}
/**
* Action to take on each method.
*/
public static interface MethodCallback {
/**
* Perform an operation using the given method.
*
* @param method the method to operate on
*/
void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to method fields to be operated on by a method
* callback.
*/
public static interface MethodFilter {
/**
* Determine whether the given method matches.
*
* @param method the method to check
*/
boolean matches(Method method);
}
/**
* Pre-built FieldFilter that matches all non-static, non-final fields.
*/
public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
public boolean matches(final Field field) {
return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
}
};
/**
* Determine whether the given method explicitly declares the given
* exception or one of its superclasses, which means that an exception of
* that type can be propagated as-is within a reflective invocation.
*
* @param method the declaring method
* @param exceptionType the exception to throw
* @return <code>true</code> if the exception can be thrown as-is;
* <code>false</code> if it needs to be wrapped
*/
public static boolean declaresException(final Method method, final Class<?> exceptionType) {
Validate.notNull(method, "Method must not be null");
final Class<?>[] declaredExceptions = method.getExceptionTypes();
for (final Class<?> declaredException : declaredExceptions) {
if (declaredException.isAssignableFrom(exceptionType)) {
return true;
}
}
return false;
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.
*
* @param targetClass the target class to analyze
* @param fc the callback to invoke for each field
*/
public static void doWithFields(final Class<?> targetClass, final FieldCallback fc)
throws IllegalArgumentException {
doWithFields(targetClass, fc, null);
}
/**
* Invoke the given callback on all fields in the target class, going up the
* class hierarchy to get all declared fields.
*
* @param targetClass the target class to analyze
* @param fc the callback to invoke for each field
* @param ff the filter that determines the fields to apply the callback to
*/
public static void doWithFields(Class<?> targetClass, final FieldCallback fc, final FieldFilter ff)
throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
do {
// Copy each field declared on this class unless it's static or
// file.
final Field[] fields = targetClass.getDeclaredFields();
for (final Field field : fields) {
// Skip static and final fields.
if (ff != null && !ff.matches(field)) {
continue;
}
try {
fc.doWith(field);
} catch (final IllegalAccessException ex) {
throw new IllegalStateException("Shouldn't be illegal to access field '"
+ field.getName() + "': " + ex);
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
}
/**
* Perform the given callback operation on all matching methods of the given
* class and superclasses.
* <p>
* The same named method occurring on subclass and superclass will appear
* twice, unless excluded by a {@link MethodFilter}.
*
* @param targetClass class to start looking at
* @param mc the callback to invoke for each method
* @see #doWithMethods(Class, MethodCallback, MethodFilter)
*/
public static void doWithMethods(final Class<?> targetClass, final MethodCallback mc)
throws IllegalArgumentException {
doWithMethods(targetClass, mc, null);
}
/**
* Perform the given callback operation on all matching methods of the given
* class and superclasses.
* <p>
* The same named method occurring on subclass and superclass will appear
* twice, unless excluded by the specified {@link MethodFilter}.
*
* @param targetClass class to start looking at
* @param mc the callback to invoke for each method
* @param mf the filter that determines the methods to apply the callback to
*/
public static void doWithMethods(Class<?> targetClass, final MethodCallback mc,
final MethodFilter mf) throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
do {
final Method[] methods = targetClass.getDeclaredMethods();
for (final Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
} catch (final IllegalAccessException ex) {
throw new IllegalStateException("Shouldn't be illegal to access method '"
+ method.getName() + "': " + ex);
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null);
}
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with
* the supplied <code>name</code>. Searches all superclasses up to
* {@link Object}.
*
* @param clazz the class to introspect
* @param name the name of the field
* @return the corresponding Field object, or <code>null</code> if not found
*/
public static Field findField(final Class<?> clazz, final String name) {
return findField(clazz, name, null);
}
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with
* the supplied <code>name</code> and/or {@link Class type}. Searches all
* superclasses up to {@link Object}.
*
* @param clazz the class to introspect
* @param name the name of the field (may be <code>null</code> if type is
* specified)
* @param type the type of the field (may be <code>null</code> if name is
* specified)
* @return the corresponding Field object, or <code>null</code> if not found
*/
public static Field findField(final Class<?> clazz, final String name, final Class<?> type) {
Validate.notNull(clazz, "Class must not be null");
Validate.isTrue(name != null || type != null,
"Either name or type of the field must be specified");
Class<?> searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
final Field[] fields = searchType.getDeclaredFields();
for (final Field field : fields) {
if ((name == null || name.equals(field.getName()))
&& (type == null || type.equals(field.getType()))) {
return field;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied
* name and no parameters. Searches all superclasses up to
* <code>Object</code>.
* <p>
* Returns <code>null</code> if no {@link Method} can be found.
*
* @param clazz the class to introspect
* @param name the name of the method
* @return the Method object, or <code>null</code> if none found
*/
public static Method findMethod(final Class<?> clazz, final String name) {
return findMethod(clazz, name, new Class[0]);
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied
* name and parameter types. Searches all superclasses up to
* <code>Object</code>.
* <p>
* Returns <code>null</code> if no {@link Method} can be found.
*
* @param clazz the class to introspect
* @param name the name of the method
* @param parameterTypes the parameter types of the method (may be
* <code>null</code> to indicate any signature)
* @return the Method object, or <code>null</code> if none found
*/
public static Method findMethod(final Class<?> clazz, final String name,
final Class<?>[] parameterTypes) {
Validate.notNull(clazz, "Class must not be null");
Validate.notNull(name, "Method name must not be null");
Class<?> searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
final Method[] methods =
searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods();
for (final Method method : methods) {
if (name.equals(method.getName())
&& (parameterTypes == null || Arrays.equals(parameterTypes, method.getParameterTypes()))) {
return method;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Get all declared methods on the leaf class and all superclasses. Leaf
* class methods are included first.
*/
public static Method[] getAllDeclaredMethods(final Class<?> leafClass)
throws IllegalArgumentException {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
public void doWith(final Method method) {
methods.add(method);
}
});
return methods.toArray(new Method[methods.size()]);
}
/**
* Get the field represented by the supplied {@link Field field object} on
* the specified {@link Object target object}. In accordance with
* {@link Field#get(Object)} semantics, the returned value is automatically
* wrapped if the underlying field has a primitive type.
* <p>
* Thrown exceptions are handled via a call to
* {@link #handleReflectionException(Exception)}.
*
* @param field the field to get
* @param target the target object from which to get the field
* @return the field's current value
*/
public static Object getField(final Field field, final Object target) {
try {
return field.get(target);
} catch (final IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException("Unexpected reflection exception - "
+ ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Handle the given invocation target exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>
* Throws the underlying RuntimeException or Error in case of such a root
* cause. Throws an IllegalStateException else.
*
* @param ex the invocation target exception to handle
*/
public static void handleInvocationTargetException(final InvocationTargetException ex) {
rethrowRuntimeException(ex.getTargetException());
}
/**
* Handle the given reflection exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>
* Throws the underlying RuntimeException or Error in case of an
* InvocationTargetException with such a root cause. Throws an
* IllegalStateException with an appropriate message else.
*
* @param ex the reflection exception to handle
*/
public static void handleReflectionException(final Exception ex) {
if (ex instanceof NoSuchMethodException) {
throw new IllegalStateException("Method not found: " + ex.getMessage());
}
if (ex instanceof IllegalAccessException) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
if (ex instanceof InvocationTargetException) {
handleInvocationTargetException((InvocationTargetException) ex);
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
handleUnexpectedException(ex);
}
/**
* Throws an IllegalStateException with the given exception as root cause.
*
* @param ex the unexpected exception
*/
private static void handleUnexpectedException(final Throwable ex) {
throw new IllegalStateException("Unexpected exception thrown", ex);
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied target
* object with no arguments.
*
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(final Method method, final Object target)
throws SQLException {
return invokeJdbcMethod(method, target, null);
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied target
* object with the supplied arguments.
*
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be <code>null</code>)
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(final Method method, final Object target,
final Object[] args) throws SQLException {
try {
return method.invoke(target, args);
} catch (final IllegalAccessException ex) {
handleReflectionException(ex);
} catch (final InvocationTargetException ex) {
if (ex.getTargetException() instanceof SQLException) {
throw (SQLException) ex.getTargetException();
}
handleInvocationTargetException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Invoke the specified {@link Method} against the supplied target object
* with no arguments. The target object can be <code>null</code> when
* invoking a static {@link Method}.
* <p>
* Thrown exceptions are handled via a call to
* {@link #handleReflectionException}.
*
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeMethod(final Method method, final Object target) {
return invokeMethod(method, target, null);
}
/**
* Invoke the specified {@link Method} against the supplied target object
* with the supplied arguments. The target object can be <code>null</code>
* when invoking a static {@link Method}.
* <p>
* Thrown exceptions are handled via a call to
* {@link #handleReflectionException}.
*
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be <code>null</code>)
* @return the invocation result, if any
*/
public static Object invokeMethod(final Method method, final Object target, final Object[] args) {
try {
return method.invoke(target, args);
} catch (final Exception ex) {
handleReflectionException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Determine whether the given method is an "equals" method.
*
* @see java.lang.Object#equals
*/
public static boolean isEqualsMethod(final Method method) {
if (method == null || !method.getName().equals("equals")) {
return false;
}
final Class<?>[] parameterTypes = method.getParameterTypes();
return parameterTypes.length == 1 && parameterTypes[0] == Object.class;
}
/**
* Determine whether the given method is a "hashCode" method.
*
* @see java.lang.Object#hashCode
*/
public static boolean isHashCodeMethod(final Method method) {
return method != null && method.getName().equals("hashCode")
&& method.getParameterTypes().length == 0;
}
/**
* Determine whether the given field is a "public static final" constant.
*
* @param field the field to check
*/
public static boolean isPublicStaticFinal(final Field field) {
final int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& Modifier.isFinal(modifiers);
}
/**
* Determine whether the given method is a "toString" method.
*
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(final Method method) {
return method != null && method.getName().equals("toString")
&& method.getParameterTypes().length == 0;
}
/**
* Make the given constructor accessible, explicitly setting it accessible
* if necessary. The <code>setAccessible(true)</code> method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
*
* @param ctor the constructor to make accessible
* @see java.lang.reflect.Constructor#setAccessible
*/
public static void makeAccessible(final Constructor<?> ctor) {
if (!Modifier.isPublic(ctor.getModifiers())
|| !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) {
ctor.setAccessible(true);
}
}
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The <code>setAccessible(true)</code> method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
*
* @param field the field to make accessible
* @see java.lang.reflect.Field#setAccessible
*/
public static void makeAccessible(final Field field) {
if (!Modifier.isPublic(field.getModifiers())
|| !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
field.setAccessible(true);
}
}
/**
* Make the given method accessible, explicitly setting it accessible if
* necessary. The <code>setAccessible(true)</code> method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
*
* @param method the method to make accessible
* @see java.lang.reflect.Method#setAccessible
*/
public static void makeAccessible(final Method method) {
if (!Modifier.isPublic(method.getModifiers())
|| !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
method.setAccessible(true);
}
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}. Should
* only be called if no checked exception is expected to be thrown by the
* target method.
* <p>
* Rethrows the underlying exception cast to an {@link Exception} or
* {@link Error} if appropriate; otherwise, throws an
* {@link IllegalStateException}.
*
* @param ex the exception to rethrow
* @throws Exception the rethrown exception (in case of a checked exception)
*/
public static void rethrowException(final Throwable ex) throws Exception {
if (ex instanceof Exception) {
throw (Exception) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
handleUnexpectedException(ex);
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}. Should
* only be called if no checked exception is expected to be thrown by the
* target method.
* <p>
* Rethrows the underlying exception cast to an {@link RuntimeException} or
* {@link Error} if appropriate; otherwise, throws an
* {@link IllegalStateException}.
*
* @param ex the exception to rethrow
* @throws RuntimeException the rethrown exception
*/
public static void rethrowRuntimeException(final Throwable ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
handleUnexpectedException(ex);
}
/**
* Set the field represented by the supplied {@link Field field object} on
* the specified {@link Object target object} to the specified
* <code>value</code>. In accordance with {@link Field#set(Object, Object)}
* semantics, the new value is automatically unwrapped if the underlying
* field has a primitive type.
* <p>
* Thrown exceptions are handled via a call to
* {@link #handleReflectionException(Exception)}.
*
* @param field the field to set
* @param target the target object on which to set the field
* @param value the value to set; may be <code>null</code>
*/
public static void setField(final Field field, final Object target, final Object value) {
try {
field.set(target, value);
} catch (final IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException("Unexpected reflection exception - "
+ ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Given the source object and the destination, which must be the same class
* or a subclass, copy all fields, including inherited fields. Designed to
* work on objects with public no-arg constructors.
*
* @throws IllegalArgumentException if the arguments are incompatible
*/
public static void shallowCopyFieldState(final Object src, final Object dest)
throws IllegalArgumentException {
if (src == null) {
throw new IllegalArgumentException("Source for field copy cannot be null");
}
if (dest == null) {
throw new IllegalArgumentException("Destination for field copy cannot be null");
}
if (!src.getClass().isAssignableFrom(dest.getClass())) {
throw new IllegalArgumentException("Destination class [" + dest.getClass().getName()
+ "] must be same or subclass as source class [" + src.getClass().getName() + "]");
}
doWithFields(src.getClass(), new FieldCallback() {
public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
makeAccessible(field);
final Object srcValue = field.get(src);
field.set(dest, srcValue);
}
}, COPYABLE_FIELDS);
}
}