Extracted AbstractJsonHttpMessageConverter from GsonHttpMessageConverter
Generic type resolution algorithm in GenericTypeResolver shared between Jackson and Gson. Issue: SPR-15381
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
@@ -109,26 +107,6 @@ public class BridgeMethodResolverTests {
|
||||
assertFalse("Should not be bridge method", BridgeMethodResolver.isBridgeMethodFor(bridge, other, MyBar.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
public void testCreateTypeVariableMap() throws Exception {
|
||||
Map<TypeVariable, Type> typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyBar.class);
|
||||
TypeVariable<?> barT = findTypeVariable(InterBar.class, "T");
|
||||
assertEquals(String.class, typeVariableMap.get(barT));
|
||||
|
||||
typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyFoo.class);
|
||||
TypeVariable<?> fooT = findTypeVariable(Foo.class, "T");
|
||||
assertEquals(String.class, typeVariableMap.get(fooT));
|
||||
|
||||
typeVariableMap = GenericTypeResolver.getTypeVariableMap(ExtendsEnclosing.ExtendsEnclosed.ExtendsReallyDeepNow.class);
|
||||
TypeVariable<?> r = findTypeVariable(Enclosing.Enclosed.ReallyDeepNow.class, "R");
|
||||
TypeVariable<?> s = findTypeVariable(Enclosing.Enclosed.class, "S");
|
||||
TypeVariable<?> t = findTypeVariable(Enclosing.class, "T");
|
||||
assertEquals(Long.class, typeVariableMap.get(r));
|
||||
assertEquals(Integer.class, typeVariableMap.get(s));
|
||||
assertEquals(String.class, typeVariableMap.get(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleParameterization() throws Exception {
|
||||
Method objectBridge = MyBoo.class.getDeclaredMethod("foo", Object.class);
|
||||
@@ -228,14 +206,6 @@ public class BridgeMethodResolverTests {
|
||||
assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
public void testSPR2454() throws Exception {
|
||||
Map<TypeVariable, Type> typeVariableMap = GenericTypeResolver.getTypeVariableMap(YourHomer.class);
|
||||
TypeVariable<?> variable = findTypeVariable(MyHomer.class, "L");
|
||||
assertEquals(AbstractBounded.class, ((ParameterizedType) typeVariableMap.get(variable)).getRawType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSPR2603() throws Exception {
|
||||
Method objectBridge = YourHomer.class.getDeclaredMethod("foo", Bounded.class);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -17,11 +17,7 @@
|
||||
package org.springframework.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -76,71 +72,11 @@ public class GenericTypeResolverTests {
|
||||
resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
public void testResolveType() {
|
||||
Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class);
|
||||
MethodParameter intMessageMethodParam = new MethodParameter(intMessageMethod, 0);
|
||||
assertEquals(MyInterfaceType.class,
|
||||
resolveType(intMessageMethodParam.getGenericParameterType(), new HashMap<>()));
|
||||
|
||||
Method intArrMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerArrayInputMessage",
|
||||
MyInterfaceType[].class);
|
||||
MethodParameter intArrMessageMethodParam = new MethodParameter(intArrMessageMethod, 0);
|
||||
assertEquals(MyInterfaceType[].class,
|
||||
resolveType(intArrMessageMethodParam.getGenericParameterType(), new HashMap<>()));
|
||||
|
||||
Method genericArrMessageMethod = findMethod(MySimpleTypeWithMethods.class, "readGenericArrayInputMessage",
|
||||
Object[].class);
|
||||
MethodParameter genericArrMessageMethodParam = new MethodParameter(genericArrMessageMethod, 0);
|
||||
Map<TypeVariable, Type> varMap = getTypeVariableMap(MySimpleTypeWithMethods.class);
|
||||
assertEquals(Integer[].class, resolveType(genericArrMessageMethodParam.getGenericParameterType(), varMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoundParameterizedType() {
|
||||
assertEquals(B.class, resolveTypeArgument(TestImpl.class, TestIfc.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
public void testGetTypeVariableMap() throws Exception {
|
||||
Map<TypeVariable, Type> map;
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(MySimpleInterfaceType.class);
|
||||
assertThat(map.toString(), equalTo("{T=class java.lang.String}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(MyCollectionInterfaceType.class);
|
||||
assertThat(map.toString(), equalTo("{T=java.util.Collection<java.lang.String>}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(MyCollectionSuperclassType.class);
|
||||
assertThat(map.toString(), equalTo("{T=java.util.Collection<java.lang.String>}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(MySimpleTypeWithMethods.class);
|
||||
assertThat(map.toString(), equalTo("{T=class java.lang.Integer}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(TopLevelClass.class);
|
||||
assertThat(map.toString(), equalTo("{}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.class);
|
||||
assertThat(map.toString(), equalTo("{T=class java.lang.Integer}"));
|
||||
|
||||
map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.TypedNested.class);
|
||||
assertThat(map.size(), equalTo(2));
|
||||
Type t = null;
|
||||
Type x = null;
|
||||
for (Map.Entry<TypeVariable, Type> entry : map.entrySet()) {
|
||||
if (entry.getKey().toString().equals("T")) {
|
||||
t = entry.getValue();
|
||||
}
|
||||
else {
|
||||
x = entry.getValue();
|
||||
}
|
||||
}
|
||||
assertThat(t, equalTo((Type) Integer.class));
|
||||
assertThat(x, equalTo((Type) Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGenericsCannotBeResolved() throws Exception {
|
||||
// SPR-11030
|
||||
|
||||
Reference in New Issue
Block a user