package org.squirrelframework.foundation.util; 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.util.Arrays; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.squirrelframework.foundation.exception.ErrorCodes; import org.squirrelframework.foundation.exception.SquirrelRuntimeException; import com.google.common.collect.Lists; import com.google.common.collect.Sets; public class ReflectUtils { private ReflectUtils() {} private final static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); public static Set<Field> getAllDeclaredFields(final Class<?> theClass) { Set<Field> aFields = Sets.newHashSet(); Class<?> aClass = theClass; while (aClass != null) { aFields.addAll(Sets.newHashSet(aClass.getDeclaredFields())); aClass = aClass.getSuperclass(); } return aFields; } /** searches for the field in the given class and in its super classes */ public static Field getField(Class<?> clazz, String fieldName) { return getField(clazz, fieldName, clazz); } private static Field getField(Class<?> clazz, String fieldName, Class<?> original) { Field field = null; try { field = clazz.getDeclaredField(fieldName); if (logger.isTraceEnabled()) { logger.trace("found field "+fieldName+" in "+getClassNameSafe(clazz)); } } catch (SecurityException e) { throw new SquirrelRuntimeException(e, ErrorCodes.NOT_ALLOW_ACCESS_FIELD, getClassNameSafe(clazz), fieldName); } catch (NoSuchFieldException e) { if (clazz.getSuperclass()!=null) { return getField(clazz.getSuperclass(), fieldName, original); } else { throw new SquirrelRuntimeException(e, ErrorCodes.FIELD_NOT_FOUND, original.getName(), fieldName); } } return field; } /** searches for the method in the given class and in its super classes */ public static Method getMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes) { return getMethod(clazz, methodName, parameterTypes, clazz); } private static Method getMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Class<?> original) { Method method = null; try { method = clazz.getDeclaredMethod(methodName, parameterTypes); if (logger.isTraceEnabled()) { logger.trace("found method "+getClassNameSafe(clazz)+"."+methodName+"("+Arrays.toString(parameterTypes)+")"); } } catch (SecurityException e) { throw new SquirrelRuntimeException(e, ErrorCodes.NOT_ALLOW_ACCESS_METHOD, getClassNameSafe(clazz), methodName, getParameterTypesText(parameterTypes)); } catch (NoSuchMethodException e) { if (clazz.getSuperclass()!=null) { return getMethod(clazz.getSuperclass(), methodName, parameterTypes, original); } else { throw new SquirrelRuntimeException(e, ErrorCodes.METHOD_NOT_FOUND, original.getName(), methodName, getParameterTypesText(parameterTypes)); } } return method; } private static String getParameterTypesText(Class<?>[] parameterTypes) { if (parameterTypes==null) return ""; StringBuilder parameterTypesText = new StringBuilder(); for (int i=0; i<parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; parameterTypesText.append(parameterType.getName()); if (i!=parameterTypes.length-1) { parameterTypesText.append(", "); } } return parameterTypesText.toString(); } public static String logMethod(Method method) { StringBuilder builder = new StringBuilder(method.getDeclaringClass().getSimpleName()); builder.append('.').append(method.getName()).append('('); if(method.getParameterTypes()!=null) { for(int i=0, size=method.getParameterTypes().length; i<size; ++i) { if(i!=0) builder.append(", "); builder.append(method.getParameterTypes()[i].getSimpleName()); } } builder.append(')'); return builder.toString(); } /** * Return the given annotation from the class. If the class does not have the annotation, it's parent class and any * interfaces will also be checked. * @param theClass the class to inspect * @param theAnnotation the annotation to retrieve * @return the class's annotation, or it's "inherited" annotation, or null if the annotation cannot be found. */ public static <T extends Annotation> T getAnnotation(Class<?> theClass, Class<T> theAnnotation) { T aAnnotation = null; if (theClass.isAnnotationPresent(theAnnotation)) { aAnnotation = theClass.getAnnotation(theAnnotation); } else { if (shouldInspectClass(theClass.getSuperclass())) aAnnotation = getAnnotation(theClass.getSuperclass(), theAnnotation); if (aAnnotation == null) { for (Class<?> aInt : theClass.getInterfaces()) { aAnnotation = getAnnotation(aInt, theAnnotation); if (aAnnotation != null) { break; } } } } return aAnnotation; } /** * Return whether or not the class has the given annotation. If the class itself does not have the annotation, * it's super class and the interfaces it implements are checked. * @param theClass the class to check * @param theAnnotation the annotation to look for * @return if the class has the annotation, or one of its parents does and it "inherited" the annotation, false otherwise */ public static boolean hasAnnotation(Class<?> theClass, Class<? extends Annotation> theAnnotation) { return getAnnotation(theClass, theAnnotation) != null; } public static List<Method> getAnnotatedMethods( Class<?> targetClass, Class<? extends Annotation> annotationClass) { List<Method> aMethods = Lists.newArrayList(); for (Method aMethod : targetClass.getMethods()) { if (aMethod.getAnnotation(annotationClass) != null) { aMethods.add(aMethod); } } if (shouldInspectClass(targetClass.getSuperclass())) { aMethods.addAll(getAnnotatedMethods(targetClass.getSuperclass(), annotationClass)); } for (Class<?> aInterface : targetClass.getInterfaces()) { aMethods.addAll(getAnnotatedMethods(aInterface, annotationClass)); } return aMethods; } private static boolean shouldInspectClass(final Class<?> theClass) { return !Object.class.equals(theClass) && theClass != null; } public static Field[] getAnnotatedFields( Class<?> targetClass, Class<? extends Annotation> annotationClass) { List<Field> annotatedFields = Lists.newArrayList(); Class<?> k = targetClass; while(shouldInspectClass(k)) { for(Field f : k.getFields()) { if(f.getAnnotation(annotationClass)!=null) { annotatedFields.add(f); } } k = k.getSuperclass(); } return annotatedFields.toArray(new Field[0]); } public static boolean isAnnotatedWith(Object obj, Class<? extends Annotation> theAnnotation) { if(obj instanceof Class) { return hasAnnotation((Class<?>)obj, theAnnotation); } else if(obj instanceof Field) { return ((Field)obj).getAnnotation(theAnnotation)!=null; } else if(obj instanceof Method) { return ((Method)obj).getAnnotation(theAnnotation)!=null; } // TODO-hhe: how about annotation with parameter? return false; } public static Method getFirstMethodOfName(Class<?> clazz, String name) { for(Method m : clazz.getMethods()) if(m.getName().equals(name)) { return m; } return null; } public static <T> Constructor<T> getConstructor(Class<T> type, Class<?>[] parameterTypes) { try { Constructor<T> constructor = type.getDeclaredConstructor(parameterTypes); return constructor; } catch (NoSuchMethodException e) { throw new SquirrelRuntimeException(e, ErrorCodes.CONSTRUCTOR_NOT_FOUND, type.getName(), Arrays.toString(parameterTypes)); } } @SuppressWarnings("unchecked") public static <T> T newInstance(String className) { return (T) newInstance(getClass(className), null, null); } public static <T> T newInstance(Class<T> clazz) { return newInstance(clazz, null, null); } public static <T> T newInstance(Constructor<T> constructor) { return newInstance(null, constructor, null); } public static <T> T newInstance(Constructor<T> constructor, Object[] args) { return newInstance(null, constructor, args); } private static <T> T newInstance(Class<T> clazz, Constructor<T> constructor, Object[] args) { if ( (clazz==null) && (constructor==null) ) { throw new IllegalArgumentException("can't create new instance without clazz or constructor"); } if (logger.isTraceEnabled()) { logger.trace("creating new instance for class '"+getClassNameSafe(clazz)+"' with args "+Arrays.toString(args)); } if (constructor==null) { if (logger.isTraceEnabled()) logger.trace("getting default constructor"); constructor = getConstructor(clazz, (Class[])null); } boolean oldAccessible = constructor.isAccessible(); try { if (!constructor.isAccessible()) { if (logger.isTraceEnabled()) logger.trace("making constructor accessible"); constructor.setAccessible(true); } return constructor.newInstance(args); } catch (Throwable t) { Throwable cause = (t instanceof InvocationTargetException) ? ((InvocationTargetException)t).getTargetException() : t; throw new SquirrelRuntimeException(cause, ErrorCodes.CONSTRUCT_NEW_INSTANCE_ERROR, getClassNameSafe(clazz), Arrays.toString(args)); } finally { constructor.setAccessible(oldAccessible); } } public static Object getStatic(Field field) { return get(field, null); } public static Object get(Field field, Object object) { if (field==null) { throw new SquirrelRuntimeException(ErrorCodes.FIELD_NULL); } boolean oldAccessible = field.isAccessible(); try { field.setAccessible(true); Object value = field.get(object); if (logger.isTraceEnabled()) { logger.trace("got value '"+value+"' from field '"+field.getName()+"'"); } return value; } catch (Exception e) { throw new SquirrelRuntimeException(e, ErrorCodes.CANNOT_GET_FIELD_VALUE, field.getName()); } finally { field.setAccessible(oldAccessible); } } public static void setStatic(Field field, Object value) { set(field, null, value); } public static void set(Field field, Object object, Object value) { if (field==null) { throw new SquirrelRuntimeException(ErrorCodes.FIELD_NULL); } boolean oldAccessible = field.isAccessible(); try { if (logger.isTraceEnabled()) { logger.trace("setting field '"+field.getName()+"' to value '"+value+"'"); } if (!oldAccessible) { if (logger.isTraceEnabled()) logger.trace("making field accessible"); field.setAccessible(true); } field.set(object, value); } catch (Exception e) { throw new SquirrelRuntimeException(e, ErrorCodes.CANNOT_SET_FIELD_VALUE, field.getName(), value); } finally { field.setAccessible(oldAccessible); } } public static Object invokeStatic(Method method) { return invoke(method, null, new Object[0]); } public static Object invokeStatic(Method method, Object[] args) { return invoke(method, null, args); } public static Object invoke(Method method, Object target) { return invoke(method, target, new Object[0]); } public static Object invoke(Method method, Object target, Object[] args) { if (method==null) { throw new SquirrelRuntimeException(ErrorCodes.METHOD_NULL); } boolean oldAccessible = method.isAccessible(); try { if (logger.isTraceEnabled()) { logger.trace("invoking '"+method.getName()+"' on '"+target+"' with "+Arrays.toString(args)); } if (!method.isAccessible()) { logger.trace("making method accessible"); method.setAccessible(true); } return method.invoke(target, args); } catch (InvocationTargetException e) { Throwable targetException = e.getTargetException(); throw new SquirrelRuntimeException(targetException, ErrorCodes.METHOD_INVOKE_ERROR, method, Arrays.toString(args), target, targetException.getCause()); } catch (Exception e) { throw new SquirrelRuntimeException(e, ErrorCodes.METHOD_INVOKE_ERROR, method, Arrays.toString(args), target, e.getMessage()); } finally { method.setAccessible(oldAccessible); } } /** * Invoke the given callback on all fields in the target class, going up the * class hierarchy to get all declared fields. * @param clazz the target class to analyze * @param fc the callback to invoke for each field */ public static void doWithFields(Class<?> clazz, FieldCallback fc) throws IllegalArgumentException { doWithFields(clazz, fc, null); } /** * Invoke the given callback on all fields in the target class, going up the * class hierarchy to get all declared fields. * @param clazz the target class to analyze * @param fc the callback to invoke for each field * @param ff the filter that determines the fields to apply the callback to */ public static void doWithFields(Class<?> clazz, FieldCallback fc, FieldFilter ff) throws IllegalArgumentException { // Keep backing up the inheritance hierarchy. Class<?> targetClass = clazz; do { Field[] fields = targetClass.getDeclaredFields(); for (Field field : fields) { // Skip static and final fields. if (ff != null && !ff.matches(field)) { continue; } fc.doWith(field); } targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); } /** * Perform the given callback operation on all matching methods of the given * class and superclasses. * <p>The same named method occurring on subclass and superclass will appear * twice, unless excluded by a {@link MethodFilter}. * @param clazz class to start looking at * @param mc the callback to invoke for each method * @see #doWithMethods(Class, MethodCallback, MethodFilter) */ public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException { doWithMethods(clazz, mc, null); } /** * Perform the given callback operation on all matching methods of the given * class and superclasses (or given interface and super-interfaces). * <p>The same named method occurring on subclass and superclass will appear * twice, unless excluded by the specified {@link MethodFilter}. * @param clazz class to start looking at * @param mc the callback to invoke for each method * @param mf the filter that determines the methods to apply the callback to */ public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) throws IllegalArgumentException { // Keep backing up the inheritance hierarchy. Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (mf != null && !mf.matches(method)) { continue; } mc.doWith(method); } if (clazz.getSuperclass() != null) { doWithMethods(clazz.getSuperclass(), mc, mf); } else if (clazz.isInterface()) { for (Class<?> superIfc : clazz.getInterfaces()) { doWithMethods(superIfc, mc, mf); } } } /** * Returns all superclasses. */ public static Class<?>[] getSuperclasses(Class<?> type) { int i = 0; for (Class<?> x = type.getSuperclass(); x != null; x = x.getSuperclass()) { i++; } Class<?>[] result = new Class[i]; i = 0; for (Class<?> x = type.getSuperclass(); x != null; x = x.getSuperclass()) { result[i] = x; i++; } return result; } /** * Returns <code>true</code> if method is user defined and not defined in <code>Object</code> class. */ public static boolean isUserDefinedMethod(final Method method) { return method.getDeclaringClass() != Object.class; } /** * Returns <code>true</code> if method is a bean property. */ public static boolean isBeanProperty(Method method) { String methodName = method.getName(); Class<?> returnType = method.getReturnType(); Class<?>[] paramTypes = method.getParameterTypes(); if (methodName.startsWith("get") && methodName.equals("getClass") == false) { // getter method must starts with 'get' and it is not getClass() if ((returnType != null) && (paramTypes.length == 0)) { // getter must have a return type and no arguments return true; } } else if (methodName.startsWith("is")) { // ister must starts with 'is' if ((returnType != null) && (paramTypes.length == 0)) { // ister must have return type and no arguments return true; } } else if (methodName.startsWith("set")) { // setter must start with a 'set' if (paramTypes.length == 1) { // setter must have just one argument return true; } } return false; } public static String getPackageName(final String className) { if (className==null || className.length() == 0) throw new SquirrelRuntimeException(ErrorCodes.ILLEGAL_CLASS_NAME); int index = className.lastIndexOf('.'); if (index != -1) return className.substring(0, index); return ""; } public static Class<?> getClass(final String className) { if (className==null || className.length() == 0) throw new SquirrelRuntimeException(ErrorCodes.ILLEGAL_CLASS_NAME); try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new SquirrelRuntimeException(e, ErrorCodes.CLASS_NOT_FOUND, className); } } private static String getClassNameSafe(Class<?> maybeNullClazz) { if (maybeNullClazz == null) return null; else return maybeNullClazz.getName(); } /** * Action to take on each method. */ public interface MethodCallback { /** * Perform an operation using the given method. * @param method the method to operate on */ void doWith(Method method); } /** * Callback optionally used to filter methods to be operated on by a method callback. */ public interface MethodFilter { /** * Determine whether the given method matches. * @param method the method to check */ boolean matches(Method method); } /** * Callback interface invoked on each field in the hierarchy. */ public interface FieldCallback { /** * Perform an operation using the given field. * @param field the field to operate on */ void doWith(Field field); } /** * Callback optionally used to filter fields to be operated on by a field callback. */ public interface FieldFilter { /** * Determine whether the given field matches. * @param field the field to check */ boolean matches(Field field); } }