001/* 002 * Copyright 2002-2020 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.lang.Nullable; 029import org.springframework.util.Assert; 030import org.springframework.util.ConcurrentReferenceHashMap; 031 032/** 033 * Helper class for resolving generic types against type variables. 034 * 035 * <p>Mainly intended for usage within the framework, resolving method 036 * parameter types even when they are declared generically. 037 * 038 * @author Juergen Hoeller 039 * @author Rob Harrop 040 * @author Sam Brannen 041 * @author Phillip Webb 042 * @since 2.5.2 043 */ 044public final class GenericTypeResolver { 045 046 /** Cache from Class to TypeVariable Map. */ 047 @SuppressWarnings("rawtypes") 048 private static final Map<Class<?>, Map<TypeVariable, Type>> typeVariableCache = new ConcurrentReferenceHashMap<>(); 049 050 051 private GenericTypeResolver() { 052 } 053 054 055 /** 056 * Determine the target type for the given generic parameter type. 057 * @param methodParameter the method parameter specification 058 * @param implementationClass the class to resolve type variables against 059 * @return the corresponding generic parameter or return type 060 * @deprecated since 5.2 in favor of {@code methodParameter.withContainingClass(implementationClass).getParameterType()} 061 */ 062 @Deprecated 063 public static Class<?> resolveParameterType(MethodParameter methodParameter, Class<?> implementationClass) { 064 Assert.notNull(methodParameter, "MethodParameter must not be null"); 065 Assert.notNull(implementationClass, "Class must not be null"); 066 methodParameter.setContainingClass(implementationClass); 067 return methodParameter.getParameterType(); 068 } 069 070 /** 071 * Determine the target type for the generic return type of the given method, 072 * where formal type variables are declared on the given class. 073 * @param method the method to introspect 074 * @param clazz the class to resolve type variables against 075 * @return the corresponding generic parameter or return type 076 */ 077 public static Class<?> resolveReturnType(Method method, Class<?> clazz) { 078 Assert.notNull(method, "Method must not be null"); 079 Assert.notNull(clazz, "Class must not be null"); 080 return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType()); 081 } 082 083 /** 084 * Resolve the single type argument of the given generic interface against the given 085 * target method which is assumed to return the given interface or an implementation 086 * of it. 087 * @param method the target method to check the return type of 088 * @param genericIfc the generic interface or superclass to resolve the type argument from 089 * @return the resolved parameter type of the method return type, or {@code null} 090 * if not resolvable or if the single argument is of type {@link WildcardType}. 091 */ 092 @Nullable 093 public static Class<?> resolveReturnTypeArgument(Method method, Class<?> genericIfc) { 094 Assert.notNull(method, "Method must not be null"); 095 ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc); 096 if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) { 097 return null; 098 } 099 return getSingleGeneric(resolvableType); 100 } 101 102 /** 103 * Resolve the single type argument of the given generic interface against 104 * the given target class which is assumed to implement the generic interface 105 * and possibly declare a concrete type for its type variable. 106 * @param clazz the target class to check against 107 * @param genericIfc the generic interface or superclass to resolve the type argument from 108 * @return the resolved type of the argument, or {@code null} if not resolvable 109 */ 110 @Nullable 111 public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) { 112 ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc); 113 if (!resolvableType.hasGenerics()) { 114 return null; 115 } 116 return getSingleGeneric(resolvableType); 117 } 118 119 @Nullable 120 private static Class<?> getSingleGeneric(ResolvableType resolvableType) { 121 Assert.isTrue(resolvableType.getGenerics().length == 1, 122 () -> "Expected 1 type argument on generic interface [" + resolvableType + 123 "] but found " + resolvableType.getGenerics().length); 124 return resolvableType.getGeneric().resolve(); 125 } 126 127 128 /** 129 * Resolve the type arguments of the given generic interface against the given 130 * target class which is assumed to implement the generic interface and possibly 131 * declare concrete types for its type variables. 132 * @param clazz the target class to check against 133 * @param genericIfc the generic interface or superclass to resolve the type argument from 134 * @return the resolved type of each argument, with the array size matching the 135 * number of actual type arguments, or {@code null} if not resolvable 136 */ 137 @Nullable 138 public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericIfc) { 139 ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc); 140 if (!type.hasGenerics() || type.isEntirelyUnresolvable()) { 141 return null; 142 } 143 return type.resolveGenerics(Object.class); 144 } 145 146 /** 147 * Resolve the given generic type against the given context class, 148 * substituting type variables as far as possible. 149 * @param genericType the (potentially) generic type 150 * @param contextClass a context class for the target type, for example a class 151 * in which the target type appears in a method signature (can be {@code null}) 152 * @return the resolved type (possibly the given generic type as-is) 153 * @since 5.0 154 */ 155 public static Type resolveType(Type genericType, @Nullable Class<?> contextClass) { 156 if (contextClass != null) { 157 if (genericType instanceof TypeVariable) { 158 ResolvableType resolvedTypeVariable = resolveVariable( 159 (TypeVariable<?>) genericType, ResolvableType.forClass(contextClass)); 160 if (resolvedTypeVariable != ResolvableType.NONE) { 161 Class<?> resolved = resolvedTypeVariable.resolve(); 162 if (resolved != null) { 163 return resolved; 164 } 165 } 166 } 167 else if (genericType instanceof ParameterizedType) { 168 ResolvableType resolvedType = ResolvableType.forType(genericType); 169 if (resolvedType.hasUnresolvableGenerics()) { 170 ParameterizedType parameterizedType = (ParameterizedType) genericType; 171 Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length]; 172 Type[] typeArguments = parameterizedType.getActualTypeArguments(); 173 ResolvableType contextType = ResolvableType.forClass(contextClass); 174 for (int i = 0; i < typeArguments.length; i++) { 175 Type typeArgument = typeArguments[i]; 176 if (typeArgument instanceof TypeVariable) { 177 ResolvableType resolvedTypeArgument = resolveVariable( 178 (TypeVariable<?>) typeArgument, contextType); 179 if (resolvedTypeArgument != ResolvableType.NONE) { 180 generics[i] = resolvedTypeArgument.resolve(); 181 } 182 else { 183 generics[i] = ResolvableType.forType(typeArgument).resolve(); 184 } 185 } 186 else { 187 generics[i] = ResolvableType.forType(typeArgument).resolve(); 188 } 189 } 190 Class<?> rawClass = resolvedType.getRawClass(); 191 if (rawClass != null) { 192 return ResolvableType.forClassWithGenerics(rawClass, generics).getType(); 193 } 194 } 195 } 196 } 197 return genericType; 198 } 199 200 private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) { 201 ResolvableType resolvedType; 202 if (contextType.hasGenerics()) { 203 resolvedType = ResolvableType.forType(typeVariable, contextType); 204 if (resolvedType.resolve() != null) { 205 return resolvedType; 206 } 207 } 208 209 ResolvableType superType = contextType.getSuperType(); 210 if (superType != ResolvableType.NONE) { 211 resolvedType = resolveVariable(typeVariable, superType); 212 if (resolvedType.resolve() != null) { 213 return resolvedType; 214 } 215 } 216 for (ResolvableType ifc : contextType.getInterfaces()) { 217 resolvedType = resolveVariable(typeVariable, ifc); 218 if (resolvedType.resolve() != null) { 219 return resolvedType; 220 } 221 } 222 return ResolvableType.NONE; 223 } 224 225 /** 226 * Resolve the specified generic type against the given TypeVariable map. 227 * <p>Used by Spring Data. 228 * @param genericType the generic type to resolve 229 * @param map the TypeVariable Map to resolved against 230 * @return the type if it resolves to a Class, or {@code Object.class} otherwise 231 */ 232 @SuppressWarnings("rawtypes") 233 public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> map) { 234 return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).toClass(); 235 } 236 237 /** 238 * Build a mapping of {@link TypeVariable#getName TypeVariable names} to 239 * {@link Class concrete classes} for the specified {@link Class}. 240 * Searches all super types, enclosing types and interfaces. 241 * @see #resolveType(Type, Map) 242 */ 243 @SuppressWarnings("rawtypes") 244 public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) { 245 Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz); 246 if (typeVariableMap == null) { 247 typeVariableMap = new HashMap<>(); 248 buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap); 249 typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap)); 250 } 251 return typeVariableMap; 252 } 253 254 @SuppressWarnings("rawtypes") 255 private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> typeVariableMap) { 256 if (type != ResolvableType.NONE) { 257 Class<?> resolved = type.resolve(); 258 if (resolved != null && type.getType() instanceof ParameterizedType) { 259 TypeVariable<?>[] variables = resolved.getTypeParameters(); 260 for (int i = 0; i < variables.length; i++) { 261 ResolvableType generic = type.getGeneric(i); 262 while (generic.getType() instanceof TypeVariable<?>) { 263 generic = generic.resolveType(); 264 } 265 if (generic != ResolvableType.NONE) { 266 typeVariableMap.put(variables[i], generic.getType()); 267 } 268 } 269 } 270 buildTypeVariableMap(type.getSuperType(), typeVariableMap); 271 for (ResolvableType interfaceType : type.getInterfaces()) { 272 buildTypeVariableMap(interfaceType, typeVariableMap); 273 } 274 if (resolved != null && resolved.isMemberClass()) { 275 buildTypeVariableMap(ResolvableType.forClass(resolved.getEnclosingClass()), typeVariableMap); 276 } 277 } 278 } 279 280 281 @SuppressWarnings({"serial", "rawtypes"}) 282 private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver { 283 284 private final Map<TypeVariable, Type> typeVariableMap; 285 286 public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) { 287 this.typeVariableMap = typeVariableMap; 288 } 289 290 @Override 291 @Nullable 292 public ResolvableType resolveVariable(TypeVariable<?> variable) { 293 Type type = this.typeVariableMap.get(variable); 294 return (type != null ? ResolvableType.forType(type) : null); 295 } 296 297 @Override 298 public Object getSource() { 299 return this.typeVariableMap; 300 } 301 } 302 303}