Skip to content

Commit e5fdd4c

Browse files
committed
Extracted AbstractJsonHttpMessageConverter from GsonHttpMessageConverter
Generic type resolution algorithm in GenericTypeResolver shared between Jackson and Gson. Issue: SPR-15381
1 parent ea98ee8 commit e5fdd4c

File tree

12 files changed

+333
-655
lines changed

12 files changed

+333
-655
lines changed

spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java

Lines changed: 58 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,8 @@
2121
import java.lang.reflect.Type;
2222
import java.lang.reflect.TypeVariable;
2323
import java.lang.reflect.WildcardType;
24-
import java.util.Collections;
25-
import java.util.HashMap;
26-
import java.util.Map;
2724

2825
import org.springframework.util.Assert;
29-
import org.springframework.util.ConcurrentReferenceHashMap;
3026

3127
/**
3228
* Helper class for resolving generic types against type variables.
@@ -42,24 +38,6 @@
4238
*/
4339
public abstract class GenericTypeResolver {
4440

45-
/** Cache from Class to TypeVariable Map */
46-
@SuppressWarnings("rawtypes")
47-
private static final Map<Class<?>, Map<TypeVariable, Type>> typeVariableCache =
48-
new ConcurrentReferenceHashMap<>();
49-
50-
51-
/**
52-
* Determine the target type for the given parameter specification.
53-
* @param methodParameter the method parameter specification
54-
* @return the corresponding generic parameter type
55-
* @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()}
56-
*/
57-
@Deprecated
58-
public static Type getTargetType(MethodParameter methodParameter) {
59-
Assert.notNull(methodParameter, "MethodParameter must not be null");
60-
return methodParameter.getGenericParameterType();
61-
}
62-
6341
/**
6442
* Determine the target type for the given generic parameter type.
6543
* @param methodParameter the method parameter specification
@@ -80,114 +58,13 @@ public static Class<?> resolveParameterType(MethodParameter methodParameter, Cla
8058
* @param method the method to introspect
8159
* @param clazz the class to resolve type variables against
8260
* @return the corresponding generic parameter or return type
83-
* @see #resolveReturnTypeForGenericMethod
8461
*/
8562
public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
8663
Assert.notNull(method, "Method must not be null");
8764
Assert.notNull(clazz, "Class must not be null");
8865
return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType());
8966
}
9067

