com.googlecode.gwt.test.utils.GwtReflectionUtils Maven / Gradle / Ivy
package com.googlecode.gwt.test.utils;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.web.bindery.event.shared.UmbrellaException;
import com.googlecode.gwt.test.exceptions.GwtTestException;
import com.googlecode.gwt.test.exceptions.ReflectionException;
import com.googlecode.gwt.test.internal.utils.DoubleMap;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
/**
* Class which provides reflection utilities.
*
* @author Bertrand Paquet
* @author Gael Lazzari
*/
@SuppressWarnings("unchecked")
public class GwtReflectionUtils {
/**
* Callback interface invoked on each field in the hierarchy.
*/
public 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 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 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 interface MethodFilter {
/**
* Determine whether the given method matches.
*
* @param method the method to check
*/
boolean matches(Method method);
}
private static final String ASSERTIONS_FIELD_NAME = "$assertionsDisabled";
private static DoubleMap, Class, Map> cacheAnnotatedField = new DoubleMap, Class, Map>();
private static DoubleMap, Class, Map> cacheAnnotatedMethod = new DoubleMap, Class, Map>();
private static DoubleMap, Class, Object> cacheAnnotation = new DoubleMap, Class, Object>();
private static Map, Set> cacheField = new HashMap, Set>();
private static DoubleMap, String, Method> cacheMethod = new DoubleMap, String, Method>();
private static DoubleMap, String, Field> cacheUniqueField = new DoubleMap, String, Field>();
public static T callPrivateMethod(JavaScriptObject target, String methodName,
String overlayOriginalType, Object... args) {
if (overlayOriginalType == null) {
throw new IllegalArgumentException("The specified overlay type should not be null");
}
overlayOriginalType = overlayOriginalType.trim();
if (overlayOriginalType.equals("")) {
throw new IllegalArgumentException("The specified overlay type should not be blank");
}
if (overlayOriginalType.endsWith("$")) {
throw new IllegalArgumentException(
"The specified overlay type should not be a rewritten type : "
+ overlayOriginalType);
}
Class rewrittenOverlayType = null;
try {
rewrittenOverlayType = Class.forName(overlayOriginalType + "$");
} catch (ClassNotFoundException e) {
throw new ReflectionException("Error while calling overlay rewritten method "
+ overlayOriginalType + "$." + methodName + "$(..) :", e);
}
Object[] newArgs = new Object[args.length + 1];
newArgs[0] = target;
System.arraycopy(args, 0, newArgs, 1, args.length);
return callStaticMethod(rewrittenOverlayType, methodName + "$", newArgs);
}
public static T callPrivateMethod(Object target, Method method, Object... args) {
try {
method.setAccessible(true);
Object res = method.invoke(target, args);
return (T) res;
} catch (InvocationTargetException e) {
if (GwtTestException.class.isInstance(e.getCause())) {
throw (GwtTestException) e.getCause();
} else if (AssertionError.class.isInstance(e.getCause())) {
throw (AssertionError) e.getCause();
} else if (UmbrellaException.class.isInstance(e.getCause())) {
throw new ReflectionException("Error while calling method '" + method.toString() + "'",
e.getCause().getCause());
}
throw new ReflectionException("Error while calling method '" + method.toString() + "'",
e.getCause());
} catch (Exception e) {
throw new ReflectionException("Unable to call method '"
+ target.getClass().getSimpleName() + "." + method.getName() + "(..)'", e);
}
}
public static T callPrivateMethod(Object target, String methodName, Object... args) {
if (target instanceof JavaScriptObject) {
throw new UnsupportedOperationException(
"Cannot call instance method on Overlay types without specifying its base type");
}
Method method = findMethod(target.getClass(), methodName, args);
if (method == null) {
throw new ReflectionException("Cannot find method '" + target.getClass().getName() + "."
+ methodName + "(..)'");
}
return (T) callPrivateMethod(target, method, args);
}
public static T callStaticMethod(Class clazz, String methodName, Object... args) {
Method m = findMethod(clazz, methodName, args);
if (m == null) {
throw new ReflectionException("Cannot find method '" + clazz.getName() + "." + methodName
+ "(..)'");
}
try {
m.setAccessible(true);
Object res = m.invoke(null, args);
return (T) res;
} catch (Exception e) {
throw new ReflectionException("Unable to call static method '" + m.toString() + "'", e);
}
}
/**
* Invoke the given callback on all fields in the target class, going up the class hierarchy to
* get all declared fields.
*
* @param clazz the target class to analyze
* @param fc the callback to invoke for each field
*/
public static void doWithFields(Class clazz, FieldCallback fc)
throws IllegalArgumentException {
doWithFields(clazz, fc, null);
}
/**
* Invoke the given callback on all fields in the target class, going up the class hierarchy to
* get all declared fields.
*
* @param clazz 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 clazz, FieldCallback fc, FieldFilter ff)
throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
Class targetClass = clazz;
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
// Skip static and final fields.
if (ff != null && !ff.matches(field)) {
continue;
}
try {
fc.doWith(field);
} catch (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.
*
* The same named method occurring on subclass and superclass will appear twice, unless excluded
* by a {@link MethodFilter}.
*
* @param clazz class to start looking at
* @param mc the callback to invoke for each method
* @see #doWithMethods(Class, MethodCallback, MethodFilter)
*/
public static void doWithMethods(Class clazz, MethodCallback mc)
throws IllegalArgumentException {
doWithMethods(clazz, mc, null);
}
/**
* Perform the given callback operation on all matching methods of the given class and
* superclasses (or given interface and super-interfaces).
*
* The same named method occurring on subclass and superclass will appear twice, unless excluded
* by the specified {@link MethodFilter}.
*
* @param clazz 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 clazz, MethodCallback mc, MethodFilter mf)
throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Shouldn't be illegal to access method '"
+ method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
} else if (clazz.isInterface()) {
for (Class superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
public static Method findMethod(Class clazz, String name, Class... paramTypes) {
Class searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
Method[] methods = searchType.isInterface() ? searchType.getMethods()
: searchType.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (name.equals(method.getName())
&& paramTypes.length == method.getParameterTypes().length) {
boolean compatibleParams = true;
for (int j = 0; j < paramTypes.length; j++) {
if (paramTypes[j] == null) {
// null class is a wildcard
continue;
}
Class methodParamType = getCheckedClass(method.getParameterTypes()[j]);
Class searchParamType = getCheckedClass(paramTypes[j]);
if (!methodParamType.isAssignableFrom(searchParamType)) {
compatibleParams = false;
}
}
if (compatibleParams) {
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(Class leafClass) throws IllegalArgumentException {
final List methods = new ArrayList(32);
doWithMethods(leafClass, new MethodCallback() {
public void doWith(Method method) {
methods.add(method);
}
});
return methods.toArray(new Method[methods.size()]);
}
public static Map getAnnotatedField(Class clazz,
Class annotationClass) {
Map l = (Map) cacheAnnotatedField.get(clazz, annotationClass);
if (l != null) {
return l;
}
l = new HashMap();
recurseGetAnnotatedField(l, clazz, annotationClass);
cacheAnnotatedField.put(clazz, annotationClass, l);
return l;
}
public static Map getAnnotatedMethod(Class target,
Class annotationClass) {
Map map = (Map) cacheAnnotatedMethod.get(target, annotationClass);
if (map != null) {
return map;
}
map = new HashMap();
recurseGetAnnotatedMethod(map, target, annotationClass);
cacheAnnotatedMethod.put(target, annotationClass, map);
return map;
}
public static T getAnnotation(Class clazz, Class annotationClass) {
Object o = cacheAnnotation.get(clazz, annotationClass);
if (o != null) {
return (T) o;
}
T result = null;
for (Annotation a : clazz.getDeclaredAnnotations()) {
if (a.annotationType() == annotationClass) {
result = (T) a;
}
}
if (result == null && clazz.getSuperclass() != null) {
result = getAnnotation(clazz.getSuperclass(), annotationClass);
}
cacheAnnotation.put(clazz, annotationClass, result);
return result;
}
/**
* Get the class corresponding to the String passed as param. This method is better than
* {@link Class#forName(String)} because it can handle inner class when necessary.
*
* @param type The class name
* @return The corresponding class object
* @throws ClassNotFoundException If the class does not exist
*/
public static Class getClass(String type) throws ClassNotFoundException {
try {
return Class.forName(type);
} catch (ClassNotFoundException e1) {
// it can be an inner class
int lastIndex = type.lastIndexOf('.');
String innerTypeName = type.substring(0, lastIndex) + "$" + type.substring(lastIndex + 1);
try {
return Class.forName(innerTypeName);
} catch (ClassNotFoundException e2) {
throw e1;
}
}
}
/**
* Get all fields from a class and its superclasses, being carefull not to add multiple
* '$assertionsDisabled' fields which are added when a class contains java assertions.
*
* @param clazz The class to introspect.
* @return The set of field of the class
*/
public static Set getFields(Class clazz) {
Set set = cacheField.get(clazz);
if (set != null) {
return set;
}
// get all field, being carefull not to add multiple $assertionsDisabled
// fields
set = getFields(clazz, false);
cacheField.put(clazz, set);
return set;
}
public static Method getMethod(Class clazz, String methodName) {
Method res = cacheMethod.get(clazz, methodName);
if (res != null) {
return res;
}
for (Method m : clazz.getDeclaredMethods()) {
if (methodName.equalsIgnoreCase(m.getName())) {
m.setAccessible(true);
cacheMethod.put(clazz, methodName, m);
return m;
}
}
for (Method m : clazz.getMethods()) {
if (methodName.equalsIgnoreCase(m.getName())) {
m.setAccessible(true);
cacheMethod.put(clazz, methodName, m);
return m;
}
}
Class superClazz = clazz.getSuperclass();
if (superClazz != null) {
res = getMethod(superClazz, methodName);
}
cacheMethod.put(clazz, methodName, res);
return res;
}
public static T getPrivateFieldValue(Object target, Field field) {
try {
makeAccessible(field);
return (T) field.get(target);
} catch (Exception e) {
throw new ReflectionException("Unable to get field '" + target.getClass().getSimpleName()
+ "." + field.getName() + "'", e);
}
}
public static T getPrivateFieldValue(Object target, String fieldName) {
Field field = getUniqueFieldByName(target.getClass(), fieldName);
return (T) getPrivateFieldValue(target, field);
}
public static T getStaticFieldValue(Class clazz, String fieldName) {
Field field = getUniqueFieldByName(clazz, fieldName);
try {
return (T) field.get(null);
} catch (Exception e) {
e.printStackTrace();
throw new ReflectionException(e.getMessage() + " Unable to get static field, class "
+ fieldName + ", fieldClass " + clazz);
}
}
/**
* Convenience method to instantiate a class using its no-arg constructor. As this method doesn't
* try to load classes by name, it should avoid class-loading issues.
*
* Note that this method tries to set the constructor accessible if given a non-accessible (that
* is, non-public) constructor.
*
* @param the type of object to instanciate
* @param clazz class to instantiate
* @return the new instance
*/
public static T instantiateClass(Class clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Class must not be null");
}
if (clazz.isInterface()) {
throw new ReflectionException("Error during instanciation of '" + clazz.getName()
+ "'. Specified class is an interface");
}
try {
return instantiateClass(clazz.getDeclaredConstructor());
} catch (NoSuchMethodException ex) {
throw new ReflectionException("Error during instanciation of '" + clazz.getName()
+ "'. No default constructor found", ex);
}
}
/**
* Convenience method to instantiate a class using the given constructor. As this method doesn't
* try to load classes by name, it should avoid class-loading issues.
*
* Note that this method tries to set the constructor accessible if given a non-accessible (that
* is, non-public) constructor.
*
* @param The object type
* @param ctor the constructor to instantiate
* @param args the constructor arguments to apply
* @return the new instance
*/
public static T instantiateClass(Constructor ctor, Object... args) {
if (ctor == null) {
throw new IllegalArgumentException("Constructor must not be null");
}
try {
makeAccessible(ctor);
return ctor.newInstance(args);
} catch (InstantiationException ex) {
throw new ReflectionException("Error during instanciation of '"
+ ctor.getDeclaringClass().getName() + "'. Is it an abstract class?", ex);
} catch (IllegalAccessException ex) {
throw new ReflectionException("Error during instanciation of '"
+ ctor.getDeclaringClass().getName()
+ "'. Has the class definition changed? Is the constructor accessible?", ex);
} catch (IllegalArgumentException ex) {
throw new ReflectionException("Error during instanciation of '"
+ ctor.getDeclaringClass().getName() + "'. Illegal arguments for constructor", ex);
} catch (InvocationTargetException ex) {
if (GwtTestException.class.isInstance(ex.getTargetException())) {
throw (GwtTestException) ex.getTargetException();
} else if (GwtTestException.class.isInstance(ex.getTargetException().getCause())) {
throw (GwtTestException) ex.getTargetException().getCause();
} else {
throw new ReflectionException("Error during instanciation of '"
+ ctor.getDeclaringClass().getName() + "'. Constructor threw exception",
ex.getTargetException());
}
}
}
/**
* Make the given constructor accessible, explicitly setting it accessible if necessary. The
* setAccessible(true)
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(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
* setAccessible(true)
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(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
* setAccessible(true)
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(Method method) {
if (!Modifier.isPublic(method.getModifiers())
|| !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
method.setAccessible(true);
}
}
public static void setPrivateFieldValue(Object target, String fieldName, Object value) {
Field field = getUniqueFieldByName(target.getClass(), fieldName);
try {
field.set(target, value);
} catch (Exception e) {
throw new ReflectionException(e);
}
}
public static void setStaticField(Class clazz, String fieldName, Object value) {
Field field = getUniqueFieldByName(clazz, fieldName);
try {
field.set(null, value);
} catch (Exception e) {
e.printStackTrace();
throw new ReflectionException(e);
}
}
private static Method findMethod(Class clazz, String methodName, Object... args) {
Class[] l = new Class[args.length];
for (int i = 0; i < args.length; i++) {
l[i] = args[i] != null ? args[i].getClass() : null;
}
return findMethod(clazz, methodName, l);
}
private static Class getCheckedClass(Class potentialPrimitiveType) {
if (!potentialPrimitiveType.isPrimitive()) {
return potentialPrimitiveType;
}
if (potentialPrimitiveType == Byte.TYPE) {
return Byte.class;
} else if (potentialPrimitiveType == Short.TYPE) {
return Short.class;
} else if (potentialPrimitiveType == Integer.TYPE) {
return Integer.class;
} else if (potentialPrimitiveType == Long.TYPE) {
return Long.class;
} else if (potentialPrimitiveType == Float.TYPE) {
return Float.class;
} else if (potentialPrimitiveType == Double.TYPE) {
return Double.class;
} else if (potentialPrimitiveType == Boolean.TYPE) {
return Boolean.class;
} else {
return Character.class;
}
}
/**
* Get all fields from a class, being carefull not to add multiple '$assertionsDisabled' fields
* which are added when a class contains java assertions.
*
* @param clazz The class to introspect.
* @param hasAssertionField A token to remember if an '$assertionDisabled' field had already been
* added to the class.
* @return The set of field of the class
*/
private static Set getFields(Class clazz, boolean hasAssertionField) {
Set set = new HashSet();
;
for (Field f : clazz.getDeclaredFields()) {
if (ASSERTIONS_FIELD_NAME.equals(f.getName())) {
// do not add this assertion specific field if it has already been
// added
// by a child class
if (!hasAssertionField) {
f.setAccessible(true);
set.add(f);
hasAssertionField = true;
}
} else {
f.setAccessible(true);
set.add(f);
}
}
Class superClazz = clazz.getSuperclass();
if (superClazz != null) {
set.addAll(getFields(superClazz, hasAssertionField));
}
return set;
}
private static Field getUniqueFieldByName(Class clazz, String fieldName) {
Field f = cacheUniqueField.get(clazz, fieldName);
if (f != null) {
return f;
}
Set fieldSet = getFields(clazz);
Set result = new HashSet();
for (Field field : fieldSet) {
if (field.getName().equals(fieldName)) {
result.add(field);
}
}
if (result.size() == 0) {
throw new ReflectionException("Unable to find field, class '" + clazz + "', fieldName '"
+ fieldName + "'");
}
if (result.size() > 1) {
throw new ReflectionException("Unable to choose field, '" + clazz + "', fieldName '"
+ fieldName + "'");
}
Field field = result.iterator().next();
cacheUniqueField.put(clazz, fieldName, field);
return field;
}
private static void recurseGetAnnotatedField(Map map,
Class target, Class annotationClass) {
for (Field f : target.getDeclaredFields()) {
for (Annotation a : f.getDeclaredAnnotations()) {
if (a.annotationType() == annotationClass) {
map.put(f, (T) a);
}
}
}
if (target.getSuperclass() != null) {
recurseGetAnnotatedField(map, target.getSuperclass(), annotationClass);
}
}
private static void recurseGetAnnotatedMethod(Map map,
Class target, Class annotationClass) {
for (Method m : target.getDeclaredMethods()) {
for (Annotation a : m.getDeclaredAnnotations()) {
if (a.annotationType() == annotationClass) {
map.put(m, (T) a);
}
}
}
if (target.getSuperclass() != null) {
recurseGetAnnotatedMethod(map, target.getSuperclass(), annotationClass);
}
}
private GwtReflectionUtils() {
}
}