package easyjava;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import easyjava.annotations.ThrowRTE;
import easyjava.exceptions.InstanceCreationException;
import easyjava.exceptions.ReadOnlyFieldException;
/**
* Provides static utility methods to make reflection operations easy.
*
* @author Ovunc Cetin
*/
public class ReflectUtils {
/**
* Creates a new instance of the given class with the given constructor
* arguments.
*
* @param cls
* class to instantiate
* @param args
* constructor arguments.
* @return new instance
*/
@ThrowRTE(throwThis = InstanceCreationException.class)
public static <T> T createInstance(Class<T> cls, Object... args) {
// find the cons. that matches the given args.
Constructor<T> cons = findConstructorForArgs(cls, args);
return cons.newInstance(args);
}
/**
* Returns <code>true</code> if the given type is neither abstract class nor
* interface.
*
* @param type
* type to check.
* @return <code>true</code> if the type is neither abstract class nor
* interface, <code>false</code> otherwise.
*/
public static boolean isConcreteType(Class<?> type) {
return !(isAbstractClass(type) | type.isInterface());
}
/**
* Finds the annotation in given annotation type for the specified field.
* <p>
* To find the annotation, this method first looks for the field
* declaration. If it is found, it is returned. Otherwise, the getter and
* the setter methods are checked in order. In the case that the result is
* unsuccessful, then <code>null</code> is returned.
*
* @param <T>
* annotation type
* @param field
* field to look for its annotation.
* @param annCls
* annotation to look for.
* @return annotation if exists, <code>null</code> otherwise.
*/
public static <T extends Annotation> T findAnnotation(Field field,
Class<T> annCls) {
// look at field...
T annotation = field.getAnnotation(annCls);
if (annotation == null) {
// look at the getter...
annotation = getGetterMethod(field).getAnnotation(annCls);
if (annotation == null) {
// look at the setter method...
annotation = getSetterMethod(field).getAnnotation(annCls);
}
}
return annotation;
}
/**
* Returns the list of fields declared in the given class and annotated by
* the given annotation.
*
* @param target
* target class holding the fields.
* @param annotation
* annotation marking the fields.
* @return field list.
* @see #findPropertyFields(Class, Class)
*/
public static List<Field> findFields(Class<?> target,
Class<? extends Annotation> annotation) {
final List<Field> result = new ArrayList<Field>();
Field[] fields = target.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(annotation)) {
result.add(field);
}
}
return result;
}
/**
* Returns the list of methods declared in the given class and annotated by
* the given annotation.
*
* @param target
* target class holding the methods.
* @param annotation
* annotation marking the methods.
* @return method list.
*/
public static List<Method> findMethods(Class<?> target,
Class<? extends Annotation> annotation) {
final List<Method> result = new ArrayList<Method>();
Method[] methods = target.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(annotation)) {
result.add(method);
}
}
return result;
}
/**
* Returns the list of methods declared in target's type and annotated by
* the given annotation.
*
* @param target
* target object of which class has the result methods.
* @param annotation
* annotation marking the methods.
* @return method list.
*/
public static List<Method> findMethods(Object target,
Class<? extends Annotation> annotation) {
return findMethods(target.getClass(), annotation);
}
/**
* Returns the list of fields declared in the given class and annotated by
* the given annotation. It also returns the fields of which accessor or
* mutator is annotated by the annotation.
*
* @param target
* target class holding the fields.
* @param annotation
* annotation marking the fields and accessor/mutator methods.
* @return field list.
* @see #findFields(Class, Class)
*/
public static List<Field> findPropertyFields(Class<?> target,
Class<? extends Annotation> annotation) {
final List<Field> result = new ArrayList<Field>();
Field[] fields = target.getDeclaredFields();
for (Field field : fields) {
if (isAnnotatedProperty(field, annotation)) {
result.add(field);
}
}
return result;
}
/**
* If exists, it returns a constructor of the given class which takes
* arguments in the given types. Otherwise, <code>null</code> is returned.
*
* @param cls
* class to find its constructor.
* @param argTypes
* argument types of the desired constructor.
* @return desired constructor if exists, <code>null</code> otherwise.
*/
public static <T> Constructor<T> getConstructorIfExists(Class<T> cls,
Class<?>... argTypes) {
try {
return cls.getConstructor(argTypes);
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Returns the getter method for the given field.
*
* @param field
* field to return its getter.
* @return getter method of the field if exists, <code>null</code> otherwise
* if the field is write-only.
*/
public static Method getGetterMethod(Field field) {
String capFieldName = capitalize(field.getName());
String methodName = "get".concat(capFieldName);
Class<?> cls = field.getDeclaringClass();
try {
return cls.getMethod(methodName, new Class<?>[0]);
} catch (NoSuchMethodException e) {
// check for "is*" method for booleans...
if (field.getType().equals(boolean.class)
|| field.getType().equals(Boolean.class)) {
try {
methodName = "is".concat(capFieldName);
return cls.getMethod(methodName, new Class<?>[0]);
} catch (NoSuchMethodException e1) {
return null; // no is* method for boolean...
}
} else {
return null; // not boolean type...
}
}
}
/**
* Returns all annotations for the specified field. To collect the
* annotations, it looks for the field declaration and the corresponding
* accessor methods.
*
* @param field
* field to look for its annotation.
* @param annCls
* annotation to look for.
* @return annotation if exists, <code>null</code> otherwise.
*/
public static Map<Class<? extends Annotation>, Annotation> getPropertyAnnotations(
Field field) {
final Method getter = getGetterMethod(field);
final Method setter = getSetterMethod(field);
Map<Class<? extends Annotation>, Annotation> result = new HashMap<Class<? extends Annotation>, Annotation>();
// look at field...
Annotation[] annotations = field.getAnnotations();
putAllAnnotations(annotations, result);
if (getter != null) {
// look at the getter...
annotations = getter.getAnnotations();
putAllAnnotations(annotations, result);
}
if (setter != null) {
// look at the setter ...
annotations = setter.getAnnotations();
putAllAnnotations(annotations, result);
}
return result;
}
/**
* Returns the setter method for the given field.
*
* @param field
* field to return its setter.
* @return setter method of the field if exists, <code>null</code> otherwise
* if the field is read-only.
*/
public static Method getSetterMethod(Field field) {
String methodName = "set".concat(capitalize(field.getName()));
Class<?> cls = field.getDeclaringClass();
try {
return cls
.getMethod(methodName, new Class<?>[] { field.getType() });
} catch (NoSuchMethodException e) {
return null; // no such setter.
}
}
/**
* Returns if the given type is an abstract class or not.
*
* @param type
* type to check.
* @return <code>true</code> if the type is an abstract class,
* <code>false</code> otherwise. It returns <code>false</code> if
* the type is an interface which has <code>abstract</code>
* modifier.
*/
public static boolean isAbstractClass(Class<?> type) {
return !type.isInterface()
& (type.getModifiers() & Modifier.ABSTRACT) != 0;
}
/**
* Returns whether the given method's parameter in the specified index is
* annotated by the given annotation or not.
*
* @param method
* method taking the parameter.
* @param paramIndex
* index of the parameter to check.
* @param ann
* annotation expected to mark the parameter.
* @return <code>true</code> if the specified method parameter is annotated
* by the given annotation, <code>false</code> otherwise.
*/
public static boolean isAnnotatedBy(Method method, int paramIndex,
Class<? extends Annotation> ann) {
Annotation[][] allParamAnns = method.getParameterAnnotations();
Annotation[] paramAnns = allParamAnns[paramIndex];
for (Annotation paramAnn : paramAnns) {
if (paramAnn.annotationType().equals(ann)) {
return true;
}
}
return false;
}
/**
* Returns if the given field or its accessor/mutator method is annotated by
* the given annotation.
*
* @param ann
* annotation.
* @return <code>true</code> if the field or its get/set method is annotated
* by the annotation, <code>false</code> otherwise.
*/
public static boolean isAnnotatedProperty(Field f,
Class<? extends Annotation> ann) {
final Method getter = getGetterMethod(f);
final Method setter = getSetterMethod(f);
boolean result = f.isAnnotationPresent(ann);
result |= getter != null ? getter.isAnnotationPresent(ann) : false;
result |= setter != null ? setter.isAnnotationPresent(ann) : false;
return result;
}
/**
* Returns if the given type is an array type or sub type of
* {@link Collection} interface.
*
* @param type
* type to check.
* @return <code>true</code> if the type is an array or a collection,
* <code>false</code> otherwise.
*/
public static boolean isArrayOrCollection(Class<?> type) {
if (type.isArray() || Collection.class.isAssignableFrom(type)) {
return true;
}
return false;
}
/**
* Sets the given value to the specified property field by invoking the
* setter method of the field or by assigning the value if and only if the
* field is accessible.
*
* @param target
* target object which owns the field.
* @param f
* field to set its value.
* @param value
* value to set.
* @throws ReadOnlyFieldException
* if setter method for the field does not exist.
*/
public static void setPropertyField(Object target, Field f, Object value) {
if (f.isAccessible()) {
try { // assign the value...
f.set(target, value);
} catch (IllegalAccessException e) {
// never reaches here...
}
}
Method setter = getSetterMethod(f);
if (setter == null) {
throw new ReadOnlyFieldException(toLongFieldName(f));
}
try {
setter.invoke(target, value);
} catch (Exception e) {
throw new ReflectionException(e.getMessage(), e);
}
}
/**
* Sets the given value to the specified property field by invoking the
* setter method of the field or by assigning the value if and only if the
* field is accessible.
*
* @param target
* target object which owns the field.
* @param f
* field to set its value.
* @param value
* value to set.
* @throws ReadOnlyFieldException
* if setter method for the field does not exist.
*/
@ThrowRTE(throwThis = ReflectionException.class)
public static void setPropertyField(Object target, String fieldName,
Object value) {
setPropertyField(target, target.getClass().getDeclaredField(fieldName),
value);
}
/**
* Capitalize the given field name.
*/
private static String capitalize(String fieldName) {
return ("" + fieldName.charAt(0)).toUpperCase(Locale.ENGLISH)
+ fieldName.substring(1);
}
/**
* Finds the constructor which can be called by the given arguments. If no
* constructor found, <code>null</code> is returned.
*
* @throws NoSuchMethodException
*
*/
@SuppressWarnings("unchecked")
private static <T> Constructor<T> findConstructorForArgs(Class<T> cls,
Object... args) throws NoSuchMethodException {
Constructor<T>[] constructors = (Constructor<T>[]) cls
.getConstructors();
if (args.length == 0) { // default constructor...
return cls.getConstructor(new Class<?>[0]);
}
for (Constructor<T> cons : constructors) {
Class<?>[] argTypes = cons.getParameterTypes();
if (argTypes.length == args.length) {
// compare each argument type...
for (int i = 0; i < argTypes.length; i++) {
if (!argTypes[i].isInstance(args[i])) {
break; // one of the arguments not matched...
}
}
// all arguments are matched...
return cons;
}
}
return null;
}
/**
* Puts each annotation in the given array into the given map if the
* annotation does not already exist in the map.
*
* @param annotations
* annotations to put.
* @param target
* target map.
*/
private static void putAllAnnotations(Annotation[] annotations,
Map<Class<? extends Annotation>, Annotation> target) {
for (Annotation annotation : annotations) {
if (!target.containsKey(annotation.annotationType())) {
target.put(annotation.annotationType(), annotation);
}
}
}
private static String toLongFieldName(Field f) {
return f.getDeclaringClass().getName() + "#" + f.getName();
}
}