// Copyright 2011 Palantir Technologies // // 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 com.palantir.ptoss.util; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import org.apache.commons.lang.mutable.MutableBoolean; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.palantir.ptoss.cinch.core.ObjectFieldMethod; /** * A collection of utility methods and classes to handle all the of the Java Reflection calls * need to wire and fire bindings. * * @see <a href='http://docs.oracle.com/javase/6/docs/api/index.html?java/lang/reflect/package-summary.html'>java.lang.reflect</a> */ public class Reflections { /** * {@link Function} that maps a {@link Field} to the simple name for the containing class. * @see Class#getSimpleName() */ public static final Function<Field, String> FIELD_TO_CONTAINING_CLASS_NAME = new Function<Field, String>() { public String apply(Field input) { return input.getDeclaringClass().getSimpleName(); } }; /** * {@link Function} that maps a {@link Field} to its string name. * @see Field#getName() */ public static final Function<Field, String> FIELD_TO_NAME = new Function<Field, String>() { public String apply(Field from) { return from.getName(); } }; /** * {@link Predicate} to determine whether or not the specified field is final. * @see Modifier#isFinal(int) */ public static final Predicate<Field> IS_FIELD_FINAL = new Predicate<Field>() { public boolean apply(Field from) { return isFieldFinal(from); } }; /** * Starting at the bottom of a class hierarchy, visit all classes (ancestors) in the hierarchy. Does * not visit interfaces. * @param klass Class to use as the bottom of the class hierarchy * @param visitor Visitor object */ public static void visitClassHierarchy(Class<?> klass, Visitor<Class<?>> visitor) { while (klass != null) { visitor.visit(klass); klass = klass.getSuperclass(); } } /** * Given an {@link Object} and a {@link Field} of a known {@link Class} type, get the field. * This will return the value of the field regardless of visibility modifiers (i.e., it will * return the value of private fields.) */ public static <T> T getFieldObject(Object object, Field field, Class<T> klass) { try { boolean accessible = field.isAccessible(); field.setAccessible(true); Object fieldObject = field.get(object); field.setAccessible(accessible); return klass.cast(fieldObject); } catch (IllegalAccessException e) { // shouldn't happen since we set accessibility above. return null; } } /** * Returns whether or not the given {@link Field} is final. */ public static boolean isFieldFinal(Field field) { int modifiers = field.getModifiers(); return Modifier.isFinal(modifiers); } /** * Returns whether or not the given {@link Field} is static. */ public static boolean isFieldStatic(Field field) { int modifiers = field.getModifiers(); return Modifier.isStatic(modifiers); } /** * Returns whether or not the given {@link Method} is public. */ public static boolean isMethodPublic(Method method) { int modifiers = method.getModifiers(); return Modifier.isPublic(modifiers); } /** * Find a {@link Field} based on the field name. Will return private fields but will not * look in superclasses. * * @return null if there is no field found */ // TODO (dcervelli) fix for superclasses public static Field getFieldByName(Class<?> klass, String fieldName) { for (Field f : klass.getDeclaredFields()) { if (f.getName().equals(fieldName)) { return f; } } return null; } /** * Gets all inner classes from a given class that are assignable from the target class. * @param klass type to query for inner-classes. * @param targetClass interface or class that inner classes must be assignable from to be * returned. * @return all inner classes in <code>klass</code> that are assignable from * <code>targetClass</code> * @see Class#isAssignableFrom(Class) * @see Class#getDeclaredClasses() */ public static List<Class<?>> getTypesOfType(Class<?> klass, Class<?> targetClass) { List<Class<?>> classes = Lists.newArrayList(); for (Class<?> cl : klass.getDeclaredClasses()) { if (targetClass.isAssignableFrom(cl)) { classes.add(cl); } } return classes; } /** * Gets all inner classes assignable from <code>targetClass</code> in the passed class's type * hierarchy. * * @param klass starting point in the type stack to query for inner classes. * @param targetClass looks for inner classes that are assignable from this type. * @return all inner classes in <code>klass</code>'s type hierarchy assignable from * <code>targetclass</code> * @see Class#isAssignableFrom(Class) * @see Class#getDeclaredClasses() * @see #getTypesOfType(Class, Class) */ public static List<Class<?>> getTypesOfTypeForClassHierarchy(Class<?> klass, final Class<?> targetClass) { final List<Class<?>> classes = Lists.newArrayList(); visitClassHierarchy(klass, new Visitor<Class<?>>() { public void visit(Class<?> c) { classes.addAll(getTypesOfType(c, targetClass)); } }); return classes; } /** * Gets all fields from a given class that are assignable from the target class. * @param klass type to query for fields. * @param targetClass interface or class that fields must be assignable from to be * returned. * @return all fields in <code>klass</code> that are assignable from * <code>targetClass</code> * @see Class#isAssignableFrom(Class) * @see Class#getDeclaredFields() */ public static List<Field> getFieldsOfType(Class<?> klass, Class<?> targetClass) { List<Field> fields = Lists.newArrayList(); for (Field f : klass.getDeclaredFields()) { if (targetClass.isAssignableFrom(f.getType())) { fields.add(f); } } return fields; } /** * Gets all fields assignable from <code>targetClass</code> in the passed class's type * hierarchy. * * @param klass starting point in the type stack to query for fields of the specified type. * @param targetClass looks for fields that are assignable from this type. * @return all fields declared by classes in <code>klass</code>'s type hierarchy assignable from * <code>targetclass</code> * @see Class#isAssignableFrom(Class) * @see Class#getDeclaredClasses() * @see #getTypesOfType(Class, Class) */ public static List<Field> getFieldsOfTypeForClassHierarchy(Class<?> klass, final Class<?> targetClass) { final List<Field> fields = Lists.newArrayList(); visitClassHierarchy(klass, new Visitor<Class<?>>() { public void visit(Class<?> c) { fields.addAll(getFieldsOfType(c, targetClass)); } }); return fields; } /** * Looks up an {@link Enum} value by its {@link String} name. * @param enumType {@link Enum} class to query. * @param value {@link String} name for the {@link Enum} value. * @return the actual {@link Enum} value specified by the passed name. * @see Enum#valueOf(Class, String) */ public static Object evalEnum(Class<?> enumType, String value) { try { Method method = enumType.getMethod("valueOf", String.class); method.setAccessible(true); return method.invoke(null, value); } catch (Exception ew) { throw new IllegalArgumentException("could not find enum value: " + value); } } /** * Checks whether or not the specified {@link Annotation} exists in the passed {@link Object}'s * class hierarchy. * @param object object to check * @param annotation annotation to look for * @return true is a class in this passed object's type hierarchy is annotated with the * passed {@link Annotation} */ public static boolean isClassAnnotatedForClassHierarchy(Object object, final Class<? extends Annotation> annotation) { final MutableBoolean bool = new MutableBoolean(false); visitClassHierarchy(object.getClass(), new Visitor<Class<?>>() { public void visit(Class<?> klass) { if (klass.isAnnotationPresent(annotation)) { bool.setValue(true); } } }); return bool.booleanValue(); } /** * Returns the list of fields on this class annotated with the passed {@link Annotation} * @param klass checks the {@link Field}s on this class * @param annotation looks for this {@link Annotation} * @return list of all {@link Field}s that are annotated with the specified {@link Annotation} */ public static List<Field> getAnnotatedFields(Class<?> klass, Class<? extends Annotation> annotation) { List<Field> annotatedFields = Lists.newArrayList(); for (Field f : klass.getDeclaredFields()) { if (f.isAnnotationPresent(annotation)) { annotatedFields.add(f); } } return annotatedFields; } /** * Returns the list of fields on this class or any of its ancestors annotated with the * passed {@link Annotation}. * @param klass checks the {@link Field}s on this class and its ancestors * @param annotation looks for this {@link Annotation} * @return list of all {@link Field}s that are annotated with the specified {@link Annotation} */ public static List<Field> getAnnotatedFieldsForClassHierarchy(Class<?> klass, final Class<? extends Annotation> annotation) { final List<Field> annotatedFields = Lists.newArrayList(); visitClassHierarchy(klass, new Visitor<Class<?>>() { public void visit(Class<?> c) { annotatedFields.addAll(getAnnotatedFields(c, annotation)); } }); return annotatedFields; } private static List<ObjectFieldMethod> getParameterlessMethods(Object tupleObject, Class<?> klass) { List<ObjectFieldMethod> methods = Lists.newArrayList(); for (Method method : klass.getDeclaredMethods()) { if (method.getParameterTypes().length == 0) { methods.add(new ObjectFieldMethod(tupleObject, null, method)); } } return methods; } /** * Returns all methods in the passed object's class hierarchy that do no not take parameters * @param object object to query for parameterless methods * @return a list {@link ObjectFieldMethod} tuples mapping the parameterless methods to * the passed object. */ public static List<ObjectFieldMethod> getParameterlessMethodsForClassHierarchy(final Object object) { final List<ObjectFieldMethod> methods = Lists.newArrayList(); visitClassHierarchy(object.getClass(), new Visitor<Class<?>>() { public void visit(Class<?> c) { methods.addAll(getParameterlessMethods(object, c)); } }); return methods; } /** * Returns a {@link Function} that will read values from the named field from a passed object. * @param klass type to read values from * @param returnType return type of read field * @param getter name of the field * @return a {@link Function} object that, when applied to an instance of <code>klass</code>, returns the * of type <code>returnType</code> that resides in field <code>getter</code> */ public static <F, T> Function<F, T> getterFunction(final Class<F> klass, final Class<T> returnType, String getter) { try { BeanInfo beanInfo = Introspector.getBeanInfo(klass); PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); Method method = null; for (PropertyDescriptor descriptor : props) { if (descriptor.getName().equals(getter)) { method = descriptor.getReadMethod(); break; } } if (method == null) { throw new IllegalStateException(); } final Method readMethod = method; return new Function<F, T>() { public T apply(F from) { try { return returnType.cast(readMethod.invoke(from)); } catch (Exception e) { Throwables.throwUncheckedException(e); return null; } } }; } catch (IntrospectionException e) { Throwables.throwUncheckedException(e); return null; } } }