org.mongodb.morphia.utils.ReflectionUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of morphia Show documentation
Show all versions of morphia Show documentation
Java Object Document Mapper for MongoDB
The newest version!
/**
* Copyright (C) 2010 Olafur Gauti Gudmundsson
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package org.mongodb.morphia.utils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import org.bson.types.CodeWScope;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Key;
import org.mongodb.morphia.annotations.Embedded;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.logging.Logger;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.mapping.MappingException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
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.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
/**
* Various reflection utility methods, used mainly in the Mapper.
*
* @author Olafur Gauti Gudmundsson
*/
public final class ReflectionUtils {
private static final Logger LOG = MorphiaLoggerFactory.get(ReflectionUtils.class);
private ReflectionUtils() {
}
/**
* Get an array of all fields declared in the supplied class, and all its superclasses (except java.lang.Object).
*
* @param type the class for which we want to retrieve the Fields
* @param returnFinalFields specifies whether to return final fields
* @return an array of all declared and inherited fields
*/
public static Field[] getDeclaredAndInheritedFields(final Class type, final boolean returnFinalFields) {
final List allFields = new ArrayList();
allFields.addAll(getValidFields(type.getDeclaredFields(), returnFinalFields));
Class parent = type.getSuperclass();
while ((parent != null) && (parent != Object.class)) {
allFields.addAll(getValidFields(parent.getDeclaredFields(), returnFinalFields));
parent = parent.getSuperclass();
}
return allFields.toArray(new Field[allFields.size()]);
}
/**
* Scans the array fields and returns any fields that are not static or (optionally) final.
*
* @param fields the fields to process
* @param returnFinalFields include final fields in the results
* @return the valid fields
*/
public static List getValidFields(final Field[] fields, final boolean returnFinalFields) {
final List validFields = new ArrayList();
// we ignore static and final fields
for (final Field field : fields) {
if (!Modifier.isStatic(field.getModifiers()) && (returnFinalFields || !Modifier.isFinal(field.getModifiers()))) {
validFields.add(field);
}
}
return validFields;
}
/**
* Get a list of all methods declared in the supplied class, and all its superclasses (except java.lang.Object), recursively.
*
* @param type the class for which we want to retrieve the Methods
* @return an array of all declared and inherited fields
*/
public static List getDeclaredAndInheritedMethods(final Class type) {
return getDeclaredAndInheritedMethods(type, new ArrayList());
}
private static List getDeclaredAndInheritedMethods(final Class type, final List methods) {
if ((type == null) || (type == Object.class)) {
return methods;
}
final Class parent = type.getSuperclass();
final List list = getDeclaredAndInheritedMethods(parent, methods == null ? new ArrayList() : methods);
for (final Method m : type.getDeclaredMethods()) {
if (!Modifier.isStatic(m.getModifiers())) {
list.add(m);
}
}
return list;
}
// public static boolean implementsAnyInterface(final Class type, final Class... interfaceClasses)
// {
// for (Class iF : interfaceClasses)
// {
// if (implementsInterface(type, iF))
// {
// return true;
// }
// }
// return false;
// }
/**
* Checks if the class is an integer type, i.e., is numeric but not a floating point type.
*
* @param type the class we want to check
* @return true if the type is an integral type
*/
public static boolean isIntegerType(final Class type) {
return Arrays.asList(Integer.class, int.class, Long.class, long.class, Short.class, short.class, Byte.class,
byte.class).contains(type);
}
/**
* Check if the class supplied represents a valid property type.
*
* @param type the class we want to check
* @return true if the class represents a valid property type
*/
public static boolean isPropertyType(final Type type) {
if (type instanceof GenericArrayType) {
return isPropertyType(((GenericArrayType) type).getGenericComponentType());
}
if (type instanceof ParameterizedType) {
return isPropertyType(((ParameterizedType) type).getRawType());
}
return type instanceof Class && isPropertyType((Class) type);
}
/**
* Get the (first) class that parameterizes the Field supplied.
*
* @param field the field
* @return the class that parameterizes the field, or null if field is not parameterized
*/
public static Class getParameterizedClass(final Field field) {
return getParameterizedClass(field, 0);
}
/**
* Get the class that parameterizes the Field supplied, at the index supplied (field can be parameterized with multiple param classes).
*
* @param field the field
* @param index the index of the parameterizing class
* @return the class that parameterizes the field, or null if field is not parameterized
*/
public static Class getParameterizedClass(final Field field, final int index) {
if (field.getGenericType() instanceof ParameterizedType) {
final ParameterizedType type = (ParameterizedType) field.getGenericType();
if ((type.getActualTypeArguments() != null) && (type.getActualTypeArguments().length <= index)) {
return null;
}
final Type paramType = type.getActualTypeArguments()[index];
if (paramType instanceof GenericArrayType) {
final Class arrayType = (Class) ((GenericArrayType) paramType).getGenericComponentType();
return Array.newInstance(arrayType, 0)
.getClass();
} else {
if (paramType instanceof ParameterizedType) {
final ParameterizedType paramPType = (ParameterizedType) paramType;
return (Class) paramPType.getRawType();
} else {
if (paramType instanceof TypeVariable) {
// TODO: Figure out what to do... Walk back up the to
// the parent class and try to get the variable type
// from the T/V/X
throw new MappingException("Generic Typed Class not supported: <" + ((TypeVariable) paramType).getName() + "> = "
+ ((TypeVariable) paramType).getBounds()[0]);
} else if (paramType instanceof Class) {
return (Class) paramType;
} else {
throw new MappingException("Unknown type... pretty bad... call for help, wave your hands... yeah!");
}
}
}
}
return getParameterizedClass(field.getType());
}
/**
* Returns the parameterized type for a field
*
* @param field the field to examine
* @param index the location of the parameter to return
* @return the type
*/
public static Type getParameterizedType(final Field field, final int index) {
if (field != null) {
if (field.getGenericType() instanceof ParameterizedType) {
final ParameterizedType type = (ParameterizedType) field.getGenericType();
if ((type.getActualTypeArguments() != null) && (type.getActualTypeArguments().length <= index)) {
return null;
}
final Type paramType = type.getActualTypeArguments()[index];
if (paramType instanceof GenericArrayType) {
return paramType; //((GenericArrayType) paramType).getGenericComponentType();
} else {
if (paramType instanceof ParameterizedType) {
return paramType;
} else {
if (paramType instanceof TypeVariable) {
// TODO: Figure out what to do... Walk back up the to
// the parent class and try to get the variable type
// from the T/V/X
// throw new MappingException("Generic Typed Class not supported: <" + ((TypeVariable)
// paramType).getName() + "> = " + ((TypeVariable) paramType).getBounds()[0]);
return paramType;
} else if (paramType instanceof WildcardType) {
return paramType;
} else if (paramType instanceof Class) {
return paramType;
} else {
throw new MappingException("Unknown type... pretty bad... call for help, wave your hands... yeah!");
}
}
}
}
// Not defined on field, but may be on class or super class...
return getParameterizedClass(field.getType());
}
return null;
}
/**
* Returns the parameterized type of a Class
*
* @param c the class to examine
* @return the type
*/
public static Class getParameterizedClass(final Class c) {
return getParameterizedClass(c, 0);
}
/**
* Returns the parameterized type in the given position
*
* @param c the class to examine
* @param index the position of the type to return
* @return the type
*/
public static Class getParameterizedClass(final Class c, final int index) {
final TypeVariable[] typeVars = c.getTypeParameters();
if (typeVars.length > 0) {
final TypeVariable typeVariable = typeVars[index];
final Type[] bounds = typeVariable.getBounds();
final Type type = bounds[0];
if (type instanceof Class) {
return (Class) type; // broke for EnumSet, cause bounds contain
// type instead of class
} else {
return null;
}
} else {
Type superclass = c.getGenericSuperclass();
if (superclass == null && c.isInterface()) {
Type[] interfaces = c.getGenericInterfaces();
if (interfaces.length > 0) {
superclass = interfaces[index];
}
}
if (superclass instanceof ParameterizedType) {
final Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();
return actualTypeArguments.length > index ? (Class) actualTypeArguments[index] : null;
} else if (!Object.class.equals(superclass)) {
return getParameterizedClass((Class) superclass);
} else {
return null;
}
}
}
/**
* Check if a field is parameterized with a specific class.
*
* @param field the field
* @param c the class to check against
* @return true if the field is parameterized and c is the class that parameterizes the field, or is an interface that the parameterized
* class implements, else false
* @deprecated this class is unused in morphia and will be removed in a future release
*/
public static boolean isFieldParameterizedWithClass(final Field field, final Class c) {
if (field.getGenericType() instanceof ParameterizedType) {
final ParameterizedType genericType = (ParameterizedType) field.getGenericType();
for (final Type type : genericType.getActualTypeArguments()) {
if (type == c) {
return true;
}
if (c.isInterface() && implementsInterface((Class) type, c)) {
return true;
}
}
}
return false;
}
/**
* Check if a class implements a specific interface.
*
* @param type the class we want to check
* @param interfaceClass the interface class we want to check against
* @return true if type implements interfaceClass, else false
*/
public static boolean implementsInterface(final Class type, final Class interfaceClass) {
return interfaceClass.isAssignableFrom(type);
}
/**
* Check if the field supplied is parameterized with a valid JCR property type.
*
* @param field the field
* @return true if the field is parameterized with a valid JCR property type, else false
* @deprecated this class is unused in morphia and will be removed in a future release
*/
public static boolean isFieldParameterizedWithPropertyType(final Field field) {
if (field.getGenericType() instanceof ParameterizedType) {
final ParameterizedType genericType = (ParameterizedType) field.getGenericType();
for (final Type type : genericType.getActualTypeArguments()) {
if (isPropertyType((Class) type)) {
return true;
}
}
}
return false;
}
/**
* Checks if the Class given is a property type
*
* @param type the Class to examine
* @return true if the Class's type is considered a property type
*/
public static boolean isPropertyType(final Class type) {
return type != null && (isPrimitiveLike(type) || type == DBRef.class || type == Pattern.class
|| type == CodeWScope.class || type == ObjectId.class || type == Key.class
|| type == DBObject.class || type == BasicDBObject.class);
}
/**
* Checks if the Class given is a primitive type. This includes the Java primitive types and their wrapper types.
*
* @param type the Class to examine
* @return true if the Class's type is considered a primitive type
*/
public static boolean isPrimitiveLike(final Class type) {
return type != null && (type == String.class || type == char.class
|| type == Character.class || type == short.class || type == Short.class
|| type == Integer.class || type == int.class || type == Long.class || type == long.class
|| type == Double.class || type == double.class || type == float.class || type == Float.class
|| type == Boolean.class || type == boolean.class || type == Byte.class || type == byte.class
|| type == Date.class || type == Locale.class || type == Class.class || type == UUID.class
|| type == URI.class || type.isEnum());
}
/**
* Returns the @Embedded annotation on a Class if present
*
* @param c the class to examine
* @return the annotation. may be null.
*/
public static Embedded getClassEmbeddedAnnotation(final Class c) {
return getAnnotation(c, Embedded.class);
}
/**
* Returns an annotation on a Class if present
*
* @param c the class to examine
* @param annotation the annotation to find
* @param the type of the annotation
* @return the annotation. may be null.
*/
public static T getAnnotation(final Class c, final Class annotation) {
final List found = getAnnotations(c, annotation);
if (found != null && !found.isEmpty()) {
return found.get(0);
} else {
return null;
}
}
/**
* Returns the (first) instance of the annotation, on the class (or any superclass, or interfaces implemented).
*
* @param c the class to examine
* @param annotation the annotation to find
* @param the type of the annotation
* @return the list of annotations
*/
@SuppressWarnings("unchecked")
public static List getAnnotations(final Class c, final Class annotation) {
final List found = new ArrayList();
// TODO isn't that actually breaking the contract of @Inherited?
if (c.isAnnotationPresent(annotation)) {
found.add((T) c.getAnnotation(annotation));
}
Class parent = c.getSuperclass();
while ((parent != null) && (parent != Object.class)) {
if (parent.isAnnotationPresent(annotation)) {
found.add((T) parent.getAnnotation(annotation));
}
// ...and interfaces that the superclass implements
for (final Class interfaceClass : parent.getInterfaces()) {
if (interfaceClass.isAnnotationPresent(annotation)) {
found.add((T) interfaceClass.getAnnotation(annotation));
}
}
parent = parent.getSuperclass();
}
// ...and all implemented interfaces
for (final Class interfaceClass : c.getInterfaces()) {
if (interfaceClass.isAnnotationPresent(annotation)) {
found.add((T) interfaceClass.getAnnotation(annotation));
}
}
// no annotation found, use the defaults
return found;
}
/**
* Returns the @Entity annotation on a Class if present
*
* @param c the class to examine
* @return the annotation. may be null.
*/
public static Entity getClassEntityAnnotation(final Class c) {
return getAnnotation(c, Entity.class);
}
/**
* Returns the classes in a package
*
* @param packageName the package to scan
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getClasses(final String packageName) throws IOException, ClassNotFoundException {
return getClasses(packageName, false);
}
/**
* Returns the classes in a package
*
* @param packageName the package to scan
* @param mapSubPackages whether to map the sub-packages while scanning
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getClasses(final String packageName, final boolean mapSubPackages) throws IOException,
ClassNotFoundException {
final ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
return getClasses(loader, packageName, mapSubPackages);
}
/**
* Returns the classes in a package
*
* @param loader the ClassLoader to use
* @param packageName the package to scan
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getClasses(final ClassLoader loader, final String packageName) throws IOException, ClassNotFoundException {
return getClasses(loader, packageName, false);
}
/**
* Returns the classes in a package
*
* @param loader the ClassLoader to use
* @param packageName the package to scan
* @param mapSubPackages whether to map the sub-packages while scanning
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getClasses(final ClassLoader loader, final String packageName, final boolean mapSubPackages) throws
IOException, ClassNotFoundException {
final Set> classes = new HashSet>();
final String path = packageName.replace('.', '/');
final Enumeration resources = loader.getResources(path);
if (resources != null) {
while (resources.hasMoreElements()) {
String filePath = resources.nextElement()
.getFile();
// WINDOWS HACK
if (filePath.indexOf("%20") > 0) {
filePath = filePath.replaceAll("%20", " ");
}
// # in the jar name
if (filePath.indexOf("%23") > 0) {
filePath = filePath.replaceAll("%23", "#");
}
if (filePath != null) {
if ((filePath.indexOf("!") > 0) && (filePath.indexOf(".jar") > 0)) {
String jarPath = filePath.substring(0, filePath.indexOf("!"))
.substring(filePath.indexOf(":") + 1);
// WINDOWS HACK
if (jarPath.contains(":")) {
jarPath = jarPath.substring(1);
}
classes.addAll(getFromJARFile(loader, jarPath, path, mapSubPackages));
} else {
classes.addAll(getFromDirectory(loader, new File(filePath), packageName, mapSubPackages));
}
}
}
}
return classes;
}
/**
* Returns the classes in a package found in a jar
*
* @param loader the ClassLoader to use
* @param jar the jar to scan
* @param packageName the package to scan
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getFromJARFile(final ClassLoader loader, final String jar, final String packageName)
throws IOException, ClassNotFoundException {
return getFromJARFile(loader, jar, packageName, false);
}
/**
* Returns the classes in a package found in a jar
*
* @param loader the ClassLoader to use
* @param jar the jar to scan
* @param packageName the package to scan
* @param mapSubPackages whether to map the sub-packages while scanning
* @return the list of classes
* @throws IOException thrown if an error is encountered scanning packages
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getFromJARFile(final ClassLoader loader, final String jar, final String packageName, final boolean
mapSubPackages) throws IOException, ClassNotFoundException {
final Set> classes = new HashSet>();
final JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
try {
JarEntry jarEntry;
do {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry != null) {
String className = jarEntry.getName();
if (className.endsWith(".class")) {
String classPackageName = getPackageName(className);
if (classPackageName.equals(packageName) || (mapSubPackages && isSubPackage(classPackageName, packageName))) {
className = stripFilenameExtension(className);
classes.add(Class.forName(className.replace('/', '.'), true, loader));
}
}
}
} while (jarEntry != null);
} finally {
jarFile.close();
}
return classes;
}
/**
* Returns the classes in a package found in a directory
*
* @param loader the ClassLoader to use
* @param directory the directory to scan
* @param packageName the package to scan
* @return the list of classes
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getFromDirectory(final ClassLoader loader, final File directory, final String packageName)
throws ClassNotFoundException {
return getFromDirectory(loader, directory, packageName, false);
}
/**
* Returns the classes in a package found in a directory
*
* @param loader the ClassLoader to use
* @param directory the directory to scan
* @param packageName the package to scan
* @param mapSubPackages whether to map the sub-packages while scanning
* @return the list of classes
* @throws ClassNotFoundException thrown if a class can not be found
*/
public static Set> getFromDirectory(final ClassLoader loader, final File directory, final String packageName,
final boolean mapSubPackages) throws ClassNotFoundException {
final Set> classes = new HashSet>();
if (directory.exists()) {
for (final String file : getFileNames(directory, packageName, mapSubPackages)) {
if (file.endsWith(".class")) {
final String name = stripFilenameExtension(file);
final Class clazz = Class.forName(name, true, loader);
classes.add(clazz);
}
}
}
return classes;
}
private static Set getFileNames(final File directory, final String packageName, final boolean mapSubPackages) {
Set fileNames = new HashSet();
for (File file: directory.listFiles()) {
if (file.isFile()) {
fileNames.add(packageName + '.' + file.getName());
} else if (mapSubPackages){
fileNames.addAll(getFileNames(file, packageName + '.' + file.getName(), true));
}
}
return fileNames;
}
private static String getPackageName(final String filename) {
return filename.contains("/") ? filename.substring(0, filename.lastIndexOf('/')) : filename;
}
private static String stripFilenameExtension(final String filename) {
if (filename.indexOf('.') != -1) {
return filename.substring(0, filename.lastIndexOf('.'));
} else {
return filename;
}
}
private static boolean isSubPackage(final String fullPackageName, final String parentPackageName) {
return fullPackageName.startsWith(parentPackageName);
}
/**
* Converts an Iterable to a List
*
* @param it the Iterable
* @param the types of the elements in the Iterable
* @return the List
*/
public static List iterToList(final Iterable it) {
if (it instanceof List) {
return (List) it;
}
if (it == null) {
return null;
}
final List ar = new ArrayList();
for (final T o : it) {
ar.add(o);
}
return ar;
}
/**
* Converts a List to an array
*
* @param type the Class type of the elements of the List
* @param values the List to convert
* @return the array
*/
public static Object convertToArray(final Class type, final List values) {
final Object exampleArray = Array.newInstance(type, values.size());
try {
return values.toArray((Object[]) exampleArray);
} catch (ClassCastException e) {
for (int i = 0; i < values.size(); i++) {
Array.set(exampleArray, i, values.get(i));
}
return exampleArray;
}
}
/**
* Get the underlying class for a type, or null if the type is a variable type.
*
* @param type the type
* @return the underlying class
*/
public static Class getClass(final Type type) {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
return getClass(((ParameterizedType) type).getRawType());
} else if (type instanceof GenericArrayType) {
final Type componentType = ((GenericArrayType) type).getGenericComponentType();
final Class componentClass = getClass(componentType);
if (componentClass != null) {
return Array.newInstance(componentClass, 0).getClass();
} else {
LOG.debug("************ ReflectionUtils.getClass 1st else");
LOG.debug("************ type = " + type);
return null;
}
} else {
LOG.debug("************ ReflectionUtils.getClass final else");
LOG.debug("************ type = " + type);
return null;
}
}
/**
* Get the actual type arguments a child class has used to extend a generic base class.
*
* @param baseClass the base class
* @param childClass the child class
* @param the type of the base class
* @return a list of the raw classes for the actual type arguments.
* @deprecated this class is unused in morphia and will be removed in a future release
*/
public static List> getTypeArguments(final Class baseClass, final Class childClass) {
final Map resolvedTypes = new HashMap();
Type type = childClass;
// start walking up the inheritance hierarchy until we hit baseClass
while (!getClass(type).equals(baseClass)) {
if (type instanceof Class) {
// there is no useful information for us in raw types, so just
// keep going.
type = ((Class) type).getGenericSuperclass();
} else {
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Class rawType = (Class) parameterizedType.getRawType();
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final TypeVariable[] typeParameters = rawType.getTypeParameters();
for (int i = 0; i < actualTypeArguments.length; i++) {
resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
}
if (!rawType.equals(baseClass)) {
type = rawType.getGenericSuperclass();
}
}
}
// finally, for each actual type argument provided to baseClass,
// determine (if possible)
// the raw class for that type argument.
final Type[] actualTypeArguments;
if (type instanceof Class) {
actualTypeArguments = ((Class) type).getTypeParameters();
} else {
actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
}
final List> typeArgumentsAsClasses = new ArrayList>();
// resolve types by chasing down type variables.
for (Type baseType : actualTypeArguments) {
while (resolvedTypes.containsKey(baseType)) {
baseType = resolvedTypes.get(baseType);
}
typeArgumentsAsClasses.add(getClass(baseType));
}
return typeArgumentsAsClasses;
}
/**
* Returns the type argument
*
* @param clazz the Class to examine
* @param tv the TypeVariable to look for
* @param the type of the Class
* @return the Class type
*/
public static Class getTypeArgument(final Class clazz, final TypeVariable tv) {
final Map resolvedTypes = new HashMap();
Type type = clazz;
// start walking up the inheritance hierarchy until we hit the end
while (type != null && !Object.class.equals(getClass(type))) {
if (type instanceof Class) {
// there is no useful information for us in raw types, so just
// keep going.
type = ((Class) type).getGenericSuperclass();
} else {
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Class rawType = (Class) parameterizedType.getRawType();
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final TypeVariable[] typeParameters = rawType.getTypeParameters();
for (int i = 0; i < actualTypeArguments.length; i++) {
if (typeParameters[i].equals(tv)) {
final Class cls = getClass(actualTypeArguments[i]);
if (cls != null) {
return cls;
}
//We don't know that the type we want is the one in the map, if this argument has been
//passed through multiple levels of the hierarchy. Walk back until we run out.
Type typeToTest = resolvedTypes.get(actualTypeArguments[i]);
while (typeToTest != null) {
final Class classToTest = getClass(typeToTest);
if (classToTest != null) {
return classToTest;
}
typeToTest = resolvedTypes.get(typeToTest);
}
}
resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
}
if (!rawType.equals(Object.class)) {
type = rawType.getGenericSuperclass();
}
}
}
return null;
}
}