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}