package eu.stratosphere.util.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* Convenience methods for reflective programming.
*
* @author Arvid Heise
*/
public class ReflectUtil {
private static Map<Class<?>, DynamicClass<?>> CACHED_CLASSES = new HashMap<Class<?>, DynamicClass<?>>();
@SuppressWarnings("serial")
private final static Map<Class<?>, Class<?>> BoxingClasses = new IdentityHashMap<Class<?>, Class<?>>() {
{
this.put(java.lang.Boolean.TYPE, java.lang.Boolean.class);
this.put(java.lang.Character.TYPE, java.lang.Character.class);
this.put(java.lang.Byte.TYPE, java.lang.Byte.class);
this.put(java.lang.Short.TYPE, java.lang.Short.class);
this.put(java.lang.Integer.TYPE, java.lang.Integer.class);
this.put(java.lang.Long.TYPE, java.lang.Long.class);
this.put(java.lang.Float.TYPE, java.lang.Float.class);
this.put(java.lang.Double.TYPE, java.lang.Double.class);
this.put(java.lang.Void.TYPE, java.lang.Void.class);
}
};
/**
* Returns the first annotation of the specified annotation type for the given type.<br>
* If no annotation is found for the type, the hierarchical ancestors are examined.
*
* @param type
* the type which might be annotated
* @param annotationType
* the annotation type
* @return the annotation or null
*/
public static <A extends Annotation> A getAnnotation(final Class<?> type, final Class<A> annotationType) {
A annotation = null;
for (Class<?> t = type; annotation == null && t != null; t = t.getSuperclass())
annotation = t.getAnnotation(annotationType);
return annotation;
}
/**
* Returns the boxing class for the given primitive type. A primitive type returns true for
* {@link Class#isPrimitive()}.
*
* @param type
* the type
* @return the boxing class or type if the given class is not a primitive
*/
public static Class<?> getClassForPrimtive(final Class<?> type) {
return type.isPrimitive() ? BoxingClasses.get(type) : type;
}
/**
* Returns the hierarchical distance between a class and a derived class. <br>
* For instance,
* <ul>
* <li><code>getDistance(Integer.class, Integer.class) == 0</code>
* <li><code>getDistance(Number.class, Integer.class) == 1</code>
* <li><code>getDistance(Object.class, Integer.class) == 2</code>
* <li><code>getDistance(Comparable.class, Integer.class) == 1</code>
* <li><code>getDistance(Serializable.class, Integer.class) == 2</code>.
* </ul>
*
* @param superClass
* the super class in the hierarchy
* @param subclass
* the sub class of the hierarchy
* @return the minimum distance or -1 if <code>subclass</code> is not a subclass of <code>superclass</code>
*/
public static int getDistance(final Class<?> superClass, final Class<?> subclass) {
if (subclass == null)
return 0;
if (isSameTypeOrPrimitive(superClass, subclass))
return 0;
if (!superClass.isAssignableFrom(subclass))
return -1;
if (superClass.isInterface()) {
final Class<?>[] interfaces = subclass.getInterfaces();
int minDistance = Integer.MAX_VALUE;
for (final Class<?> xface : interfaces)
if (xface == superClass) {
minDistance = 1;
break;
} else if (superClass.isAssignableFrom(xface))
minDistance = Math.min(minDistance, getDistance(superClass, xface) + 1);
if (minDistance != Integer.MAX_VALUE)
return minDistance;
return getDistance(superClass, subclass.getSuperclass()) + 1;
}
int distance = 1;
for (Class<?> klazz = subclass; superClass != klazz; distance++)
klazz = klazz.getSuperclass();
return distance;
}
/**
* Checks dynamically whether the object has the specified function, which takes the given parameters.
*
* @param object
* the object to invoke on
* @param function
* the function to call
* @param params
* the parameters of the function
* @return true if such a method exists
*/
public static boolean hasFunction(final Object object, final String function, final Object... params) {
return getDynamicClass(object.getClass()).getMethod(function).isInvokableFor(params);
}
/**
* Dynamically invokes the specified function on an object with the given parameters.
*
* @param object
* the object to invoke on
* @param function
* the function to call
* @param params
* the parameters of the function
* @return the result of the invocation
*/
@SuppressWarnings("unchecked")
public static Object invoke(final Object object, final String function, final Object... params) {
return getDynamicClass((Class<Object>) object.getClass()).getMethod(function).invoke(object, params);
}
/**
* Returns true if the given type has an accessible default constructor.<br>
* Note: this method is thread-safe
*
* @param type
* the type to check
* @return true if it has an accessible default constructor.
*/
public static boolean isInstantiable(final Class<?> type) {
return getDynamicClass(type).isInstantiable();
}
/**
* Returns true if both types are the same or represent the same primitive or boxing type.
*
* @param type1
* the first type
* @param type2
* the second type
* @return the boxing class or null if the given class is not a primitive
*/
public static boolean isSameTypeOrPrimitive(final Class<?> type1, final Class<?> type2) {
return getClassForPrimtive(type1) == getClassForPrimtive(type2);
}
/**
* Creates a new instance of the given type by invoking the default constructor. If the default constructor is not
* public, the method will try to
* gain access through {@link Constructor#setAccessible(boolean)}. <br>
* <br>
* Note: this method is thread-safe
*
* @param <T>
* the type to instantiate
* @param type
* the type to instantiate
* @return the created instance
* @throws IllegalArgumentException
* if the type has no accessible default constructor or an exception occurred during the invocation:
* possible causes are {@link NoSuchMethodException}, {@link InstantiationException} ,
* {@link IllegalAccessException}, * {@link InvocationTargetException}
*/
public static <T> T newInstance(final Class<T> type) throws IllegalArgumentException {
return getDynamicClass(type).newInstance();
}
/**
* Creates a new instance of the given type by invoking the best public constructor for the given parameter.<br>
* If there are multiple compatible constructors, the most specific one is chosen. <br>
* If there are several constructors with the same degree of specify, an Exception is thrown. <br>
* Note: this method is thread-safe
*
* @param <T>
* the type to instantiate
* @param type
* the type to instantiate
* @param params
* The constructors parameters.
* @return the created instance
* @throws IllegalArgumentException
* if the type has 0 or more than 2 matching constructors or an exception occurred during the invocation:
* possible causes are {@link NoSuchMethodException}, {@link InstantiationException} ,
* {@link IllegalAccessException}, {@link InvocationTargetException}
*/
public static <T> T newInstance(final Class<T> type, final Object... params) throws IllegalArgumentException {
return getDynamicClass(type).newInstance(params);
}
public static synchronized <T> DynamicClass<T> getDynamicClass(final Class<T> clazz) {
@SuppressWarnings("unchecked")
DynamicClass<T> dynamicClass = (DynamicClass<T>) CACHED_CLASSES.get(clazz);
if (dynamicClass == null)
CACHED_CLASSES.put(clazz, dynamicClass = new DynamicClass<T>(clazz));
return dynamicClass;
}
public static synchronized List<Method> getMethods(final Class<?> clazz, final String name,
final int... modifierBitsets) {
final ArrayList<Method> methods = new ArrayList<Method>();
int modifiers = 0;
for (final int modifier : modifierBitsets)
modifiers |= modifier;
for (final Method method : clazz.getDeclaredMethods())
if (name == null || method.getName().equals(name)) {
if ((method.getModifiers() & modifiers) != modifiers)
continue;
methods.add(method);
}
return methods;
}
public static synchronized List<Field> getFields(final Class<?> clazz, final String name,
final int... modifierBitsets) {
final ArrayList<Field> fields = new ArrayList<Field>();
for (final Field field : clazz.getDeclaredFields())
if (name == null || field.getName().equals(name)) {
for (final int modifiers : modifierBitsets)
if ((field.getModifiers() & modifiers) != modifiers)
continue;
fields.add(field);
}
return fields;
}
}