|
1 | 1 | /* |
2 | | - * Copyright 2002-2011 the original author or authors. |
| 2 | + * Copyright 2002-2012 the original author or authors. |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
|
30 | 30 | import java.util.Map; |
31 | 31 | import java.util.WeakHashMap; |
32 | 32 |
|
| 33 | +import org.apache.commons.logging.Log; |
| 34 | +import org.apache.commons.logging.LogFactory; |
| 35 | + |
33 | 36 | import org.springframework.util.Assert; |
| 37 | +import org.springframework.util.ObjectUtils; |
34 | 38 |
|
35 | 39 | /** |
36 | 40 | * Helper class for resolving generic types against type variables. |
|
40 | 44 | * |
41 | 45 | * @author Juergen Hoeller |
42 | 46 | * @author Rob Harrop |
| 47 | + * @author Sam Brannen |
43 | 48 | * @since 2.5.2 |
44 | 49 | * @see GenericCollectionTypeResolver |
45 | 50 | */ |
46 | 51 | public abstract class GenericTypeResolver { |
47 | 52 |
|
| 53 | + private static final Log logger = LogFactory.getLog(GenericTypeResolver.class); |
| 54 | + |
48 | 55 | /** Cache from Class to TypeVariable Map */ |
49 | 56 | private static final Map<Class, Reference<Map<TypeVariable, Type>>> typeVariableCache = |
50 | 57 | Collections.synchronizedMap(new WeakHashMap<Class, Reference<Map<TypeVariable, Type>>>()); |
@@ -88,18 +95,144 @@ public static Class<?> resolveParameterType(MethodParameter methodParam, Class c |
88 | 95 | } |
89 | 96 |
|
90 | 97 | /** |
91 | | - * Determine the target type for the generic return type of the given method. |
| 98 | + * Determine the target type for the generic return type of the given method, |
| 99 | + * where the type variable is declared on the given class. |
| 100 | + * |
92 | 101 | * @param method the method to introspect |
93 | 102 | * @param clazz the class to resolve type variables against |
94 | 103 | * @return the corresponding generic parameter or return type |
| 104 | + * @see #resolveParameterizedReturnType |
95 | 105 | */ |
96 | | - public static Class<?> resolveReturnType(Method method, Class clazz) { |
| 106 | + public static Class<?> resolveReturnType(Method method, Class<?> clazz) { |
97 | 107 | Assert.notNull(method, "Method must not be null"); |
98 | 108 | Type genericType = method.getGenericReturnType(); |
99 | 109 | Assert.notNull(clazz, "Class must not be null"); |
100 | 110 | Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz); |
101 | 111 | Type rawType = getRawType(genericType, typeVariableMap); |
102 | | - return (rawType instanceof Class ? (Class) rawType : method.getReturnType()); |
| 112 | + return (rawType instanceof Class ? (Class<?>) rawType : method.getReturnType()); |
| 113 | + } |
| 114 | + |
| 115 | + /** |
| 116 | + * Determine the target type for the generic return type of the given |
| 117 | + * <em>parameterized</em> method, where the type variable is declared |
| 118 | + * on the given method. |
| 119 | + * |
| 120 | + * <p>For example, given a factory method with the following signature, |
| 121 | + * if {@code resolveParameterizedReturnType()} is invoked with the reflected |
| 122 | + * method for {@code creatProxy()} and an {@code Object[]} array containing |
| 123 | + * {@code MyService.class}, {@code resolveParameterizedReturnType()} will |
| 124 | + * infer that the target return type is {@code MyService}. |
| 125 | + * |
| 126 | + * <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre> |
| 127 | + * |
| 128 | + * <h4>Possible Return Values</h4> |
| 129 | + * <ul> |
| 130 | + * <li>the target return type if it can be inferred</li> |
| 131 | + * <li>the {@link Method#getReturnType() standard return type}, if |
| 132 | + * the given {@code method} does not declare any {@link |
| 133 | + * Method#getTypeParameters() generic types}</li> |
| 134 | + * <li>the {@link Method#getReturnType() standard return type}, if the |
| 135 | + * target return type cannot be inferred (e.g., due to type erasure)</li> |
| 136 | + * <li>{@code null}, if the length of the given arguments array is shorter |
| 137 | + * than the length of the {@link |
| 138 | + * Method#getGenericParameterTypes() formal argument list} for the given |
| 139 | + * method</li> |
| 140 | + * </ul> |
| 141 | + * |
| 142 | + * @param method the method to introspect, never {@code null} |
| 143 | + * @param args the arguments that will be supplied to the method when it is |
| 144 | + * invoked, never {@code null} |
| 145 | + * @return the resolved target return type, the standard return type, or |
| 146 | + * {@code null} |
| 147 | + * @since 3.2 |
| 148 | + * @see #resolveReturnType |
| 149 | + */ |
| 150 | + public static Class<?> resolveParameterizedReturnType(Method method, Object[] args) { |
| 151 | + Assert.notNull(method, "method must not be null"); |
| 152 | + Assert.notNull(args, "args must not be null"); |
| 153 | + |
| 154 | + final TypeVariable<Method>[] declaredGenericTypes = method.getTypeParameters(); |
| 155 | + final Type genericReturnType = method.getGenericReturnType(); |
| 156 | + final Type[] genericArgumentTypes = method.getGenericParameterTypes(); |
| 157 | + |
| 158 | + if (logger.isDebugEnabled()) { |
| 159 | + logger.debug(String.format( |
| 160 | + "Resolving parameterized return type for [%s] with concrete method arguments [%s].", |
| 161 | + method.toGenericString(), ObjectUtils.nullSafeToString(args))); |
| 162 | + } |
| 163 | + |
| 164 | + // No declared generic types to inspect, so just return the standard return type. |
| 165 | + if (declaredGenericTypes.length == 0) { |
| 166 | + return method.getReturnType(); |
| 167 | + } |
| 168 | + |
| 169 | + // The supplied argument list is too short for the method's signature, so |
| 170 | + // return null, since such a method invocation would fail. |
| 171 | + if (args.length < genericArgumentTypes.length) { |
| 172 | + return null; |
| 173 | + } |
| 174 | + |
| 175 | + // Ensure that the generic type is declared directly on the method |
| 176 | + // itself, not on the enclosing class or interface. |
| 177 | + boolean locallyDeclaredGenericTypeMatchesReturnType = false; |
| 178 | + for (TypeVariable<Method> currentType : declaredGenericTypes) { |
| 179 | + if (currentType.equals(genericReturnType)) { |
| 180 | + if (logger.isDebugEnabled()) { |
| 181 | + logger.debug(String.format( |
| 182 | + "Found declared generic type [%s] that matches the target return type [%s].", |
| 183 | + currentType, genericReturnType)); |
| 184 | + } |
| 185 | + locallyDeclaredGenericTypeMatchesReturnType = true; |
| 186 | + break; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + if (locallyDeclaredGenericTypeMatchesReturnType) { |
| 191 | + for (int i = 0; i < genericArgumentTypes.length; i++) { |
| 192 | + final Type currentArgumentType = genericArgumentTypes[i]; |
| 193 | + |
| 194 | + if (currentArgumentType.equals(genericReturnType)) { |
| 195 | + if (logger.isDebugEnabled()) { |
| 196 | + logger.debug(String.format( |
| 197 | + "Found generic method argument at index [%s] that matches the target return type.", i)); |
| 198 | + } |
| 199 | + return args[i].getClass(); |
| 200 | + } |
| 201 | + |
| 202 | + if (currentArgumentType instanceof ParameterizedType) { |
| 203 | + ParameterizedType parameterizedType = (ParameterizedType) currentArgumentType; |
| 204 | + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); |
| 205 | + |
| 206 | + for (int j = 0; j < actualTypeArguments.length; j++) { |
| 207 | + final Type typeArg = actualTypeArguments[j]; |
| 208 | + |
| 209 | + if (typeArg.equals(genericReturnType)) { |
| 210 | + if (logger.isDebugEnabled()) { |
| 211 | + logger.debug(String.format( |
| 212 | + "Found method argument at index [%s] that is parameterized with a type that matches the target return type.", |
| 213 | + i)); |
| 214 | + } |
| 215 | + |
| 216 | + if (args[i] instanceof Class) { |
| 217 | + return (Class<?>) args[i]; |
| 218 | + } else { |
| 219 | + // Consider adding logic to determine the class of the |
| 220 | + // J'th typeArg, if possible. |
| 221 | + logger.info(String.format( |
| 222 | + "Could not determine the target type for parameterized type [%s] for method [%s].", |
| 223 | + typeArg, method.toGenericString())); |
| 224 | + |
| 225 | + // For now, just fall back... |
| 226 | + return method.getReturnType(); |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + } |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + // Fall back... |
| 235 | + return method.getReturnType(); |
103 | 236 | } |
104 | 237 |
|
105 | 238 | /** |
@@ -128,7 +261,7 @@ public static Class<?> resolveReturnTypeArgument(Method method, Class<?> generic |
128 | 261 | return null; |
129 | 262 | } |
130 | 263 | } |
131 | | - return GenericTypeResolver.resolveTypeArgument((Class<?>) returnType, genericIfc); |
| 264 | + return resolveTypeArgument((Class<?>) returnType, genericIfc); |
132 | 265 | } |
133 | 266 |
|
134 | 267 | /** |
@@ -186,7 +319,7 @@ private static Class[] doResolveTypeArguments(Class ownerClass, Class classToInt |
186 | 319 | } |
187 | 320 | return null; |
188 | 321 | } |
189 | | - |
| 322 | + |
190 | 323 | private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { |
191 | 324 | if (ifc instanceof ParameterizedType) { |
192 | 325 | ParameterizedType paramIfc = (ParameterizedType) ifc; |
@@ -236,7 +369,6 @@ else if (arg instanceof TypeVariable) { |
236 | 369 | return (arg instanceof Class ? (Class) arg : Object.class); |
237 | 370 | } |
238 | 371 |
|
239 | | - |
240 | 372 | /** |
241 | 373 | * Resolve the specified generic type against the given TypeVariable map. |
242 | 374 | * @param genericType the generic type to resolve |
@@ -272,9 +404,9 @@ static Type getRawType(Type genericType, Map<TypeVariable, Type> typeVariableMap |
272 | 404 | } |
273 | 405 |
|
274 | 406 | /** |
275 | | - * Build a mapping of {@link TypeVariable#getName TypeVariable names} to concrete |
276 | | - * {@link Class} for the specified {@link Class}. Searches all super types, |
277 | | - * enclosing types and interfaces. |
| 407 | + * Build a mapping of {@link TypeVariable#getName TypeVariable names} to |
| 408 | + * {@link Class concrete classes} for the specified {@link Class}. Searches |
| 409 | + * all super types, enclosing types and interfaces. |
278 | 410 | */ |
279 | 411 | public static Map<TypeVariable, Type> getTypeVariableMap(Class clazz) { |
280 | 412 | Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz); |
|
0 commit comments