/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.fragment.common;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
/**
* Enhanced type resolution utilities. Originally based on
* org.springframework.core.GenericTypeResolver.
*
* @author Jonathan Halterman
*/
public final class TypeResolver {
private TypeResolver() {
}
/** An unknown type. */
public static final class Unknown {
private Unknown() {
}
}
/** Cache of type variable/argument pairs */
private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> typeVariableCache = Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>());
private static boolean cacheEnabled = true;
/**
* Enables the internal caching of resolved TypeVariables.
*/
public static void enableCache() {
cacheEnabled = true;
}
/**
* Disables the internal caching of resolved TypeVariables.
*/
public static void disableCache() {
typeVariableCache.clear();
cacheEnabled = false;
}
/**
* Returns the raw class representing the type argument for the {@code type} using type variable
* information from the {@code subType}. If no arguments can be resolved then
* {@code Unknown.class} is returned.
*
* @param type to resolve argument for
* @param subType to extract type variable information from
* @return type argument for {@code type} else {@link Unknown.class} if no type arguments are
* declared
* @throws IllegalArgumentException if more or less than one type argument is resolved for the
* {@code type}
*/
public static <T, S extends T> Class<?> resolveRawArgument(Class<T> type, Class<S> subType) {
return resolveRawArgument(resolveGenericType(type, subType), subType);
}
/**
* Returns the raw class representing the type argument for the {@code genericType} using type
* variable information from the {@code subType}. If {@code genericType} is an instance of class,
* then {@code genericType} is returned. If no arguments can be resolved then
* {@code Unknown.class} is returned.
*
* @param genericType to resolve argument for
* @param subType to extract type variable information from
* @return type argument for {@code genericType} else {@link Unknown.class} if no type arguments
* are declared
* @throws IllegalArgumentException if more or less than one type argument is resolved for the
* {@code genericType}
*/
public static Class<?> resolveRawArgument(Type genericType, Class<?> subType) {
Class<?>[] arguments = resolveRawArguments(genericType, subType);
if (arguments == null)
return Unknown.class;
if (arguments.length != 1)
throw new IllegalArgumentException("Expected 1 type argument on generic type " + genericType
+ " but found " + arguments.length);
return arguments[0];
}
/**
* Returns an array of raw classes representing type arguments for the {@code type} using type
* variable information from the {@code subType}. Arguments for {@code type} that cannot be
* resolved are returned as {@code Unknown.class}. If no arguments can be resolved then
* {@code null} is returned.
*
* @param type to resolve arguments for
* @param subType to extract type variable information from
* @return array of raw classes representing type arguments for the {@code type} else {@code null}
* if no type arguments are declared
*/
public static <T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Class<S> subType) {
return resolveRawArguments(resolveGenericType(type, subType), subType);
}
/**
* Returns an array of raw classes representing type arguments for the {@code genericType} using
* type variable information from the {@code subType}. Arguments for {@code genericType} that
* cannot be resolved are returned as {@code Unknown.class}. If no arguments can be resolved then
* {@code null} is returned.
*
* @param genericType to resolve arguments for
* @param subType to extract type variable information from
* @return array of raw classes representing type arguments for the {@code genericType} else
* {@code null} if no type arguments are declared
*/
public static Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
Class<?>[] result = null;
if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] arguments = paramType.getActualTypeArguments();
result = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++)
result[i] = resolveRawClass(arguments[i], subType);
} else if (genericType instanceof TypeVariable) {
result = new Class[1];
result[0] = resolveRawClass(genericType, subType);
}
return result;
}
/**
* Returns the generic {@code type} using type variable information from the {@code subType} else
* {@code null} if the generic type cannot be resolved.
*
* @param type to resolve generic type for
* @param subType to extract type variable information from
* @return generic {@code type} else {@code null} if it cannot be resolved
*/
public static Type resolveGenericType(Class<?> type, Type subType) {
Class<?> rawType;
if (subType instanceof ParameterizedType)
rawType = (Class<?>) ((ParameterizedType) subType).getRawType();
else
rawType = (Class<?>) subType;
if (type.equals(rawType))
return subType;
Type result;
if (type.isInterface()) {
for (Type superInterface : rawType.getGenericInterfaces())
if (superInterface != null && !superInterface.equals(Object.class))
if ((result = resolveGenericType(type, superInterface)) != null)
return result;
}
Type superClass = rawType.getGenericSuperclass();
if (superClass != null && !superClass.equals(Object.class))
if ((result = resolveGenericType(type, superClass)) != null)
return result;
return null;
}
/**
* Resolves the raw class for the {@code genericType}, using the type variable information from
* the {@code subType} else {@link Unknown} if the raw class cannot be resolved.
*
* @param type to resolve raw class for
* @param subType to extract type variable information from
* @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved
*/
public static Class<?> resolveRawClass(Type genericType, Class<?> subType) {
if (genericType instanceof Class) {
return (Class<?>) genericType;
} else if (genericType instanceof ParameterizedType) {
return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType);
} else if (genericType instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) genericType;
Class<?> compoment = resolveRawClass(arrayType.getGenericComponentType(), subType);
return Array.newInstance(compoment, 0).getClass();
} else if (genericType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) genericType;
genericType = getTypeVariableMap(subType).get(variable);
genericType = genericType == null ? resolveBound(variable) : resolveRawClass(genericType,
subType);
}
return genericType instanceof Class ? (Class<?>) genericType : Unknown.class;
}
private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType) {
Reference<Map<TypeVariable<?>, Type>> ref = typeVariableCache.get(targetType);
Map<TypeVariable<?>, Type> map = ref != null ? ref.get() : null;
if (map == null) {
map = new HashMap<TypeVariable<?>, Type>();
// Populate interfaces
buildTypeVariableMap(targetType.getGenericInterfaces(), map);
// Populate super classes and interfaces
Type genericType = targetType.getGenericSuperclass();
Class<?> type = targetType.getSuperclass();
while (type != null && !Object.class.equals(type)) {
if (genericType instanceof ParameterizedType)
buildTypeVariableMap((ParameterizedType) genericType, map);
buildTypeVariableMap(type.getGenericInterfaces(), map);
genericType = type.getGenericSuperclass();
type = type.getSuperclass();
}
// Populate enclosing classes
type = targetType;
while (type.isMemberClass()) {
genericType = type.getGenericSuperclass();
if (genericType instanceof ParameterizedType)
buildTypeVariableMap((ParameterizedType) genericType, map);
type = type.getEnclosingClass();
}
if (cacheEnabled)
typeVariableCache.put(targetType, new WeakReference<Map<TypeVariable<?>, Type>>(map));
}
return map;
}
/**
* Populates the {@code map} with with variable/argument pairs for the given {@code types}.
*/
private static void buildTypeVariableMap(final Type[] types, final Map<TypeVariable<?>, Type> map) {
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
buildTypeVariableMap(parameterizedType, map);
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class)
buildTypeVariableMap(((Class<?>) rawType).getGenericInterfaces(), map);
} else if (type instanceof Class) {
buildTypeVariableMap(((Class<?>) type).getGenericInterfaces(), map);
}
}
}
/**
* Populates the {@code typeVariableMap} with type arguments and parameters for the given
* {@code type}.
*/
private static void buildTypeVariableMap(ParameterizedType type,
Map<TypeVariable<?>, Type> typeVariableMap) {
if (type.getRawType() instanceof Class) {
TypeVariable<?>[] typeVariables = ((Class<?>) type.getRawType()).getTypeParameters();
Type[] typeArguments = type.getActualTypeArguments();
for (int i = 0; i < typeArguments.length; i++) {
TypeVariable<?> variable = typeVariables[i];
Type typeArgument = typeArguments[i];
if (typeArgument instanceof Class) {
typeVariableMap.put(variable, typeArgument);
} else if (typeArgument instanceof GenericArrayType) {
typeVariableMap.put(variable, typeArgument);
} else if (typeArgument instanceof ParameterizedType) {
typeVariableMap.put(variable, typeArgument);
} else if (typeArgument instanceof TypeVariable) {
TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument;
Type resolvedType = typeVariableMap.get(typeVariableArgument);
if (resolvedType == null)
resolvedType = resolveBound(typeVariableArgument);
typeVariableMap.put(variable, resolvedType);
}
}
}
}
/**
* Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none
* can be resolved.
*/
public static Type resolveBound(TypeVariable<?> typeVariable) {
Type[] bounds = typeVariable.getBounds();
if (bounds.length == 0)
return Unknown.class;
Type bound = bounds[0];
if (bound instanceof TypeVariable)
bound = resolveBound((TypeVariable<?>) bound);
return bound == Object.class ? Unknown.class : bound;
}
}