/**
* AnalyzerBeans
* 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.eobjects.analyzer.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.eobjects.analyzer.data.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(Type thisType, Class<?> ofThatType) {
return is(thisType, ofThatType, true);
}
public static boolean is(Type thisType, Class<?> ofThatType, 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(Type type) {
return (type == char.class || type == Character.class);
}
public static boolean isInputColumn(Class<?> type) {
return is(type, InputColumn.class);
}
public static boolean isColumn(Class<?> type) {
return is(type, Column.class);
}
public static boolean isTable(Class<?> type) {
return is(type, Table.class);
}
public static boolean isSchema(Class<?> type) {
return is(type, Schema.class);
}
public static boolean isCloseable(Class<?> type) {
return is(type, Closeable.class);
}
public static boolean isBoolean(Type type) {
return (type == Boolean.class || type == boolean.class);
}
public static boolean isString(Type type) {
return is(type, String.class);
}
public static boolean isShort(Type type) {
return (type == Short.class || type == short.class);
}
public static boolean isDouble(Type type) {
return (type == Double.class || type == double.class);
}
public static boolean isLong(Type type) {
return (type == Long.class || type == long.class);
}
public static boolean isInteger(Type type) {
return (type == Integer.class || type == int.class);
}
public static boolean isFloat(Type type) {
return (type == Float.class || type == float.class);
}
public static boolean isMap(Type type) {
return type == Map.class;
}
public static boolean isSet(Type type) {
return type == Set.class;
}
public static boolean isList(Type type) {
return type == List.class;
}
public static boolean isDate(Type type) {
return is(type, Date.class, false);
}
public static boolean isNumber(Type type) {
if (type instanceof Class<?>) {
Class<?> clazz = (Class<?>) type;
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(Type type) {
return type == byte.class || type == Byte.class;
}
public static boolean isByteArray(Type type) {
if (type == byte[].class || type == Byte[].class) {
return true;
}
return false;
}
public static String explodeCamelCase(String str, boolean excludeGetOrSet) {
if (str == null) {
return "";
}
StringBuilder sb = new StringBuilder(str.trim());
if (sb.length() > 1) {
if (excludeGetOrSet) {
if (str.startsWith("get") || str.startsWith("set")) {
sb.delete(0, 3);
}
}
// Special handling for instance variables that have the "_" prefix
if (sb.charAt(0) == '_') {
sb.deleteCharAt(0);
}
// First character is set to uppercase
sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
boolean previousUpperCase = true;
for (int i = 1; i < sb.length(); i++) {
char currentChar = sb.charAt(i);
if (!previousUpperCase) {
if (Character.isUpperCase(currentChar)) {
sb.setCharAt(i, Character.toLowerCase(currentChar));
sb.insert(i, ' ');
i++;
}
} else {
if (Character.isLowerCase(currentChar)) {
previousUpperCase = false;
}
}
}
}
return sb.toString();
}
public static int getTypeParameterCount(Field field) {
Type genericType = field.getGenericType();
return getTypeParameterCount(genericType);
}
public static int getTypeParameterCount(Type genericType) {
if (genericType instanceof GenericArrayType) {
GenericArrayType gaType = (GenericArrayType) genericType;
genericType = gaType.getGenericComponentType();
}
if (genericType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) genericType;
Type[] typeArguments = pType.getActualTypeArguments();
return typeArguments.length;
}
return 0;
}
public static Class<?> getTypeParameter(Class<?> clazz, Class<?> genericInterface, int parameterIndex) {
Type baseType = GenericTypeReflector.getExactSuperType(clazz, genericInterface);
ParameterizedType pBaseType = (ParameterizedType) baseType;
Type typeParameterForBaseInterface = pBaseType.getActualTypeArguments()[parameterIndex];
return getSafeClassToUse(typeParameterForBaseInterface);
}
public static Class<?> getTypeParameter(Type genericType, int parameterIndex) {
if (genericType instanceof GenericArrayType) {
GenericArrayType gaType = (GenericArrayType) genericType;
genericType = gaType.getGenericComponentType();
}
if (genericType instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) genericType;
Type[] typeArguments = ptype.getActualTypeArguments();
if (typeArguments.length > parameterIndex) {
Type argument = typeArguments[parameterIndex];
return getSafeClassToUse(argument);
} else {
throw new IllegalArgumentException("Only " + typeArguments.length + " parameters available");
}
}
return null;
}
public static Class<?> getTypeParameter(Field field, int parameterIndex) {
Type genericType = field.getGenericType();
return getTypeParameter(genericType, parameterIndex);
}
public static boolean isWildcard(Type type) {
return type instanceof WildcardType;
}
private static Class<?> getSafeClassToUse(Type someType) {
if (someType instanceof GenericArrayType) {
GenericArrayType gaType = (GenericArrayType) someType;
someType = gaType.getGenericComponentType();
return Array.newInstance((Class<?>) someType, 0).getClass();
}
if (someType instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) someType;
Type[] upperBounds = wildcardType.getUpperBounds();
if (upperBounds != null && upperBounds.length > 0) {
return (Class<?>) upperBounds[0];
}
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) {
ParameterizedType pType = (ParameterizedType) someType;
return (Class<?>) pType.getRawType();
}
throw new UnsupportedOperationException("Parameter type not supported: " + someType);
}
public static int getHierarchyDistance(Class<?> subtype, Class<?> supertype) {
assert subtype != null;
assert supertype != null;
if (subtype == supertype) {
return 0;
}
if (!ReflectionUtils.is(subtype, supertype)) {
throw new IllegalArgumentException("Not a valid subtype of " + supertype.getName() + ": "
+ subtype.getName());
}
Class<?> subSuperclass = subtype.getSuperclass();
if (subSuperclass != Object.class) {
return 1 + getHierarchyDistance(subSuperclass, supertype);
}
if (supertype.isInterface()) {
return getInterfaceHierarchyDistance(subtype, supertype);
} else {
return 1 + getHierarchyDistance(subSuperclass, supertype);
}
}
private static int getInterfaceHierarchyDistance(Class<?> subtype, Class<?> supertype) {
if (subtype == supertype) {
return 0;
}
Class<?>[] interfaces = subtype.getInterfaces();
for (Class<?> i : interfaces) {
if (i == supertype) {
return 1;
}
}
for (Class<?> i : interfaces) {
Class<?>[] subInterfaces = i.getInterfaces();
if (subInterfaces != null && subInterfaces.length > 0) {
for (Class<?> subInterface : subInterfaces) {
int result = getInterfaceHierarchyDistance(subInterface, supertype);
if (result != -1) {
return 1 + result;
}
}
}
}
return -1;
}
public static boolean isArray(Object o) {
if (o == null) {
return false;
}
return o.getClass().isArray();
}
public static Method[] getMethods(Class<?> clazz, Class<? extends Annotation> withAnnotation) {
List<Method> result = new ArrayList<Method>();
Method[] methods = getMethods(clazz);
for (Method method : methods) {
if (isAnnotationPresent(method, withAnnotation)) {
result.add(method);
}
}
return result.toArray(new Method[result.size()]);
}
public static Field[] getFields(Class<?> clazz, Class<? extends Annotation> withAnnotation) {
List<Field> result = new ArrayList<Field>();
Field[] fields = getFields(clazz);
for (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(Class<?> clazz, 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(Class<?> clazz, String name, boolean withParameters) {
if (clazz == Object.class || clazz == null) {
return null;
}
try {
// first try without parameters
Method method = clazz.getDeclaredMethod(name);
return method;
} catch (SecurityException e) {
throw new IllegalStateException(e);
} catch (NoSuchMethodException e) {
if (withParameters) {
Method[] methods = getMethods(clazz);
for (Method method : methods) {
if (name.equals(method.getName())) {
return method;
}
}
return null;
}
return getMethod(clazz.getSuperclass(), name, withParameters);
}
}
public static Field getField(Class<?> clazz, String fieldName) {
if (clazz == Object.class || clazz == null) {
return null;
}
try {
return clazz.getDeclaredField(fieldName);
} catch (SecurityException e) {
throw new IllegalStateException(e);
} catch (NoSuchFieldException e) {
return getField(clazz.getSuperclass(), fieldName);
}
}
/**
* Tells which approach {@link #getMethods(Class)} is being implemented with
*
* @return
*/
public static boolean isGetMethodsLegacyApproach() {
final String version = SystemProperties.getString("java.version", "");
final boolean legacyApproach = version.startsWith("1.7");
return legacyApproach;
}
/**
* Gets all methods of a class, excluding those from Object.
*
* Warning: This method's result varies slightly on Java 7 and on Java 8.
* With Java 8 overridden methods are properly removed from the result, and
* inherited annotations thus also available from the result. On Java 7,
* overridden methods will be returned multiple times.
*
* @see #isGetMethodsLegacyApproach()
*
* @param clazz
* @return
*/
public static Method[] getMethods(Class<?> clazz) {
final boolean legacyApproach = isGetMethodsLegacyApproach();
final List<Method> allMethods = new ArrayList<>();
addMethods(allMethods, clazz, legacyApproach);
return allMethods.toArray(new Method[allMethods.size()]);
}
private static void addMethods(final List<Method> allMethods, final Class<?> clazz, final boolean legacyApproach) {
if (clazz == Object.class || clazz == null) {
return;
}
if (legacyApproach) {
// in java 7 and previous, getDeclaredMethods() is used and a
// recursive call is applied throughout the hierarchy
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
allMethods.add(method);
}
final Class<?> superclass = clazz.getSuperclass();
addMethods(allMethods, superclass, legacyApproach);
} else {
// since java 8, getMethods() returns all relevant methods
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[] getFields(Class<?> clazz) {
List<Field> allFields = new ArrayList<>();
addFields(allFields, clazz);
return allFields.toArray(new Field[allFields.size()]);
}
private static void addFields(List<Field> allFields, Class<?> clazz) {
if (clazz == Object.class) {
return;
}
Field[] f = clazz.getDeclaredFields();
for (Field field : f) {
allFields.add(field);
}
Class<?> superclass = clazz.getSuperclass();
addFields(allFields, superclass);
}
public static <E> E newInstance(Class<? extends E> clazz) {
try {
return clazz.newInstance();
} catch (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(Enum<?> enumConstant, Class<A> annotationClass) {
try {
Field field = enumConstant.getClass().getDeclaredField(enumConstant.name());
return getAnnotation(field, annotationClass);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static <A extends Annotation> A getAnnotation(AnnotatedElement element, Class<A> annotationClass) {
synchronized (ANNOTATION_REFLECTION_LOCK) {
A annotation = element.getAnnotation(annotationClass);
return annotation;
}
}
public static boolean isAnnotationPresent(Enum<?> enumConstant, Class<? extends Annotation> annotationClass) {
try {
Field field = enumConstant.getClass().getDeclaredField(enumConstant.name());
return isAnnotationPresent(field, annotationClass);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static boolean isAnnotationPresent(AnnotatedElement element, Class<? extends Annotation> annotationClass) {
synchronized (ANNOTATION_REFLECTION_LOCK) {
return element.isAnnotationPresent(annotationClass);
}
}
}