/** * DataCleaner (community edition) * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.datacleaner.util; import java.io.Closeable; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.Schema; import org.apache.metamodel.schema.Table; import org.datacleaner.api.InputColumn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.googlecode.gentyref.GenericTypeReflector; /** * Various static methods for reflection related tasks. * * */ public final class ReflectionUtils { /** * A lock used at various points when calling Class.getAnnotation(...) or * Field.getAnnotation(...), since it seems there is a deadlock issue in the * Sun JVM when calling this method in a multithreaded environment! */ public static final Object ANNOTATION_REFLECTION_LOCK = new Object(); private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); private ReflectionUtils() { // Prevent instantiation } /** * @return true if thisType is a valid type ofThatType, either as a single * instance or as an array of ofThatType */ public static boolean is(final Type thisType, final Class<?> ofThatType) { return is(thisType, ofThatType, true); } public static boolean is(final Type thisType, final Class<?> ofThatType, final boolean includeArray) { Class<?> thisClass = null; if (thisType instanceof Class<?>) { thisClass = (Class<?>) thisType; if (includeArray && thisClass.isArray() && !ofThatType.isArray()) { if (ofThatType == Object.class) { return true; } thisClass = thisClass.getComponentType(); } } if (thisClass == ofThatType) { return true; } if (thisClass.isPrimitive() != ofThatType.isPrimitive()) { if (isByte(thisClass) && isByte(ofThatType)) { return true; } if (isCharacter(thisClass) && isCharacter(ofThatType)) { return true; } if (isBoolean(thisClass) && isBoolean(ofThatType)) { return true; } if (isShort(thisClass) && isShort(ofThatType)) { return true; } if (isInteger(thisClass) && isInteger(ofThatType)) { return true; } if (isLong(thisClass) && isLong(ofThatType)) { return true; } if (isFloat(thisClass) && isFloat(ofThatType)) { return true; } if (isDouble(thisClass) && isDouble(ofThatType)) { return true; } } return ofThatType.isAssignableFrom(thisClass); } public static boolean isCharacter(final Type type) { return (type == char.class || type == Character.class); } public static boolean isInputColumn(final Class<?> type) { return is(type, InputColumn.class); } public static boolean isColumn(final Class<?> type) { return is(type, Column.class); } public static boolean isTable(final Class<?> type) { return is(type, Table.class); } public static boolean isSchema(final Class<?> type) { return is(type, Schema.class); } public static boolean isCloseable(final Class<?> type) { return is(type, Closeable.class); } public static boolean isBoolean(final Type type) { return (type == Boolean.class || type == boolean.class); } public static boolean isString(final Type type) { return is(type, String.class); } public static boolean isShort(final Type type) { return (type == Short.class || type == short.class); } public static boolean isDouble(final Type type) { return (type == Double.class || type == double.class); } public static boolean isLong(final Type type) { return (type == Long.class || type == long.class); } public static boolean isInteger(final Type type) { return (type == Integer.class || type == int.class); } public static boolean isFloat(final Type type) { return (type == Float.class || type == float.class); } public static boolean isMap(final Type type) { return type == Map.class; } public static boolean isSet(final Type type) { return type == Set.class; } public static boolean isList(final Type type) { return type == List.class; } public static boolean isDate(final Type type) { return is(type, Date.class, false); } public static boolean isNumber(final Type type) { if (type instanceof Class<?>) { final Class<?> clazz = (Class<?>) type; final boolean numberClass = is(clazz, Number.class, false); if (numberClass) { return true; } return type == byte.class || type == int.class || type == short.class || type == long.class || type == float.class || type == double.class; } return false; } public static boolean isByte(final Type type) { return type == byte.class || type == Byte.class; } public static boolean isByteArray(final Type type) { if (type == byte[].class || type == Byte[].class) { return true; } return false; } public static String explodeCamelCase(final String str, final boolean excludeGetOrSet) { return ApiStringUtils.explodeCamelCase(str, excludeGetOrSet); } public static int getTypeParameterCount(final Field field) { final Type genericType = field.getGenericType(); return getTypeParameterCount(genericType); } public static int getTypeParameterCount(Type genericType) { if (genericType instanceof GenericArrayType) { final GenericArrayType gaType = (GenericArrayType) genericType; genericType = gaType.getGenericComponentType(); } if (genericType instanceof ParameterizedType) { final ParameterizedType pType = (ParameterizedType) genericType; final Type[] typeArguments = pType.getActualTypeArguments(); return typeArguments.length; } return 0; } public static Class<?> getTypeParameter(final Class<?> clazz, final Class<?> genericInterface, final int parameterIndex) { final Type baseType = GenericTypeReflector.getExactSuperType(clazz, genericInterface); final ParameterizedType pBaseType = (ParameterizedType) baseType; final Type typeParameterForBaseInterface = pBaseType.getActualTypeArguments()[parameterIndex]; return getSafeClassToUse(typeParameterForBaseInterface); } public static Class<?> getTypeParameter(Type genericType, final int parameterIndex) { if (genericType instanceof GenericArrayType) { final GenericArrayType gaType = (GenericArrayType) genericType; genericType = gaType.getGenericComponentType(); } if (genericType instanceof ParameterizedType) { final ParameterizedType ptype = (ParameterizedType) genericType; final Type[] typeArguments = ptype.getActualTypeArguments(); if (typeArguments.length > parameterIndex) { final Type argument = typeArguments[parameterIndex]; return getSafeClassToUse(argument); } else { throw new IllegalArgumentException("Only " + typeArguments.length + " parameters available"); } } return null; } public static Class<?> getTypeParameter(final Field field, final int parameterIndex) { final Type genericType = field.getGenericType(); return getTypeParameter(genericType, parameterIndex); } public static boolean isWildcard(final Type type) { return type instanceof WildcardType; } private static Class<?> getSafeClassToUse(Type someType) { if (someType instanceof GenericArrayType) { final GenericArrayType gaType = (GenericArrayType) someType; someType = gaType.getGenericComponentType(); return Array.newInstance((Class<?>) someType, 0).getClass(); } if (someType instanceof WildcardType) { final WildcardType wildcardType = (WildcardType) someType; final Type[] upperBounds = wildcardType.getUpperBounds(); if (upperBounds != null && upperBounds.length > 0) { return (Class<?>) upperBounds[0]; } final Type[] lowerBounds = wildcardType.getLowerBounds(); if (lowerBounds != null && lowerBounds.length > 0) { return (Class<?>) lowerBounds[0]; } } else if (someType instanceof Class) { return (Class<?>) someType; } else if (someType instanceof ParameterizedType) { final ParameterizedType pType = (ParameterizedType) someType; return (Class<?>) pType.getRawType(); } throw new UnsupportedOperationException("Parameter type not supported: " + someType); } public static int getHierarchyDistance(final Class<?> subtype, final Class<?> supertype) throws IllegalArgumentException { assert subtype != null; assert supertype != null; if (!ReflectionUtils.is(subtype, supertype)) { throw new IllegalArgumentException( "Not a valid subtype of " + supertype.getName() + ": " + subtype.getName()); } if (supertype.isInterface()) { return getInterfaceHierarchyDistance(subtype, supertype); } else { return getClassHierarchyDistance(subtype, supertype); } } private static int getClassHierarchyDistance(final Class<?> subtype, final Class<?> supertype) { if (subtype == supertype) { return 0; } if (subtype == Object.class) { return Integer.MAX_VALUE; } final Class<?> subSuperclass = subtype.getSuperclass(); final int distance = getClassHierarchyDistance(subSuperclass, supertype); if (distance != Integer.MAX_VALUE) { return 1 + distance; } return Integer.MAX_VALUE; } private static int getInterfaceHierarchyDistance(final Class<?> subtype, final Class<?> supertype) { if (subtype == supertype) { return 0; } final Class<?>[] interfaces = subtype.getInterfaces(); for (final Class<?> i : interfaces) { if (i == supertype) { return 1; } } int bestCandidate = Integer.MAX_VALUE; if (!subtype.isInterface()) { final Class<?> subSuperclass = subtype.getSuperclass(); if (subSuperclass != null) { final int distance = getInterfaceHierarchyDistance(subSuperclass, supertype); if (distance != Integer.MAX_VALUE) { final int candidate = 1 + distance; bestCandidate = Math.min(bestCandidate, candidate); } } } for (final Class<?> i : interfaces) { final Class<?>[] subInterfaces = i.getInterfaces(); if (subInterfaces != null && subInterfaces.length > 0) { for (final Class<?> subInterface : subInterfaces) { final int distance = getInterfaceHierarchyDistance(subInterface, supertype); if (distance != Integer.MAX_VALUE) { final int candidate = 1 + distance; bestCandidate = Math.min(bestCandidate, candidate); } } } } return bestCandidate; } public static boolean isArray(final Object obj) { if (obj == null) { return false; } return obj.getClass().isArray(); } public static Method[] getMethods(final Class<?> clazz, final Class<? extends Annotation> withAnnotation) { final List<Method> result = new ArrayList<>(); final Method[] methods = getMethods(clazz); for (final Method method : methods) { if (isAnnotationPresent(method, withAnnotation)) { result.add(method); } } return result.toArray(new Method[result.size()]); } public static Field[] getAllFields(final Class<?> clazz, final Class<? extends Annotation> withAnnotation) { final List<Field> result = new ArrayList<>(); final Field[] fields = getAllFields(clazz); for (final Field field : fields) { if (isAnnotationPresent(field, withAnnotation)) { result.add(field); } } return result.toArray(new Field[result.size()]); } /** * Gets a method of a class by name. * * @param clazz * @param name * @return */ public static Method getMethod(final Class<?> clazz, final String name) { return getMethod(clazz, name, false); } /** * Gets a method of a class by name. * * @param clazz * @param name * @param withParameters * whether or not to include methods with parameters * @return */ public static Method getMethod(final Class<?> clazz, final String name, final boolean withParameters) { if (clazz == Object.class || clazz == null) { return null; } try { // first try without parameters return clazz.getDeclaredMethod(name); } catch (final SecurityException e) { throw new IllegalStateException(e); } catch (final NoSuchMethodException e) { if (withParameters) { final Method[] methods = getMethods(clazz); for (final Method method : methods) { if (name.equals(method.getName())) { return method; } } return null; } return getMethod(clazz.getSuperclass(), name, withParameters); } } public static Field getField(final Class<?> clazz, final String fieldName) { if (clazz == Object.class || clazz == null) { return null; } try { return clazz.getDeclaredField(fieldName); } catch (final SecurityException e) { throw new IllegalStateException(e); } catch (final NoSuchFieldException e) { return getField(clazz.getSuperclass(), fieldName); } } /** * Tells which approach {@link #getMethods(Class)} is being implemented with * * @return * * @deprecated since DataCleaner 5.0 we no longer support Java 7 or older, * so there is no longer a "legacy approach". This method always * returns false. */ @Deprecated public static boolean isGetMethodsLegacyApproach() { return false; } /** * Gets all methods of a class, excluding those from Object. * * @param clazz * @return */ public static Method[] getMethods(final Class<?> clazz) { final List<Method> allMethods = new ArrayList<>(); addMethods(allMethods, clazz); return allMethods.toArray(new Method[allMethods.size()]); } private static void addMethods(final List<Method> allMethods, final Class<?> clazz) { if (clazz == Object.class || clazz == null) { return; } final Method[] methods = clazz.getMethods(); for (final Method method : methods) { final Class<?> declaringClass = method.getDeclaringClass(); if (declaringClass != Object.class) { allMethods.add(method); } } } /** * Gets all fields of a class, including private fields in super-classes. * * @param clazz * @return */ public static Field[] getAllFields(final Class<?> clazz) { final List<Field> allFields = new ArrayList<>(); addFields(allFields, clazz); return allFields.toArray(new Field[allFields.size()]); } /** * Gets non-synthetic fields of a class, including private fields in * super-classes. * * @param clazz * @return */ public static Field[] getNonSyntheticFields(final Class<?> clazz) { final List<Field> fieldList = new ArrayList<>(); addFields(fieldList, clazz, true); return fieldList.toArray(new Field[fieldList.size()]); } private static void addFields(final List<Field> allFields, final Class<?> clazz) { addFields(allFields, clazz, false); } private static void addFields(final List<Field> allFields, final Class<?> clazz, final boolean excludeSynthetic) { if (clazz == Object.class) { return; } final Field[] f = clazz.getDeclaredFields(); for (final Field field : f) { if (excludeSynthetic && field.isSynthetic()) { continue; } allFields.add(field); } final Class<?> superclass = clazz.getSuperclass(); addFields(allFields, superclass, excludeSynthetic); } public static <E> E newInstance(final Class<? extends E> clazz) { try { return clazz.newInstance(); } catch (final Exception e) { logger.warn("Could not instantiate {}: {}", clazz, e); if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new IllegalStateException(e); } } public static <A extends Annotation> A getAnnotation(final Enum<?> enumConstant, final Class<A> annotationClass) { try { final Field field = enumConstant.getClass().getDeclaredField(enumConstant.name()); return getAnnotation(field, annotationClass); } catch (final Exception e) { throw new IllegalStateException(e); } } public static <A extends Annotation> A getAnnotation(final AnnotatedElement element, final Class<A> annotationClass) { synchronized (ANNOTATION_REFLECTION_LOCK) { final A annotation = element.getAnnotation(annotationClass); if (annotation == null && element instanceof Method) { // check for annotations on overridden methods. Since Java 8 // those are not returned by .getAnnotation(...) final Method m = (Method) element; final Class<?> declaringClass = m.getDeclaringClass(); final Class<?> superClass = declaringClass.getSuperclass(); final String methodName = m.getName(); final Class<?>[] methodParameterTypes = m.getParameterTypes(); if (superClass != null) { try { final Method overriddenMethod = superClass.getMethod(methodName, methodParameterTypes); return getAnnotation(overriddenMethod, annotationClass); } catch (final NoSuchMethodException e) { logger.debug("Failed to get overridden method '{}' from {}", methodName, superClass); } } // check for annotations on interface methods too. final Class<?>[] interfaces = declaringClass.getInterfaces(); for (final Class<?> interfaceClass : interfaces) { try { final Method overriddenMethod = interfaceClass.getMethod(methodName, methodParameterTypes); return getAnnotation(overriddenMethod, annotationClass); } catch (final NoSuchMethodException e) { logger.debug("Failed to get overridden method '{}' from {}", methodName, interfaceClass); } } } return annotation; } } public static boolean isAnnotationPresent(final Enum<?> enumConstant, final Class<? extends Annotation> annotationClass) { try { final Field field = enumConstant.getClass().getDeclaredField(enumConstant.name()); return isAnnotationPresent(field, annotationClass); } catch (final Exception e) { throw new IllegalStateException(e); } } public static boolean isAnnotationPresent(final AnnotatedElement element, final Class<? extends Annotation> annotationClass) { return getAnnotation(element, annotationClass) != null; } }