package org.deephacks.confit.serialization;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.deephacks.confit.Config;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* General purpose class for commonly used reflection operations.
*
* @author Kristoffer Sjogren
*/
public final class Reflections {
private static final Map<String, Class<?>> ALL_PRIMITIVE_TYPES = new HashMap<>();
private static final Map<String, Class<?>> ALL_PRIMITIVE_NUMBERS = new HashMap<>();
static {
for (Class<?> primitiveNumber : Arrays.asList(byte.class, short.class,
int.class, long.class, float.class, double.class)) {
ALL_PRIMITIVE_NUMBERS.put(primitiveNumber.getName(), primitiveNumber);
ALL_PRIMITIVE_TYPES.put(primitiveNumber.getName(), primitiveNumber);
}
for (Class<?> primitive : Arrays.asList(char.class, boolean.class)) {
ALL_PRIMITIVE_TYPES.put(primitive.getName(), primitive);
}
}
public static Class<?> forName(String className) {
try {
Class<?> primitive = ALL_PRIMITIVE_TYPES.get(className);
return primitive != null ? primitive : Thread.currentThread()
.getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static boolean isPrimitiveNumber(Class<?> type) {
return ALL_PRIMITIVE_NUMBERS.get(type.getName()) != null;
}
/**
* Get list superclasses and interfaces recursively.
*
* @param clazz The class to start the search with.
*
* @return List of list super classes and interfaces of {@code clazz}. The list contains the class itself! The empty
* list is returned if {@code clazz} is {@code null}.
*/
public static List<Class<?>> computeClassHierarchy(Class<?> clazz) {
List<Class<?>> classes = new ArrayList<Class<?>>();
computeClassHierarchy(clazz, classes);
return classes;
}
/**
* Get list superclasses and interfaces recursively.
*
* @param clazz The class to start the search with.
* @param classes List of classes to which to add list found super classes and interfaces.
*/
private static void computeClassHierarchy(Class<?> clazz, List<Class<?>> classes) {
for (Class<?> current = clazz; current != null; current = current.getSuperclass()) {
if (classes.contains(current)) {
return;
}
classes.add(current);
for (Class<?> currentInterface : current.getInterfaces()) {
computeClassHierarchy(currentInterface, classes);
}
}
}
public static List<Class<?>> computeEnclosingClasses(Class<?> clazz) {
List<Class<?>> classes = new ArrayList<Class<?>>();
computeEnclosingClasses(clazz, classes);
return classes;
}
private static void computeEnclosingClasses(Class<?> clazz, List<Class<?>> classes) {
for (Class<?> current = clazz; current != null; current = current.getEnclosingClass()) {
if (classes.contains(current)) {
return;
}
classes.add(current);
for (Class<?> currentInterface : current.getInterfaces()) {
computeEnclosingClasses(currentInterface, classes);
}
}
}
public static <T> T newInstance(Class<T> type) throws InstantiationException,
IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Constructor<?> c;
if(Modifier.isStatic(type.getModifiers())) {
c = type.getDeclaredConstructor(new Class[] {});
} else {
try {
Class<?> enclosing = type.getEnclosingClass();
if(type.getName().contains("$") && enclosing != null) {
throw new IllegalArgumentException("Non-static inner classes are not supported: " + type);
}
} catch (Exception e) {
// this may occur for byte code generated proxies
throw new IllegalArgumentException("Non-static inner classes are not supported: " + type);
}
c = type.getDeclaredConstructor();
}
c.setAccessible(true);
return type.cast(c.newInstance());
}
public static Object newInstance(String clazzName, String value) {
try {
Class<?> type = forName(clazzName);
Class<?> enclosing = type.getEnclosingClass();
if (enclosing == null) {
Constructor<?> c = type.getConstructor(String.class);
c.setAccessible(true);
return type.cast(c.newInstance(value));
}
Object o = enclosing.newInstance();
Constructor<?> c = type.getDeclaredConstructor(enclosing, String.class);
c.setAccessible(true);
return type.cast(c.newInstance(o, value));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Object newInstance(String className) {
try {
Class<?> type = forName(className);
Class<?> enclosing = type.getEnclosingClass();
if (enclosing == null) {
Constructor<?> c = type.getDeclaredConstructor();
c.setAccessible(true);
return c.newInstance();
}
Object o = enclosing.newInstance();
Constructor<?> cc = type.getDeclaredConstructor(enclosing);
cc.setAccessible(true);
return cc.newInstance(o);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static <T> Class<? extends T> getComponentType(T[] a) {
Class<?> k = a.getClass().getComponentType();
return (Class<? extends T>) k; // unchecked cast
}
public static <T> T[] newArray(T[] a, int size) {
return newArray(getComponentType(a), size);
}
@SuppressWarnings("unchecked")
public static <T> T[] newArray(Class<? extends T> k, int size) {
if (k.isPrimitive())
throw new IllegalArgumentException("Argument cannot be primitive: " + k);
Object a = java.lang.reflect.Array.newInstance(k, size);
return (T[]) a; // unchecked cast
}
public static <T> T cast(Class<T> clazz, Object object) {
if (object == null) {
return null;
}
if (clazz.isAssignableFrom(object.getClass())) {
return clazz.cast(object);
}
return null;
}
/**
* Find list field (including private) on a specific class. Searches list
* super-classes up to {@link Object}. If the same field name is found in
* a superclass it is ignore.
*
* @param clazz
* Class to inspect
* @return list found fields.
*/
public static Map<String, Field> findFields(final Class<?> clazz) {
Map<String, Field> foundFields = new HashMap<>();
Class<?> searchType = clazz;
while (!Object.class.equals(searchType) && (searchType != null)) {
Field[] fields = searchType.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if(foundFields.get(field.getName()) == null) {
foundFields.put(field.getName(), field);
}
}
searchType = searchType.getSuperclass();
}
return foundFields;
}
public static Field findField(final Class<?> cls, String fieldName) {
Class<?> searchType = cls;
while (!Object.class.equals(searchType) && (searchType != null)) {
Field field = null;
try {
field = searchType.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// ignore
}
if(field != null) {
field.setAccessible(true);
return field;
}
searchType = searchType.getSuperclass();
}
throw new RuntimeException("Could not find field " + fieldName + " on " + cls);
}
public static Multimap<Class<? extends Annotation>, Field> findFieldsAnnotations(
List<Field> fields) {
Multimap<Class<? extends Annotation>, Field> fieldAnnotations = ArrayListMultimap.create();
for (Field field : fields) {
field.setAccessible(true);
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
fieldAnnotations.put(annotation.annotationType(), field);
}
}
return fieldAnnotations;
}
/**
* Returns the parameterized type of a field, if exists. Wild cards, type
* variables and raw types will be returned as an empty list.
* <p>
* If a field is of type Set<String> then java.lang.String is returned.
* </p>
* <p>
* If a field is of type Map<String, Integer> then [java.lang.String,
* java.lang.Integer] is returned.
* </p>
*
* @param field
* @return A list of classes of the parameterized type.
*/
public static List<Class<?>> getParameterizedType(final Field field) {
Type type = field.getGenericType();
if (!ParameterizedType.class.isAssignableFrom(type.getClass())) {
// the field is it a raw type and does not have generic type
// argument. Return empty list.
return new ArrayList<Class<?>>();
}
ParameterizedType ptype = (ParameterizedType) type;
Type[] targs = ptype.getActualTypeArguments();
List<Class<?>> classes = new ArrayList<Class<?>>();
for (Type aType : targs) {
if (Class.class.isAssignableFrom(aType.getClass())) {
classes.add((Class<?>) aType);
} else if (WildcardType.class.isAssignableFrom(aType.getClass())) {
// wild cards are not handled by this method
} else if (TypeVariable.class.isAssignableFrom(aType.getClass())) {
// type variables are not handled by this method
}
}
return classes;
}
/**
* Returns the parameterized type of a class, if exists. Wild cards, type
* variables and raw types will be returned as an empty list.
* <p>
* If a field is of type Set<String> then java.lang.String is returned.
* </p>
* <p>
* If a field is of type Map<String, Integer> then [java.lang.String,
* java.lang.Integer] is returned.
* </p>
*
* @param ownerClass the implementing target class to check against
* @param ownerClass generic interface to resolve the type argument from
* @return A list of classes of the parameterized type.
*/
public static List<Class<?>> getParameterizedType(final Class<?> ownerClass,
Class<?> genericSuperClass) {
Type[] types = null;
if (genericSuperClass.isInterface()) {
types = ownerClass.getGenericInterfaces();
} else {
types = new Type[] { ownerClass.getGenericSuperclass() };
}
List<Class<?>> classes = new ArrayList<>();
for (Type type : types) {
if (!ParameterizedType.class.isAssignableFrom(type.getClass())) {
// the field is it a raw type and does not have generic type
// argument. Return empty list.
return new ArrayList<Class<?>>();
}
ParameterizedType ptype = (ParameterizedType) type;
Type[] targs = ptype.getActualTypeArguments();
for (Type aType : targs) {
classes.add(extractClass(ownerClass, aType));
}
}
return classes;
}
public static Method getStaticMethod(Class<?> clazz, String methodName, Class<?>... args) {
try {
Method method = clazz.getMethod(methodName, args);
return Modifier.isStatic(method.getModifiers()) ? method : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... paramTypes) {
try {
return clazz.getConstructor(paramTypes);
} catch (NoSuchMethodException ex) {
return null;
}
}
private static Class<?> extractClass(Class<?> ownerClass, Type arg) {
if (arg instanceof ParameterizedType) {
return extractClass(ownerClass, ((ParameterizedType) arg).getRawType());
} else if (arg instanceof GenericArrayType) {
throw new UnsupportedOperationException("GenericArray types are not supported.");
} else if (arg instanceof TypeVariable) {
throw new UnsupportedOperationException("GenericArray types are not supported.");
}
return (arg instanceof Class ? (Class<?>) arg : Object.class);
}
/**
* TODO: Need to rewrite this mess!
*/
public static class ClassIntrospector {
private Class<?> clazz;
private Map<String, Field> fields;
public ClassIntrospector(Class<?> clazz) {
this.clazz = clazz;
fields = findFields(clazz);
}
/**
* Get the class name of the class.
* @return
*/
public String getName() {
return clazz.getName();
}
public Class<?> getTarget() {
return clazz;
}
/**
* Get class level annotation for class.
*
* @param annotation
* @return
*/
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
return clazz.getAnnotation(annotation);
}
public <T extends Annotation> List<FieldWrap> getFieldList(Class<T> clazz) {
ArrayList<FieldWrap> wrap = new ArrayList<>();
for (Field f : fields.values()) {
if (f.isAnnotationPresent(clazz)) {
wrap.add(new FieldWrap(f, f.getAnnotation(clazz)));
}
}
return wrap;
}
public Collection<FieldWrap> getNonStaticFieldList() {
ArrayList<FieldWrap> wrap = new ArrayList<>();
for (Field f : fields.values()) {
if (Modifier.isFinal(f.getModifiers()) && Modifier.isStatic(f.getModifiers())) {
continue;
}
if (Modifier.isTransient(f.getModifiers())) {
continue;
}
wrap.add(new FieldWrap(f));
}
return wrap;
}
public <T extends Annotation> Map<String, FieldWrap> getFieldMap(Class<T> clazz) {
HashMap<String, FieldWrap> wrap = new HashMap<>();
for (Field f : fields.values()) {
if (f.isAnnotationPresent(clazz)) {
wrap.put(f.getName(), new FieldWrap(f, f.getAnnotation(clazz)));
}
}
return wrap;
}
/**
* TODO: Need to rewrite this mess!
*/
public static class FieldWrap {
private Optional<Annotation> annotation;
private Field field;
private boolean isCollection = false;
private boolean isMap = false;
private Object defaultDeclaringInstance;
private boolean primitive;
private boolean array;
public FieldWrap(Field f, Annotation annotation) {
this.field = f;
this.annotation = Optional.of(annotation);
this.isCollection = Collection.class.isAssignableFrom(f.getType());
this.isMap = Map.class.isAssignableFrom(f.getType());
}
public FieldWrap(Field f) {
this.field = f;
Annotation config = f.getAnnotation(Config.class);
if (config == null) {
this.annotation = Optional.absent();
} else {
this.annotation = Optional.of(config);
}
this.isCollection = Collection.class.isAssignableFrom(f.getType());
this.isMap = Map.class.isAssignableFrom(f.getType());
}
public Field getField() {
return field;
}
public Optional<Annotation> getAnnotation() {
return annotation;
}
public String getFieldName() {
return field.getName();
}
public Class<?> getType() {
if (!isCollection) {
return field.getType();
}
List<Class<?>> p = getParameterizedType(field);
if (p.size() == 0) {
throw new UnsupportedOperationException("Collection of field [" + field
+ "] does not have parameterized arguments, which is not allowed.");
}
return p.get(0);
}
public List<Class<?>> getMapParamTypes() {
if (!isMap) {
throw new UnsupportedOperationException("Field [" + field + "] is not a map.");
}
List<Class<?>> p = getParameterizedType(field);
if (p.size() == 0) {
throw new UnsupportedOperationException("Map of field [" + field
+ "] does not have parameterized arguments, which is not allowed.");
}
return p;
}
public boolean isCollection() {
return isCollection;
}
public boolean isMap() {
return isMap;
}
public boolean isFinal() {
return Modifier.isFinal(field.getModifiers());
}
public boolean isStatic() {
return Modifier.isStatic(field.getModifiers());
}
public boolean isTransient() {
return Modifier.isTransient(field.getModifiers());
}
public boolean isAnnotationPresent(Class<? extends Annotation> cls) {
return field.isAnnotationPresent(cls);
}
public List<String> getEnums() {
if (!isCollection) {
if (field.getType().isEnum()) {
List<String> s = new ArrayList<>();
for (Object o : field.getType().getEnumConstants()) {
s.add(o.toString());
}
return s;
} else {
return new ArrayList<>();
}
}
List<Class<?>> p = getParameterizedType(field);
if (p.size() == 0) {
throw new UnsupportedOperationException("Collection of field [" + field
+ "] does not have parameterized arguments, which is not allowed.");
}
if (p.get(0).isEnum()) {
List<String> s = new ArrayList<>();
for (Object o : p.get(0).getEnumConstants()) {
s.add(o.toString());
}
return s;
}
return new ArrayList<>();
}
/**
* Return the raw collection type
* @return
*/
public Class<?> getCollRawType() {
if (!isCollection) {
throw new UnsupportedOperationException("This field is not a collection.");
}
return field.getType();
}
/**
* Return the raw map type
* @return
*/
public Class<?> getMapRawType() {
if (!isMap) {
throw new UnsupportedOperationException("This field is not a map.");
}
return field.getType();
}
public boolean isPrimitive() {
return field.getType().isPrimitive();
}
public Object getDefaultValue() {
if (defaultDeclaringInstance == null) {
try {
defaultDeclaringInstance = newInstance(field.getDeclaringClass());
} catch (InstantiationException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of class which cannot be constructed.", e);
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of class which cannot be constructed.", e);
} catch (InvocationTargetException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of class which cannot be constructed.", e);
} catch (NoSuchMethodException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of class which cannot be constructed.", e);
}
}
try {
return field.get(defaultDeclaringInstance);
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of instances which cannot be accessed.", e);
} catch (IllegalAccessException e) {
throw new UnsupportedOperationException("Cannot access default values "
+ "from fields of class which cannot be accessed.", e);
}
}
public Object getStaticValue() {
try {
return field.get(null);
} catch (Exception e) {
throw new IllegalArgumentException(
"Cannot access values from fields that arent static.");
}
}
@SuppressWarnings("unchecked")
public Collection<Object> getDefaultValues() {
if (!isCollection) {
throw new UnsupportedOperationException("This field is not a collection.");
}
return (Collection<Object>) getDefaultValue();
}
public Object getValue(Object source) {
try {
return field.get(source);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void checkNotPublic() {
if(Modifier.isPublic(field.getModifiers())) {
throw new UnsupportedOperationException(
"Field ["+field+"] is public. Public fields are not allowed since it " +
"breaks the API of having cached proxies.");
}
}
public boolean isArray() {
return field.getType().isArray();
}
}
public <T extends Annotation> List<Field> getFieldsAnnotatedWith(Class<T> clazz) {
ArrayList<Field> f = new ArrayList<>();
for (Field field : fields.values()) {
if (field.isAnnotationPresent(clazz)) {
f.add(field);
}
}
return f;
}
}
}