org.springframework.core.GenericTypeResolver Maven / Gradle / Ivy
/*
* Copyright 2002-2018 the original author or authors.
*
* 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.springframework.core;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Helper class for resolving generic types against type variables.
*
* Mainly intended for usage within the framework, resolving method
* parameter types even when they are declared generically.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Sam Brannen
* @author Phillip Webb
* @since 2.5.2
*/
public abstract class GenericTypeResolver {
/** Cache from Class to TypeVariable Map */
@SuppressWarnings("rawtypes")
private static final Map, Map> typeVariableCache =
new ConcurrentReferenceHashMap, Map>();
/**
* Determine the target type for the given parameter specification.
* @param methodParameter the method parameter specification
* @return the corresponding generic parameter type
* @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()}
*/
@Deprecated
public static Type getTargetType(MethodParameter methodParameter) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
return methodParameter.getGenericParameterType();
}
/**
* Determine the target type for the given generic parameter type.
* @param methodParameter the method parameter specification
* @param implementationClass the class to resolve type variables against
* @return the corresponding generic parameter or return type
*/
public static Class resolveParameterType(MethodParameter methodParameter, Class implementationClass) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
Assert.notNull(implementationClass, "Class must not be null");
methodParameter.setContainingClass(implementationClass);
ResolvableType.resolveMethodParameter(methodParameter);
return methodParameter.getParameterType();
}
/**
* Determine the target type for the generic return type of the given method,
* where formal type variables are declared on the given class.
* @param method the method to introspect
* @param clazz the class to resolve type variables against
* @return the corresponding generic parameter or return type
*/
public static Class resolveReturnType(Method method, Class clazz) {
Assert.notNull(method, "Method must not be null");
Assert.notNull(clazz, "Class must not be null");
return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType());
}
/**
* Determine the target type for the generic return type of the given
* generic method, where formal type variables are declared on
* the given method itself.
* For example, given a factory method with the following signature,
* if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
* method for {@code creatProxy()} and an {@code Object[]} array containing
* {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
* infer that the target return type is {@code MyService}.
*
{@code public static T createProxy(Class clazz)}
* Possible Return Values
*
* - the target return type, if it can be inferred
* - the {@linkplain Method#getReturnType() standard return type}, if
* the given {@code method} does not declare any {@linkplain
* Method#getTypeParameters() formal type variables}
* - the {@linkplain Method#getReturnType() standard return type}, if the
* target return type cannot be inferred (e.g., due to type erasure)
* - {@code null}, if the length of the given arguments array is shorter
* than the length of the {@linkplain
* Method#getGenericParameterTypes() formal argument list} for the given
* method
*
* @param method the method to introspect, never {@code null}
* @param args the arguments that will be supplied to the method when it is
* invoked (never {@code null})
* @param classLoader the ClassLoader to resolve class names against, if necessary
* (may be {@code null})
* @return the resolved target return type, the standard return type, or {@code null}
* @since 3.2.5
* @deprecated as of Spring Framework 4.3.8, superseded by {@link ResolvableType} usage
*/
@Deprecated
public static Class resolveReturnTypeForGenericMethod(Method method, Object[] args, ClassLoader classLoader) {
Assert.notNull(method, "Method must not be null");
Assert.notNull(args, "Argument array must not be null");
TypeVariable[] declaredTypeVariables = method.getTypeParameters();
Type genericReturnType = method.getGenericReturnType();
Type[] methodArgumentTypes = method.getGenericParameterTypes();
// No declared type variables to inspect, so just return the standard return type.
if (declaredTypeVariables.length == 0) {
return method.getReturnType();
}
// The supplied argument list is too short for the method's signature, so
// return null, since such a method invocation would fail.
if (args.length < methodArgumentTypes.length) {
return null;
}
// Ensure that the type variable (e.g., T) is declared directly on the method
// itself (e.g., via ), not on the enclosing class or interface.
boolean locallyDeclaredTypeVariableMatchesReturnType = false;
for (TypeVariable currentTypeVariable : declaredTypeVariables) {
if (currentTypeVariable.equals(genericReturnType)) {
locallyDeclaredTypeVariableMatchesReturnType = true;
break;
}
}
if (locallyDeclaredTypeVariableMatchesReturnType) {
for (int i = 0; i < methodArgumentTypes.length; i++) {
Type currentMethodArgumentType = methodArgumentTypes[i];
if (currentMethodArgumentType.equals(genericReturnType)) {
return args[i].getClass();
}
if (currentMethodArgumentType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type typeArg : actualTypeArguments) {
if (typeArg.equals(genericReturnType)) {
Object arg = args[i];
if (arg instanceof Class) {
return (Class) arg;
}
else if (arg instanceof String && classLoader != null) {
try {
return classLoader.loadClass((String) arg);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Could not resolve specific class name argument [" + arg + "]", ex);
}
}
else {
// Consider adding logic to determine the class of the typeArg, if possible.
// For now, just fall back...
return method.getReturnType();
}
}
}
}
}
}
// Fall back...
return method.getReturnType();
}
/**
* Resolve the single type argument of the given generic interface against the given
* target method which is assumed to return the given interface or an implementation
* of it.
* @param method the target method to check the return type of
* @param genericIfc the generic interface or superclass to resolve the type argument from
* @return the resolved parameter type of the method return type, or {@code null}
* if not resolvable or if the single argument is of type {@link WildcardType}.
*/
public static Class resolveReturnTypeArgument(Method method, Class genericIfc) {
Assert.notNull(method, "Method must not be null");
ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc);
if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) {
return null;
}
return getSingleGeneric(resolvableType);
}
/**
* Resolve the single type argument of the given generic interface against
* the given target class which is assumed to implement the generic interface
* and possibly declare a concrete type for its type variable.
* @param clazz the target class to check against
* @param genericIfc the generic interface or superclass to resolve the type argument from
* @return the resolved type of the argument, or {@code null} if not resolvable
*/
public static Class resolveTypeArgument(Class clazz, Class genericIfc) {
ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc);
if (!resolvableType.hasGenerics()) {
return null;
}
return getSingleGeneric(resolvableType);
}
private static Class getSingleGeneric(ResolvableType resolvableType) {
if (resolvableType.getGenerics().length > 1) {
throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
resolvableType + "] but found " + resolvableType.getGenerics().length);
}
return resolvableType.getGeneric().resolve();
}
/**
* Resolve the type arguments of the given generic interface against the given
* target class which is assumed to implement the generic interface and possibly
* declare concrete types for its type variables.
* @param clazz the target class to check against
* @param genericIfc the generic interface or superclass to resolve the type argument from
* @return the resolved type of each argument, with the array size matching the
* number of actual type arguments, or {@code null} if not resolvable
*/
public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) {
ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc);
if (!type.hasGenerics() || type.isEntirelyUnresolvable()) {
return null;
}
return type.resolveGenerics(Object.class);
}
/**
* Resolve the specified generic type against the given TypeVariable map.
* Used by Spring Data.
* @param genericType the generic type to resolve
* @param map the TypeVariable Map to resolved against
* @return the type if it resolves to a Class, or {@code Object.class} otherwise
*/
@SuppressWarnings("rawtypes")
public static Class resolveType(Type genericType, Map map) {
return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class);
}
/**
* Build a mapping of {@link TypeVariable#getName TypeVariable names} to
* {@link Class concrete classes} for the specified {@link Class}.
* Searches all super types, enclosing types and interfaces.
* @see #resolveType(Type, Map)
*/
@SuppressWarnings("rawtypes")
public static Map getTypeVariableMap(Class clazz) {
Map typeVariableMap = typeVariableCache.get(clazz);
if (typeVariableMap == null) {
typeVariableMap = new HashMap();
buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap);
typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap));
}
return typeVariableMap;
}
@SuppressWarnings("rawtypes")
private static void buildTypeVariableMap(ResolvableType type, Map typeVariableMap) {
if (type != ResolvableType.NONE) {
if (type.getType() instanceof ParameterizedType) {
TypeVariable[] variables = type.resolve().getTypeParameters();
for (int i = 0; i < variables.length; i++) {
ResolvableType generic = type.getGeneric(i);
while (generic.getType() instanceof TypeVariable) {
generic = generic.resolveType();
}
if (generic != ResolvableType.NONE) {
typeVariableMap.put(variables[i], generic.getType());
}
}
}
buildTypeVariableMap(type.getSuperType(), typeVariableMap);
for (ResolvableType interfaceType : type.getInterfaces()) {
buildTypeVariableMap(interfaceType, typeVariableMap);
}
if (type.resolve().isMemberClass()) {
buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap);
}
}
}
@SuppressWarnings({"serial", "rawtypes"})
private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver {
private final Map typeVariableMap;
public TypeVariableMapVariableResolver(Map typeVariableMap) {
this.typeVariableMap = typeVariableMap;
}
@Override
public ResolvableType resolveVariable(TypeVariable variable) {
Type type = this.typeVariableMap.get(variable);
return (type != null ? ResolvableType.forType(type) : null);
}
@Override
public Object getSource() {
return this.typeVariableMap;
}
}
}