91-
/**
92-
* Determine the target type for the generic return type of the given
93-
* <em>generic method</em>, where formal type variables are declared on
94-
* the given method itself.
95-
* <p>For example, given a factory method with the following signature,
96-
* if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
97-
* method for {@code creatProxy()} and an {@code Object[]} array containing
98-
* {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
99-
* infer that the target return type is {@code MyService}.
100-
* <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre>
101-
* <h4>Possible Return Values</h4>
102-
* <ul>
103-
* <li>the target return type, if it can be inferred</li>
104-
* <li>the {@linkplain Method#getReturnType() standard return type}, if
105-
* the given {@code method} does not declare any {@linkplain
106-
* Method#getTypeParameters() formal type variables}</li>
107-
* <li>the {@linkplain Method#getReturnType() standard return type}, if the
108-
* target return type cannot be inferred (e.g., due to type erasure)</li>
109-
* <li>{@code null}, if the length of the given arguments array is shorter
110-
* than the length of the {@linkplain
111-
* Method#getGenericParameterTypes() formal argument list} for the given
112-
* method</li>
113-
* </ul>
114-
* @param method the method to introspect, never {@code null}
115-
* @param args the arguments that will be supplied to the method when it is
116-
* invoked (never {@code null})
117-
* @param classLoader the ClassLoader to resolve class names against, if necessary
118-
* (may be {@code null})
119-
* @return the resolved target return type, the standard return type, or {@code null}
120-
* @since 3.2.5
121-
* @see #resolveReturnType
122-
*/
123-
public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args, ClassLoader classLoader) {
124-
Assert.notNull(method, "Method must not be null");
125-
Assert.notNull(args, "Argument array must not be null");
126-
127-
TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
128-
Type genericReturnType = method.getGenericReturnType();
129-
Type[] methodArgumentTypes = method.getGenericParameterTypes();
130-
131-
// No declared type variables to inspect, so just return the standard return type.
132-
if (declaredTypeVariables.length == 0) {
133-
return method.getReturnType();
134-
}
135-
136-
// The supplied argument list is too short for the method's signature, so
137-
// return null, since such a method invocation would fail.
138-
if (args.length < methodArgumentTypes.length) {
139-
return null;
140-
}
141-
142-
// Ensure that the type variable (e.g., T) is declared directly on the method
143-
// itself (e.g., via <T>), not on the enclosing class or interface.
144-
boolean locallyDeclaredTypeVariableMatchesReturnType = false;
145-
for (TypeVariable<Method> currentTypeVariable : declaredTypeVariables) {
146-
if (currentTypeVariable.equals(genericReturnType)) {
147-
locallyDeclaredTypeVariableMatchesReturnType = true;
148-
break;
149-
}
150-
}
151-
152-
if (locallyDeclaredTypeVariableMatchesReturnType) {
153-
for (int i = 0; i < methodArgumentTypes.length; i++) {
154-
Type currentMethodArgumentType = methodArgumentTypes[i];
155-
if (currentMethodArgumentType.equals(genericReturnType)) {
156-
return args[i].getClass();
157-
}
158-
if (currentMethodArgumentType instanceof ParameterizedType) {
159-
ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
160-
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
161-
for (Type typeArg : actualTypeArguments) {
162-
if (typeArg.equals(genericReturnType)) {
163-
Object arg = args[i];
164-
if (arg instanceof Class) {
165-
return (Class<?>) arg;
166-
}
167-
else if (arg instanceof String && classLoader != null) {
168-
try {
169-
return classLoader.loadClass((String) arg);
170-
}
171-
catch (ClassNotFoundException ex) {
172-
throw new IllegalStateException(
173-
"Could not resolve specific class name argument [" + arg + "]", ex);
174-
}
175-
}
176-
else {
177-
// Consider adding logic to determine the class of the typeArg, if possible.
178-
// For now, just fall back...
179-
return method.getReturnType();
180-
}
181-
}
182-
}
183-
}
184-
}
185-
}
186-
187-
// Fall back...
188-
return method.getReturnType();
189-
}
190-
19168
/**
19269
* Resolve the single type argument of the given generic interface against the given
19370
* target method which is assumed to return the given interface or an implementation
@@ -248,81 +125,75 @@ public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericIf
248125
}
249126

250127
/**
251-
* Resolve the specified generic type against the given TypeVariable map.
252-
* @param genericType the generic type to resolve
253-
* @param map the TypeVariable Map to resolved against
254-
* @return the type if it resolves to a Class, or {@code Object.class} otherwise
255-
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
128+
* Resolve the given generic type against the given context class,
129+
* substituting type variables as far as possible.
130+
* @param genericType the (potentially) generic type
131+
* @param contextClass a context class for the target type, for example a class
132+
* in which the target type appears in a method signature (can be {@code null})
133+
* @return the resolved type (possibly the given generic type as-is)
134+
* @since 5.0
256135
*/
257-
@Deprecated
258-
@SuppressWarnings("rawtypes")
259-
public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> map) {
260-
return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class);
261-
}
262-
263-
/**
264-
* Build a mapping of {@link TypeVariable#getName TypeVariable names} to
265-
* {@link Class concrete classes} for the specified {@link Class}. Searches
266-
* all super types, enclosing types and interfaces.
267-
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
268-
*/
269-
@Deprecated
270-
@SuppressWarnings("rawtypes")
271-
public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) {
272-
Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz);
273-
if (typeVariableMap == null) {
274-
typeVariableMap = new HashMap<>();
275-
buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap);
276-
typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap));
277-
}
278-
return typeVariableMap;
279-
}
280-
281-
@SuppressWarnings("rawtypes")
282-
private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> typeVariableMap) {
283-
if (type != ResolvableType.NONE) {
284-
if (type.getType() instanceof ParameterizedType) {
285-
TypeVariable<?>[] variables = type.resolve().getTypeParameters();
286-
for (int i = 0; i < variables.length; i++) {
287-
ResolvableType generic = type.getGeneric(i);
288-
while (generic.getType() instanceof TypeVariable<?>) {
289-
generic = generic.resolveType();
290-
}
291-
if (generic != ResolvableType.NONE) {
292-
typeVariableMap.put(variables[i], generic.getType());
293-
}
136+
public static Type resolveType(Type genericType, Class<?> contextClass) {
137+
if (contextClass != null) {
138+
if (genericType instanceof TypeVariable) {
139+
ResolvableType resolvedTypeVariable = resolveVariable(
140+
(TypeVariable<?>) genericType, ResolvableType.forClass(contextClass));
141+
if (resolvedTypeVariable != ResolvableType.NONE) {
142+
return resolvedTypeVariable.resolve();
294143
}
295144
}
296-
buildTypeVariableMap(type.getSuperType(), typeVariableMap);
297-
for (ResolvableType interfaceType : type.getInterfaces()) {
298-
buildTypeVariableMap(interfaceType, typeVariableMap);
299-
}
300-
if (type.resolve().isMemberClass()) {
301-
buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap);
145+
else if (genericType instanceof ParameterizedType) {
146+
ResolvableType resolvedType = ResolvableType.forType(genericType);
147+
if (resolvedType.hasUnresolvableGenerics()) {
148+
ParameterizedType parameterizedType = (ParameterizedType) genericType;
149+
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
150+
Type[] typeArguments = parameterizedType.getActualTypeArguments();
151+
for (int i = 0; i < typeArguments.length; i++) {
152+
Type typeArgument = typeArguments[i];
153+
if (typeArgument instanceof TypeVariable) {
154+
ResolvableType resolvedTypeArgument = resolveVariable(
155+
(TypeVariable<?>) typeArgument, ResolvableType.forClass(contextClass));
156+
if (resolvedTypeArgument != ResolvableType.NONE) {
157+
generics[i] = resolvedTypeArgument.resolve();
158+
}
159+
else {
160+
generics[i] = ResolvableType.forType(typeArgument).resolve();
161+
}
162+
}
163+
else {
164+
generics[i] = ResolvableType.forType(typeArgument).resolve();
165+
}
166+
}
167+
return ResolvableType.forClassWithGenerics(resolvedType.getRawClass(), generics).getType();
168+
}
302169
}
303170
}
171+
return genericType;
304172
}
305173

306-
307-
@SuppressWarnings({"serial", "rawtypes"})
308-
private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver {
309-
310-
private final Map<TypeVariable, Type> typeVariableMap;
311-
312-
public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) {
313-
this.typeVariableMap = typeVariableMap;
174+
private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
175+
ResolvableType resolvedType;
176+
if (contextType.hasGenerics()) {
177+
resolvedType = ResolvableType.forType(typeVariable, contextType);
178+
if (resolvedType.resolve() != null) {
179+
return resolvedType;
180+
}
314181
}
315182

316-
@Override
317-
public ResolvableType resolveVariable(TypeVariable<?> variable) {
318-
Type type = this.typeVariableMap.get(variable);
319-
return (type != null ? ResolvableType.forType(type) : null);
183+
ResolvableType superType = contextType.getSuperType();
184+
if (superType != ResolvableType.NONE) {
185+
resolvedType = resolveVariable(typeVariable, superType);
186+
if (resolvedType.resolve() != null) {
187+
return resolvedType;
188+
}
320189
}
321-
322-
@Override
323-
public Object getSource() {
324-
return this.typeVariableMap;
190+
for (ResolvableType ifc : contextType.getInterfaces()) {
191+
resolvedType = resolveVariable(typeVariable, ifc);
192+
if (resolvedType.resolve() != null) {
193+
return resolvedType;
194+
}
325195
}
196+
return ResolvableType.NONE;
326197
}
327198

328199
}

spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,8 +18,6 @@
1818

1919
import java.io.Serializable;
2020
import java.lang.reflect.Method;
21-
import java.lang.reflect.ParameterizedType;
22-
import java.lang.reflect.Type;
2321
import java.lang.reflect.TypeVariable;
2422
import java.util.Collection;
2523
import java.util.Date;
@@ -109,26 +107,6 @@ public void testIsBridgeMethodFor() throws Exception {
109107
assertFalse("Should not be bridge method", BridgeMethodResolver.isBridgeMethodFor(bridge, other, MyBar.class));
110108
}
111109

112-
@Test
113-
@Deprecated
114-
public void testCreateTypeVariableMap() throws Exception {
115-
Map<TypeVariable, Type> typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyBar.class);
116-
TypeVariable<?> barT = findTypeVariable(InterBar.class, "T");
117-
assertEquals(String.class, typeVariableMap.get(barT));
118-
119-
typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyFoo.class);
120-
TypeVariable<?> fooT = findTypeVariable(Foo.class, "T");
121-
assertEquals(String.class, typeVariableMap.get(fooT));
122-
123-
typeVariableMap = GenericTypeResolver.getTypeVariableMap(ExtendsEnclosing.ExtendsEnclosed.ExtendsReallyDeepNow.class);
124-
TypeVariable<?> r = findTypeVariable(Enclosing.Enclosed.ReallyDeepNow.class, "R");
125-
TypeVariable<?> s = findTypeVariable(Enclosing.Enclosed.class, "S");
126-
TypeVariable<?> t = findTypeVariable(Enclosing.class, "T");
127-
assertEquals(Long.class, typeVariableMap.get(r));
128-
assertEquals(Integer.class, typeVariableMap.get(s));
129-
assertEquals(String.class, typeVariableMap.get(t));
130-
}
131-
132110
@Test
133111
public void testDoubleParameterization() throws Exception {
134112
Method objectBridge = MyBoo.class.getDeclaredMethod("foo", Object.class);
@@ -228,14 +206,6 @@ public void testSPR2583() throws Exception {
228206
assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
229207
}
230208

231-
@Test
232-
@Deprecated
233-
public void testSPR2454() throws Exception {
234-
Map<TypeVariable, Type> typeVariableMap = GenericTypeResolver.getTypeVariableMap(YourHomer.class);
235-
TypeVariable<?> variable = findTypeVariable(MyHomer.class, "L");
236-
assertEquals(AbstractBounded.class, ((ParameterizedType) typeVariableMap.get(variable)).getRawType());
237-
}
238-
239209
@Test
240210
public void testSPR2603() throws Exception {
241211
Method objectBridge = YourHomer.class.getDeclaredMethod("foo", Bounded.class);

0 commit comments

Comments
 (0)