/**
*
*/
package ch.panter.edu.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* This class let's you access private fields
* and field w/o a setter trough reflection.
* <p>
* Code copied from Spring Framwork 2.5.
* </p>
*
* @author bseelige
*
*/
public class ReflectionTestUtil {
/**
* <p>
* Sets the {@link Field field} with the given <code>name</code> on the
* provided {@link Object target object} to the supplied <code>value</code>.
* </p>
* <p>
* This method traverses the class hierarchy in search of the desired field.
* In addition, an attempt will be made to make non-<code>public</code>
* fields <em>accessible</em>, thus allowing one to set
* <code>protected</code>, <code>private</code>, and
* <em>package-private</em> fields.
* </p>
*
* @param target The target object on which to set the field.
* @param name The name of the field to set.
* @param value The value to set; may be <code>null</code> unless the
* field type is a primitive type.
* @param type The type of the field.
* @throws IllegalArgumentException if the target object or field type is
* <code>null</code>; if the field name is <em>empty</em>; or
* if the field could not be found on the target object.
* @throws Exception Allows all other exceptions to propagate.
* @see ReflectionUtils#findField(Class, String, Class)
* @see ReflectionUtils#makeAccessible(Field)
* @see ReflectionUtils#setField(Field, Object, Object)
*/
public void setField(final Object target, final String name, final Object value, final Class type)
throws Exception {
final Field field = findField(target.getClass(), name, type);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + name + "] on target [" + target
+ "] with type [" + type + "].");
}
makeAccessible(field);
setField(field, target, value);
}
/**
* 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
*/
private static void makeAccessible(Field field) {
if (!Modifier.isPublic(field.getModifiers()) ||
!Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
field.setAccessible(true);
}
}
/**
* 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>
*/
private void setField(Field field, Object target, Object value) {
try {
field.set(target, value);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException("Unexpected reflection exception - " + ex.getClass().getName() + ": " +
ex.getMessage());
}
}
/**
* 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
*/
private void handleReflectionException(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());
}
throw new RuntimeException(ex);
}
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with
* the supplied <code>name</code> and {@link Class type}. Searches all
* superclasses up to {@link Object}.
* @param clazz the class to introspect
* @param name the name of the field
* @param type the type of the field
* @return the corresponding Field object, or <code>null</code> if not found
* @throws IllegalArgumentException if the class or field type is
* <code>null</code> or if the field name is <em>empty</em>
*/
private Field findField(final Class clazz, final String name, final Class type) {
Class searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
final Field[] fields = searchType.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (name.equals(field.getName()) && type.equals(field.getType())) {
return field;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
}