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