001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.ParameterizedType; 021import java.lang.reflect.Type; 022import java.lang.reflect.TypeVariable; 023import java.lang.reflect.WildcardType; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.Map; 027 028import org.springframework.util.Assert; 029import org.springframework.util.ConcurrentReferenceHashMap; 030 031/** 032 * Helper class for resolving generic types against type variables. 033 * 034 * <p>Mainly intended for usage within the framework, resolving method 035 * parameter types even when they are declared generically. 036 * 037 * @author Juergen Hoeller 038 * @author Rob Harrop 039 * @author Sam Brannen 040 * @author Phillip Webb 041 * @since 2.5.2 042 */ 043public abstract class GenericTypeResolver { 044 045 /** Cache from Class to TypeVariable Map */ 046 @SuppressWarnings("rawtypes") 047 private static final Map<Class<?>, Map<TypeVariable, Type>> typeVariableCache = 048 new ConcurrentReferenceHashMap<Class<?>, Map<TypeVariable, Type>>(); 049 050 051 /** 052 * Determine the target type for the given parameter specification. 053 * @param methodParameter the method parameter specification 054 * @return the corresponding generic parameter type 055 * @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()} 056 */ 057 @Deprecated 058 public static Type getTargetType(MethodParameter methodParameter) { 059 Assert.notNull(methodParameter, "MethodParameter must not be null"); 060 return methodParameter.getGenericParameterType(); 061 } 062 063 /** 064 * Determine the target type for the given generic parameter type. 065 * @param methodParameter the method parameter specification 066 * @param implementationClass the class to resolve type variables against 067 * @return the corresponding generic parameter or return type 068 */ 069 public static Class<?> resolveParameterType(MethodParameter methodParameter, Class<?> implementationClass) { 070 Assert.notNull(methodParameter, "MethodParameter must not be null"); 071 Assert.notNull(implementationClass, "Class must not be null"); 072 methodParameter.setContainingClass(implementationClass); 073 ResolvableType.resolveMethodParameter(methodParameter); 074 return methodParameter.getParameterType(); 075 } 076 077 /** 078 * Determine the target type for the generic return type of the given method, 079 * where formal type variables are declared on the given class. 080 * @param method the method to introspect 081 * @param clazz the class to resolve type variables against 082 * @return the corresponding generic parameter or return type 083 */ 084 public static Class<?> resolveReturnType(Method method, Class<?> clazz) { 085 Assert.notNull(method, "Method must not be null"); 086 Assert.notNull(clazz, "Class must not be null"); 087 return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType()); 088 } 089 090 /** 091 * Determine the target type for the generic return type of the given 092 * <em>generic method</em>, where formal type variables are declared on 093 * the given method itself. 094 * <p>For example, given a factory method with the following signature, 095 * if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected 096 * method for {@code creatProxy()} and an {@code Object[]} array containing 097 * {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will 098 * infer that the target return type is {@code MyService}. 099 * <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre> 100 * <h4>Possible Return Values</h4> 101 * <ul> 102 * <li>the target return type, if it can be inferred</li> 103 * <li>the {@linkplain Method#getReturnType() standard return type}, if 104 * the given {@code method} does not declare any {@linkplain 105 * Method#getTypeParameters() formal type variables}</li> 106 * <li>the {@linkplain Method#getReturnType() standard return type}, if the 107 * target return type cannot be inferred (e.g., due to type erasure)</li> 108 * <li>{@code null}, if the length of the given arguments array is shorter 109 * than the length of the {@linkplain 110 * Method#getGenericParameterTypes() formal argument list} for the given 111 * method</li> 112 * </ul> 113 * @param method the method to introspect, never {@code null} 114 * @param args the arguments that will be supplied to the method when it is 115 * invoked (never {@code null}) 116 * @param classLoader the ClassLoader to resolve class names against, if necessary 117 * (may be {@code null}) 118 * @return the resolved target return type, the standard return type, or {@code null} 119 * @since 3.2.5 120 * @deprecated as of Spring Framework 4.3.8, superseded by {@link ResolvableType} usage 121 */ 122 @Deprecated 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 191 /** 192 * Resolve the single type argument of the given generic interface against the given 193 * target method which is assumed to return the given interface or an implementation 194 * of it. 195 * @param method the target method to check the return type of 196 * @param genericIfc the generic interface or superclass to resolve the type argument from 197 * @return the resolved parameter type of the method return type, or {@code null} 198 * if not resolvable or if the single argument is of type {@link WildcardType}. 199 */ 200 public static Class<?> resolveReturnTypeArgument(Method method, Class<?> genericIfc) { 201 Assert.notNull(method, "Method must not be null"); 202 ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc); 203 if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) { 204 return null; 205 } 206 return getSingleGeneric(resolvableType); 207 } 208 209 /** 210 * Resolve the single type argument of the given generic interface against 211 * the given target class which is assumed to implement the generic interface 212 * and possibly declare a concrete type for its type variable. 213 * @param clazz the target class to check against 214 * @param genericIfc the generic interface or superclass to resolve the type argument from 215 * @return the resolved type of the argument, or {@code null} if not resolvable 216 */ 217 public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) { 218 ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc); 219 if (!resolvableType.hasGenerics()) { 220 return null; 221 } 222 return getSingleGeneric(resolvableType); 223 } 224 225 private static Class<?> getSingleGeneric(ResolvableType resolvableType) { 226 if (resolvableType.getGenerics().length > 1) { 227 throw new IllegalArgumentException("Expected 1 type argument on generic interface [" + 228 resolvableType + "] but found " + resolvableType.getGenerics().length); 229 } 230 return resolvableType.getGeneric().resolve(); 231 } 232 233 234 /** 235 * Resolve the type arguments of the given generic interface against the given 236 * target class which is assumed to implement the generic interface and possibly 237 * declare concrete types for its type variables. 238 * @param clazz the target class to check against 239 * @param genericIfc the generic interface or superclass to resolve the type argument from 240 * @return the resolved type of each argument, with the array size matching the 241 * number of actual type arguments, or {@code null} if not resolvable 242 */ 243 public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericIfc) { 244 ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc); 245 if (!type.hasGenerics() || type.isEntirelyUnresolvable()) { 246 return null; 247 } 248 return type.resolveGenerics(Object.class); 249 } 250 251 /** 252 * Resolve the specified generic type against the given TypeVariable map. 253 * <p>Used by Spring Data. 254 * @param genericType the generic type to resolve 255 * @param map the TypeVariable Map to resolved against 256 * @return the type if it resolves to a Class, or {@code Object.class} otherwise 257 */ 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}. 266 * Searches all super types, enclosing types and interfaces. 267 * @see #resolveType(Type, Map) 268 */ 269 @SuppressWarnings("rawtypes") 270 public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) { 271 Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz); 272 if (typeVariableMap == null) { 273 typeVariableMap = new HashMap<TypeVariable, Type>(); 274 buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap); 275 typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap)); 276 } 277 return typeVariableMap; 278 } 279 280 @SuppressWarnings("rawtypes") 281 private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> typeVariableMap) { 282 if (type != ResolvableType.NONE) { 283 if (type.getType() instanceof ParameterizedType) { 284 TypeVariable<?>[] variables = type.resolve().getTypeParameters(); 285 for (int i = 0; i < variables.length; i++) { 286 ResolvableType generic = type.getGeneric(i); 287 while (generic.getType() instanceof TypeVariable<?>) { 288 generic = generic.resolveType(); 289 } 290 if (generic != ResolvableType.NONE) { 291 typeVariableMap.put(variables[i], generic.getType()); 292 } 293 } 294 } 295 buildTypeVariableMap(type.getSuperType(), typeVariableMap); 296 for (ResolvableType interfaceType : type.getInterfaces()) { 297 buildTypeVariableMap(interfaceType, typeVariableMap); 298 } 299 if (type.resolve().isMemberClass()) { 300 buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap); 301 } 302 } 303 } 304 305 306 @SuppressWarnings({"serial", "rawtypes"}) 307 private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver { 308 309 private final Map<TypeVariable, Type> typeVariableMap; 310 311 public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) { 312 this.typeVariableMap = typeVariableMap; 313 } 314 315 @Override 316 public ResolvableType resolveVariable(TypeVariable<?> variable) { 317 Type type = this.typeVariableMap.get(variable); 318 return (type != null ? ResolvableType.forType(type) : null); 319 } 320 321 @Override 322 public Object getSource() { 323 return this.typeVariableMap; 324 } 325 } 326 327}