Extracted AbstractJsonHttpMessageConverter from GsonHttpMessageConverter

Generic type resolution algorithm in GenericTypeResolver shared between Jackson and Gson.

Issue: SPR-15381
This commit is contained in:
Juergen Hoeller
2017-04-10 15:36:29 +02:00
parent ea98ee820a
commit e5fdd4cd1d
12 changed files with 337 additions and 659 deletions

View File

@@ -21,12 +21,8 @@ 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.
@@ -42,24 +38,6 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*/
public abstract class GenericTypeResolver {
/** Cache from Class to TypeVariable Map */
@SuppressWarnings("rawtypes")
private static final Map<Class<?>, Map<TypeVariable, Type>> typeVariableCache =
new ConcurrentReferenceHashMap<>();
/**
* 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
@@ -80,7 +58,6 @@ public abstract class GenericTypeResolver {
* @param method the method to introspect
* @param clazz the class to resolve type variables against
* @return the corresponding generic parameter or return type
* @see #resolveReturnTypeForGenericMethod
*/
public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
Assert.notNull(method, "Method must not be null");
@@ -88,106 +65,6 @@ public abstract class GenericTypeResolver {
return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType());
}
/**
* Determine the target type for the generic return type of the given
* <em>generic method</em>, where formal type variables are declared on
* the given method itself.
* <p>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}.
* <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre>
* <h4>Possible Return Values</h4>
* <ul>
* <li>the target return type, if it can be inferred</li>
* <li>the {@linkplain Method#getReturnType() standard return type}, if
* the given {@code method} does not declare any {@linkplain
* Method#getTypeParameters() formal type variables}</li>
* <li>the {@linkplain Method#getReturnType() standard return type}, if the
* target return type cannot be inferred (e.g., due to type erasure)</li>
* <li>{@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</li>
* </ul>
* @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
* @see #resolveReturnType
*/
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<Method>[] 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 <T>), not on the enclosing class or interface.
boolean locallyDeclaredTypeVariableMatchesReturnType = false;
for (TypeVariable<Method> 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
@@ -248,81 +125,75 @@ public abstract class GenericTypeResolver {
}
/**
* Resolve the specified generic type against the given TypeVariable map.
* @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
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
* Resolve the given generic type against the given context class,
* substituting type variables as far as possible.
* @param genericType the (potentially) generic type
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature (can be {@code null})
* @return the resolved type (possibly the given generic type as-is)
* @since 5.0
*/
@Deprecated
@SuppressWarnings("rawtypes")
public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> 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.
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
*/
@Deprecated
@SuppressWarnings("rawtypes")
public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) {
Map<TypeVariable, Type> 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<TypeVariable, Type> 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());
}
public static Type resolveType(Type genericType, Class<?> contextClass) {
if (contextClass != null) {
if (genericType instanceof TypeVariable) {
ResolvableType resolvedTypeVariable = resolveVariable(
(TypeVariable<?>) genericType, ResolvableType.forClass(contextClass));
if (resolvedTypeVariable != ResolvableType.NONE) {
return resolvedTypeVariable.resolve();
}
}
buildTypeVariableMap(type.getSuperType(), typeVariableMap);
for (ResolvableType interfaceType : type.getInterfaces()) {
buildTypeVariableMap(interfaceType, typeVariableMap);
}
if (type.resolve().isMemberClass()) {
buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap);
else if (genericType instanceof ParameterizedType) {
ResolvableType resolvedType = ResolvableType.forType(genericType);
if (resolvedType.hasUnresolvableGenerics()) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
Type[] typeArguments = parameterizedType.getActualTypeArguments();
for (int i = 0; i < typeArguments.length; i++) {
Type typeArgument = typeArguments[i];
if (typeArgument instanceof TypeVariable) {
ResolvableType resolvedTypeArgument = resolveVariable(
(TypeVariable<?>) typeArgument, ResolvableType.forClass(contextClass));
if (resolvedTypeArgument != ResolvableType.NONE) {
generics[i] = resolvedTypeArgument.resolve();
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
}
return ResolvableType.forClassWithGenerics(resolvedType.getRawClass(), generics).getType();
}
}
}
return genericType;
}
@SuppressWarnings({"serial", "rawtypes"})
private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver {
private final Map<TypeVariable, Type> typeVariableMap;
public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) {
this.typeVariableMap = typeVariableMap;
private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
ResolvableType resolvedType;
if (contextType.hasGenerics()) {
resolvedType = ResolvableType.forType(typeVariable, contextType);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
Type type = this.typeVariableMap.get(variable);
return (type != null ? ResolvableType.forType(type) : null);
ResolvableType superType = contextType.getSuperType();
if (superType != ResolvableType.NONE) {
resolvedType = resolveVariable(typeVariable, superType);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
@Override
public Object getSource() {
return this.typeVariableMap;
for (ResolvableType ifc : contextType.getInterfaces()) {
resolvedType = resolveVariable(typeVariable, ifc);
if (resolvedType.resolve() != null) {
return resolvedType;
}
}
return ResolvableType.NONE;
}
}