/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.common.util.reflections;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;
/**
* Utility class for Types
*/
public class Types {
private Types() {
}
/**
* Gets the boxed type of a class
*
* @param type The type
*
* @return The boxed type
*/
public static Type boxedType(Type type) {
if (type instanceof Class<?>) {
return boxedClass((Class<?>) type);
} else {
return type;
}
}
public static Class<?> boxedClass(Class<?> type) {
if (!type.isPrimitive()) {
return type;
} else if (type.equals(Boolean.TYPE)) {
return Boolean.class;
} else if (type.equals(Character.TYPE)) {
return Character.class;
} else if (type.equals(Byte.TYPE)) {
return Byte.class;
} else if (type.equals(Short.TYPE)) {
return Short.class;
} else if (type.equals(Integer.TYPE)) {
return Integer.class;
} else if (type.equals(Long.TYPE)) {
return Long.class;
} else if (type.equals(Float.TYPE)) {
return Float.class;
} else if (type.equals(Double.TYPE)) {
return Double.class;
} else if (type.equals(Void.TYPE)) {
return Void.class;
} else {
// Vagaries of if/else statement, can't be reached ;-)
return type;
}
}
/**
* Is the genericType of a certain class?
*/
public static boolean isA(Class clazz, ParameterizedType pType)
{
return clazz.isAssignableFrom((Class) pType.getRawType());
}
/**
* Gets the index-th type argument.
*/
public static Class getArgumentType(ParameterizedType pType, int index)
{
return (Class) pType.getActualTypeArguments()[index];
}
public static Class getTemplateParameterOfInterface(Class base, Class desiredInterface)
{
Object rtn = searchForInterfaceTemplateParameter(base, desiredInterface);
if (rtn != null && rtn instanceof Class) return (Class) rtn;
return null;
}
private static Object searchForInterfaceTemplateParameter(Class base, Class desiredInterface)
{
for (int i = 0; i < base.getInterfaces().length; i++)
{
Class intf = base.getInterfaces()[i];
if (intf.equals(desiredInterface))
{
Type generic = base.getGenericInterfaces()[i];
if (generic instanceof ParameterizedType)
{
ParameterizedType p = (ParameterizedType) generic;
Type type = p.getActualTypeArguments()[0];
Class rtn = getRawTypeNoException(type);
if (rtn != null) return rtn;
return type;
}
else
{
return null;
}
}
}
if (base.getSuperclass() == null || base.getSuperclass().equals(Object.class)) return null;
Object rtn = searchForInterfaceTemplateParameter(base.getSuperclass(), desiredInterface);
if (rtn == null || rtn instanceof Class) return rtn;
if (!(rtn instanceof TypeVariable)) return null;
String name = ((TypeVariable) rtn).getName();
int index = -1;
TypeVariable[] variables = base.getSuperclass().getTypeParameters();
if (variables == null || variables.length < 1) return null;
for (int i = 0; i < variables.length; i++)
{
if (variables[i].getName().equals(name)) index = i;
}
if (index == -1) return null;
Type genericSuperclass = base.getGenericSuperclass();
if (!(genericSuperclass instanceof ParameterizedType)) return null;
ParameterizedType pt = (ParameterizedType) genericSuperclass;
Type type = pt.getActualTypeArguments()[index];
Class clazz = getRawTypeNoException(type);
if (clazz != null) return clazz;
return type;
}
/**
* See if the two methods are compatible, that is they have the same relative signature
*
* @param method
* @param intfMethod
* @return
*/
public static boolean isCompatible(Method method, Method intfMethod)
{
if (method == intfMethod) return true;
if (!method.getName().equals(intfMethod.getName())) return false;
if (method.getParameterTypes().length != intfMethod.getParameterTypes().length) return false;
for (int i = 0; i < method.getParameterTypes().length; i++)
{
Class rootParam = method.getParameterTypes()[i];
Class intfParam = intfMethod.getParameterTypes()[i];
if (!intfParam.isAssignableFrom(rootParam)) return false;
}
return true;
}
/**
* Given a method and a root class, find the actual method declared in the root that implements the method.
*
* @param clazz
* @param intfMethod
* @return
*/
public static Method getImplementingMethod(Class clazz, Method intfMethod)
{
Class<?> declaringClass = intfMethod.getDeclaringClass();
if (declaringClass.equals(clazz)) return intfMethod;
Class[] paramTypes = intfMethod.getParameterTypes();
if (declaringClass.getTypeParameters().length > 0 && paramTypes.length > 0)
{
Type[] intfTypes = findParameterizedTypes(clazz, declaringClass);
Map<String, Type> typeVarMap = new HashMap<String, Type>();
TypeVariable<? extends Class<?>>[] vars = declaringClass.getTypeParameters();
for (int i = 0; i < vars.length; i++)
{
if (intfTypes != null && i < intfTypes.length)
{
typeVarMap.put(vars[i].getName(), intfTypes[i]);
}
else
{
// Interface type parameters may not have been filled out
typeVarMap.put(vars[i].getName(), vars[i].getGenericDeclaration());
}
}
Type[] paramGenericTypes = intfMethod.getGenericParameterTypes();
paramTypes = new Class[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
{
if (paramGenericTypes[i] instanceof TypeVariable)
{
TypeVariable tv = (TypeVariable)paramGenericTypes[i];
Type t = typeVarMap.get(tv.getName());
if (t == null)
{
throw new RuntimeException("Unable to resolve type variable");
}
paramTypes[i] = getRawType(t);
}
else
{
paramTypes[i] = getRawType(paramGenericTypes[i]);
}
}
}
try
{
return clazz.getMethod(intfMethod.getName(), paramTypes);
}
catch (NoSuchMethodException e)
{
}
try
{
Method tmp = clazz.getMethod(intfMethod.getName(), intfMethod.getParameterTypes());
return tmp;
}
catch (NoSuchMethodException e)
{
}
return intfMethod;
}
public static Class<?> getRawType(Type type)
{
if (type instanceof Class<?>)
{
// type is a normal class.
return (Class<?>) type;
}
else if (type instanceof ParameterizedType)
{
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
return (Class<?>) rawType;
}
else if (type instanceof GenericArrayType)
{
final GenericArrayType genericArrayType = (GenericArrayType) type;
final Class<?> componentRawType = getRawType(genericArrayType.getGenericComponentType());
return Array.newInstance(componentRawType, 0).getClass();
}
else if (type instanceof TypeVariable)
{
final TypeVariable typeVar = (TypeVariable) type;
if (typeVar.getBounds() != null && typeVar.getBounds().length > 0)
{
return getRawType(typeVar.getBounds()[0]);
}
}
throw new RuntimeException("unable to determine base class");
}
public static Class<?> getRawTypeNoException(Type type)
{
if (type instanceof Class<?>)
{
// type is a normal class.
return (Class<?>) type;
}
else if (type instanceof ParameterizedType)
{
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
return (Class<?>) rawType;
}
else if (type instanceof GenericArrayType)
{
final GenericArrayType genericArrayType = (GenericArrayType) type;
final Class<?> componentRawType = getRawType(genericArrayType.getGenericComponentType());
return Array.newInstance(componentRawType, 0).getClass();
}
return null;
}
/**
* Returns the type argument from a parameterized type
*
* @param genericType
* @return null if there is no type parameter
*/
public static Class<?> getTypeArgument(Type genericType)
{
if (!(genericType instanceof ParameterizedType)) return null;
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Class<?> typeArg = (Class<?>) parameterizedType.getActualTypeArguments()[0];
return typeArg;
}
public static Class getCollectionBaseType(Class type, Type genericType)
{
if (genericType instanceof ParameterizedType)
{
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type componentGenericType = parameterizedType.getActualTypeArguments()[0];
return getRawType(componentGenericType);
}
else if (genericType instanceof GenericArrayType)
{
final GenericArrayType genericArrayType = (GenericArrayType) genericType;
Type componentGenericType = genericArrayType.getGenericComponentType();
return getRawType(componentGenericType);
}
else if (type.isArray())
{
return type.getComponentType();
}
return null;
}
public static Class getMapKeyType(Type genericType)
{
if (genericType instanceof ParameterizedType)
{
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type componentGenericType = parameterizedType.getActualTypeArguments()[0];
return getRawType(componentGenericType);
}
return null;
}
public static Class getMapValueType(Type genericType)
{
if (genericType instanceof ParameterizedType)
{
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type componentGenericType = parameterizedType.getActualTypeArguments()[1];
return getRawType(componentGenericType);
}
return null;
}
public static Type resolveTypeVariables(Class<?> root, Type type)
{
if (type instanceof TypeVariable)
{
Type newType = resolveTypeVariable(root, (TypeVariable)type);
return (newType == null) ? type : newType;
}
else if (type instanceof ParameterizedType)
{
final ParameterizedType param = (ParameterizedType)type;
final Type[] actuals = new Type[param.getActualTypeArguments().length];
for (int i = 0; i < actuals.length; i++)
{
Type newType = resolveTypeVariables(root, param.getActualTypeArguments()[i]);
actuals[i] = newType == null ? param.getActualTypeArguments()[i] : newType;
}
return new ParameterizedType() {
@Override
public Type[] getActualTypeArguments()
{
return actuals;
}
@Override
public Type getRawType()
{
return param.getRawType();
}
@Override
public Type getOwnerType()
{
return param.getOwnerType();
}
};
}
else if (type instanceof GenericArrayType)
{
GenericArrayType arrayType = (GenericArrayType)type;
final Type componentType = resolveTypeVariables(root, arrayType.getGenericComponentType());
if (componentType == null) return type;
return new GenericArrayType()
{
@Override
public Type getGenericComponentType()
{
return componentType;
}
};
}
else
{
return type;
}
}
/**
* Finds an actual value of a type variable. The method looks in a class hierarchy for a class defining the variable
* and returns the value if present.
*
* @param root
* @param typeVariable
* @return actual type of the type variable
*/
public static Type resolveTypeVariable(Class<?> root, TypeVariable<?> typeVariable)
{
if (typeVariable.getGenericDeclaration() instanceof Class<?>)
{
Class<?> classDeclaringTypeVariable = (Class<?>) typeVariable.getGenericDeclaration();
Type[] types = findParameterizedTypes(root, classDeclaringTypeVariable);
if (types == null) return null;
for (int i = 0; i < types.length; i++)
{
TypeVariable<?> tv = classDeclaringTypeVariable.getTypeParameters()[i];
if (tv.equals(typeVariable))
{
return types[i];
}
}
}
return null;
}
/**
* Given a class and an interfaces, go through the class hierarchy to find the interface and return its type arguments.
*
* @param classToSearch
* @param interfaceToFind
* @return type arguments of the interface
*/
public static Type[] getActualTypeArgumentsOfAnInterface(Class<?> classToSearch, Class<?> interfaceToFind)
{
Type[] types = findParameterizedTypes(classToSearch, interfaceToFind);
if (types == null) throw new RuntimeException("Unable to find type arguments");
return types;
}
private static final Type[] EMPTY_TYPE_ARRAY = {};
/**
* Search for the given interface or class within the root's class/interface hierarchy.
* If the searched for class/interface is a generic return an array of real types that fill it out.
*
* @param root
* @param searchedFor
* @return
*/
public static Type[] findParameterizedTypes(Class<?> root, Class<?> searchedFor)
{
if (searchedFor.isInterface())
{
return findInterfaceParameterizedTypes(root, null, searchedFor);
}
return findClassParameterizedTypes(root, null, searchedFor);
}
public static Type[] findClassParameterizedTypes(Class<?> root, ParameterizedType rootType, Class<?> searchedForClass)
{
if (Object.class.equals(root)) return null;
Map<String, Type> typeVarMap = populateParameterizedMap(root, rootType);
Class<?> superclass = root.getSuperclass();
Type genericSuper = root.getGenericSuperclass();
if (superclass.equals(searchedForClass))
{
return extractTypes(typeVarMap, genericSuper);
}
if (genericSuper instanceof ParameterizedType)
{
ParameterizedType intfParam = (ParameterizedType) genericSuper;
Type[] types = findClassParameterizedTypes(superclass, intfParam, searchedForClass);
if (types != null)
{
return extractTypeVariables(typeVarMap, types);
}
}
else
{
Type[] types = findClassParameterizedTypes(superclass, null, searchedForClass);
if (types != null)
{
return types;
}
}
return null;
}
private static Map<String, Type> populateParameterizedMap(Class<?> root, ParameterizedType rootType)
{
Map<String, Type> typeVarMap = new HashMap<String, Type>();
if (rootType != null)
{
TypeVariable<? extends Class<?>>[] vars = root.getTypeParameters();
for (int i = 0; i < vars.length; i++)
{
typeVarMap.put(vars[i].getName(), rootType.getActualTypeArguments()[i]);
}
}
return typeVarMap;
}
public static Type[] findInterfaceParameterizedTypes(Class<?> root, ParameterizedType rootType, Class<?> searchedForInterface)
{
Map<String, Type> typeVarMap = populateParameterizedMap(root, rootType);
for (int i = 0; i < root.getInterfaces().length; i++)
{
Class<?> sub = root.getInterfaces()[i];
Type genericSub = root.getGenericInterfaces()[i];
if (sub.equals(searchedForInterface))
{
return extractTypes(typeVarMap, genericSub);
}
}
for (int i = 0; i < root.getInterfaces().length; i++)
{
Type genericSub = root.getGenericInterfaces()[i];
Class<?> sub = root.getInterfaces()[i];
Type[] types = recurseSuperclassForInterface(searchedForInterface, typeVarMap, genericSub, sub);
if (types != null) return types;
}
if (root.isInterface()) return null;
Class<?> superclass = root.getSuperclass();
Type genericSuper = root.getGenericSuperclass();
return recurseSuperclassForInterface(searchedForInterface, typeVarMap, genericSuper, superclass);
}
private static Type[] recurseSuperclassForInterface(Class<?> searchedForInterface, Map<String, Type> typeVarMap, Type genericSub, Class<?> sub)
{
if (genericSub instanceof ParameterizedType)
{
ParameterizedType intfParam = (ParameterizedType) genericSub;
Type[] types = findInterfaceParameterizedTypes(sub, intfParam, searchedForInterface);
if (types != null)
{
return extractTypeVariables(typeVarMap, types);
}
}
else
{
Type[] types = findInterfaceParameterizedTypes(sub, null, searchedForInterface);
if (types != null)
{
return types;
}
}
return null;
}
private static Type[] extractTypeVariables(Map<String, Type> typeVarMap, Type[] types)
{
for (int j = 0; j < types.length; j++)
{
if (types[j] instanceof TypeVariable)
{
TypeVariable tv = (TypeVariable) types[j];
types[j] = typeVarMap.get(tv.getName());
}
else
{
types[j] = types[j];
}
}
return types;
}
private static Type[] extractTypes(Map<String, Type> typeVarMap, Type genericSub)
{
if (genericSub instanceof ParameterizedType)
{
ParameterizedType param = (ParameterizedType) genericSub;
Type[] types = param.getActualTypeArguments();
Type[] returnTypes = new Type[types.length];
System.arraycopy(types, 0, returnTypes, 0, types.length);
extractTypeVariables(typeVarMap, returnTypes);
return returnTypes;
}
else
{
return EMPTY_TYPE_ARRAY;
}
}
/**
* Grabs the parameterized type of fromInterface
* that object implements and sees if it is assignable from type.
*
* @param type
* @param object
* @param fromInterface
* @param <T>
* @return
*/
public static <T> boolean supports(Class<T> type, Object object, Class<?> fromInterface) {
Type providerType = getActualTypeArgumentsOfAnInterface(object.getClass(), fromInterface)[0];
Class providerClass = getRawType(providerType);
return type.isAssignableFrom(providerClass);
}
}