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}