package eu.stratosphere.util.reflect; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.common.primitives.Primitives; /** * Convenience methods for reflective programming. */ public class ReflectUtil { private static Map<Class<?>, DynamicClass<?>> CACHED_CLASSES = new HashMap<Class<?>, DynamicClass<?>>(); /** * 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 first annotation of the specified annotation type for the given field.<br> * If no annotation is found for the type, the hierarchical ancestors are examined. * * @param field * the field which might be annotated * @param annotationType * the annotation type * @return the annotation or null */ public static <A extends Annotation> A getAnnotation(final Field field, final Class<A> annotationType) { A annotation = null; annotation = field.getAnnotation(annotationType); return annotation; } /** * 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; } 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<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; } public static List<Type> getHierarchy(final Class<?> superClass, final Class<?> subclass) { if (!superClass.isAssignableFrom(subclass)) throw new IllegalArgumentException(); List<Type> hierarchy = new ArrayList<Type>(); if (superClass == subclass) return hierarchy; if (superClass.isInterface()) { final Type[] interfaces = subclass.getGenericInterfaces(); int minDistance = Integer.MAX_VALUE; for (final Type xface : interfaces) { final Class<?> type = (Class<?>) (xface instanceof Class ? xface : ((ParameterizedType) xface) .getRawType()); if (type == superClass) { hierarchy.clear(); hierarchy.add(xface); break; } if (superClass.isAssignableFrom(type)) { final List<Type> partialHierarchy = getHierarchy(superClass, type); if (partialHierarchy.size() + 1 < minDistance) { hierarchy = partialHierarchy; hierarchy.add(0, xface); minDistance = hierarchy.size(); } } } if (hierarchy.isEmpty()) { hierarchy.add(subclass.getGenericSuperclass()); hierarchy.addAll(getHierarchy(superClass, subclass.getSuperclass())); } return hierarchy; } Type clazz = subclass; Class<?> rawType = subclass; do { hierarchy.add(clazz); rawType = (Class<?>) (clazz instanceof Class ? clazz : ((ParameterizedType) clazz).getRawType()); clazz = rawType.getGenericSuperclass(); } while (superClass != rawType); return hierarchy; } 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; } /** * 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 * @throws Throwable */ @SuppressWarnings("unchecked") public static Object invoke(final Object object, final String function, final Object... params) throws Throwable { try { return getDynamicClass((Class<Object>) object.getClass()).getMethod(function).invoke(object, params); } catch (final Exception e) { throw new IllegalArgumentException(e); } } /** * 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 Primitives.wrap(type1) == Primitives.wrap(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 */ public static <T> T newInstance(final Class<T> type) throws IllegalArgumentException { try { return getDynamicClass(type).newInstance(); } catch (final Throwable e) { throw new IllegalArgumentException(e); } } /** * 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 { try { return getDynamicClass(type).newInstance(params); } catch (final Throwable e) { throw new IllegalArgumentException(e); } } public static void setField(final Object object, final Class<?> declaringClass, final String fieldName, final Object value) { try { final Field field = declaringClass.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } catch (final Exception e) { throw new IllegalArgumentException( String.format("Cannot set field %s of %s to %s", fieldName, object, value), e); } } public static void setField(final Object object, final String fieldName, final Object value) { setField(object, object.getClass(), fieldName, value); } }