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.Proxy;
021import java.util.LinkedHashMap;
022import java.util.LinkedHashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.springframework.lang.Nullable;
027import org.springframework.util.ClassUtils;
028import org.springframework.util.ReflectionUtils;
029
030/**
031 * Defines the algorithm for searching for metadata-associated methods exhaustively
032 * including interfaces and parent classes while also dealing with parameterized methods
033 * as well as common scenarios encountered with interface and class-based proxies.
034 *
035 * <p>Typically, but not necessarily, used for finding annotated handler methods.
036 *
037 * @author Juergen Hoeller
038 * @author Rossen Stoyanchev
039 * @since 4.2.3
040 */
041public final class MethodIntrospector {
042
043        private MethodIntrospector() {
044        }
045
046
047        /**
048         * Select methods on the given target type based on the lookup of associated metadata.
049         * <p>Callers define methods of interest through the {@link MetadataLookup} parameter,
050         * allowing to collect the associated metadata into the result map.
051         * @param targetType the target type to search methods on
052         * @param metadataLookup a {@link MetadataLookup} callback to inspect methods of interest,
053         * returning non-null metadata to be associated with a given method if there is a match,
054         * or {@code null} for no match
055         * @return the selected methods associated with their metadata (in the order of retrieval),
056         * or an empty map in case of no match
057         */
058        public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
059                final Map<Method, T> methodMap = new LinkedHashMap<>();
060                Set<Class<?>> handlerTypes = new LinkedHashSet<>();
061                Class<?> specificHandlerType = null;
062
063                if (!Proxy.isProxyClass(targetType)) {
064                        specificHandlerType = ClassUtils.getUserClass(targetType);
065                        handlerTypes.add(specificHandlerType);
066                }
067                handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
068
069                for (Class<?> currentHandlerType : handlerTypes) {
070                        final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
071
072                        ReflectionUtils.doWithMethods(currentHandlerType, method -> {
073                                Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
074                                T result = metadataLookup.inspect(specificMethod);
075                                if (result != null) {
076                                        Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
077                                        if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
078                                                methodMap.put(specificMethod, result);
079                                        }
080                                }
081                        }, ReflectionUtils.USER_DECLARED_METHODS);
082                }
083
084                return methodMap;
085        }
086
087        /**
088         * Select methods on the given target type based on a filter.
089         * <p>Callers define methods of interest through the {@code MethodFilter} parameter.
090         * @param targetType the target type to search methods on
091         * @param methodFilter a {@code MethodFilter} to help
092         * recognize handler methods of interest
093         * @return the selected methods, or an empty set in case of no match
094         */
095        public static Set<Method> selectMethods(Class<?> targetType, final ReflectionUtils.MethodFilter methodFilter) {
096                return selectMethods(targetType,
097                                (MetadataLookup<Boolean>) method -> (methodFilter.matches(method) ? Boolean.TRUE : null)).keySet();
098        }
099
100        /**
101         * Select an invocable method on the target type: either the given method itself
102         * if actually exposed on the target type, or otherwise a corresponding method
103         * on one of the target type's interfaces or on the target type itself.
104         * <p>Matches on user-declared interfaces will be preferred since they are likely
105         * to contain relevant metadata that corresponds to the method on the target class.
106         * @param method the method to check
107         * @param targetType the target type to search methods on
108         * (typically an interface-based JDK proxy)
109         * @return a corresponding invocable method on the target type
110         * @throws IllegalStateException if the given method is not invocable on the given
111         * target type (typically due to a proxy mismatch)
112         */
113        public static Method selectInvocableMethod(Method method, Class<?> targetType) {
114                if (method.getDeclaringClass().isAssignableFrom(targetType)) {
115                        return method;
116                }
117                try {
118                        String methodName = method.getName();
119                        Class<?>[] parameterTypes = method.getParameterTypes();
120                        for (Class<?> ifc : targetType.getInterfaces()) {
121                                try {
122                                        return ifc.getMethod(methodName, parameterTypes);
123                                }
124                                catch (NoSuchMethodException ex) {
125                                        // Alright, not on this interface then...
126                                }
127                        }
128                        // A final desperate attempt on the proxy class itself...
129                        return targetType.getMethod(methodName, parameterTypes);
130                }
131                catch (NoSuchMethodException ex) {
132                        throw new IllegalStateException(String.format(
133                                        "Need to invoke method '%s' declared on target class '%s', " +
134                                        "but not found in any interface(s) of the exposed proxy type. " +
135                                        "Either pull the method up to an interface or switch to CGLIB " +
136                                        "proxies by enforcing proxy-target-class mode in your configuration.",
137                                        method.getName(), method.getDeclaringClass().getSimpleName()));
138                }
139        }
140
141
142        /**
143         * A callback interface for metadata lookup on a given method.
144         * @param <T> the type of metadata returned
145         */
146        @FunctionalInterface
147        public interface MetadataLookup<T> {
148
149                /**
150                 * Perform a lookup on the given method and return associated metadata, if any.
151                 * @param method the method to inspect
152                 * @return non-null metadata to be associated with a method if there is a match,
153                 * or {@code null} for no match
154                 */
155                @Nullable
156                T inspect(Method method);
157        }
158
159}