001/*
002 * Copyright 2002-2019 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.Type;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.List;
024import java.util.Map;
025
026import org.springframework.lang.Nullable;
027import org.springframework.util.ClassUtils;
028import org.springframework.util.ConcurrentReferenceHashMap;
029import org.springframework.util.ReflectionUtils;
030import org.springframework.util.ReflectionUtils.MethodFilter;
031
032/**
033 * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
034 * {@link Method} being bridged.
035 *
036 * <p>Given a synthetic {@link Method#isBridge bridge Method} returns the {@link Method}
037 * being bridged. A bridge method may be created by the compiler when extending a
038 * parameterized type whose methods have parameterized arguments. During runtime
039 * invocation the bridge {@link Method} may be invoked and/or used via reflection.
040 * When attempting to locate annotations on {@link Method Methods}, it is wise to check
041 * for bridge {@link Method Methods} as appropriate and find the bridged {@link Method}.
042 *
043 * <p>See <a href="https://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.4.5">
044 * The Java Language Specification</a> for more details on the use of bridge methods.
045 *
046 * @author Rob Harrop
047 * @author Juergen Hoeller
048 * @author Phillip Webb
049 * @since 2.0
050 */
051public final class BridgeMethodResolver {
052
053        private static final Map<Method, Method> cache = new ConcurrentReferenceHashMap<>();
054
055        private BridgeMethodResolver() {
056        }
057
058
059        /**
060         * Find the original method for the supplied {@link Method bridge Method}.
061         * <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
062         * In such a case, the supplied {@link Method} instance is returned directly to the caller.
063         * Callers are <strong>not</strong> required to check for bridging before calling this method.
064         * @param bridgeMethod the method to introspect
065         * @return the original method (either the bridged method or the passed-in method
066         * if no more specific one could be found)
067         */
068        public static Method findBridgedMethod(Method bridgeMethod) {
069                if (!bridgeMethod.isBridge()) {
070                        return bridgeMethod;
071                }
072                Method bridgedMethod = cache.get(bridgeMethod);
073                if (bridgedMethod == null) {
074                        // Gather all methods with matching name and parameter size.
075                        List<Method> candidateMethods = new ArrayList<>();
076                        MethodFilter filter = candidateMethod ->
077                                        isBridgedCandidateFor(candidateMethod, bridgeMethod);
078                        ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
079                        if (!candidateMethods.isEmpty()) {
080                                bridgedMethod = candidateMethods.size() == 1 ?
081                                                candidateMethods.get(0) :
082                                                searchCandidates(candidateMethods, bridgeMethod);
083                        }
084                        if (bridgedMethod == null) {
085                                // A bridge method was passed in but we couldn't find the bridged method.
086                                // Let's proceed with the passed-in method and hope for the best...
087                                bridgedMethod = bridgeMethod;
088                        }
089                        cache.put(bridgeMethod, bridgedMethod);
090                }
091                return bridgedMethod;
092        }
093
094        /**
095         * Returns {@code true} if the supplied '{@code candidateMethod}' can be
096         * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged}
097         * by the supplied {@link Method bridge Method}. This method performs inexpensive
098         * checks and can be used quickly filter for a set of possible matches.
099         */
100        private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
101                return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
102                                candidateMethod.getName().equals(bridgeMethod.getName()) &&
103                                candidateMethod.getParameterCount() == bridgeMethod.getParameterCount());
104        }
105
106        /**
107         * Searches for the bridged method in the given candidates.
108         * @param candidateMethods the List of candidate Methods
109         * @param bridgeMethod the bridge method
110         * @return the bridged method, or {@code null} if none found
111         */
112        @Nullable
113        private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
114                if (candidateMethods.isEmpty()) {
115                        return null;
116                }
117                Method previousMethod = null;
118                boolean sameSig = true;
119                for (Method candidateMethod : candidateMethods) {
120                        if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
121                                return candidateMethod;
122                        }
123                        else if (previousMethod != null) {
124                                sameSig = sameSig &&
125                                                Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
126                        }
127                        previousMethod = candidateMethod;
128                }
129                return (sameSig ? candidateMethods.get(0) : null);
130        }
131
132        /**
133         * Determines whether or not the bridge {@link Method} is the bridge for the
134         * supplied candidate {@link Method}.
135         */
136        static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
137                if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
138                        return true;
139                }
140                Method method = findGenericDeclaration(bridgeMethod);
141                return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
142        }
143
144        /**
145         * Returns {@code true} if the {@link Type} signature of both the supplied
146         * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method}
147         * are equal after resolving all types against the declaringType, otherwise
148         * returns {@code false}.
149         */
150        private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
151                Type[] genericParameters = genericMethod.getGenericParameterTypes();
152                if (genericParameters.length != candidateMethod.getParameterCount()) {
153                        return false;
154                }
155                Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
156                for (int i = 0; i < candidateParameters.length; i++) {
157                        ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
158                        Class<?> candidateParameter = candidateParameters[i];
159                        if (candidateParameter.isArray()) {
160                                // An array type: compare the component type.
161                                if (!candidateParameter.getComponentType().equals(genericParameter.getComponentType().toClass())) {
162                                        return false;
163                                }
164                        }
165                        // A non-array type: compare the type itself.
166                        if (!candidateParameter.equals(genericParameter.toClass())) {
167                                return false;
168                        }
169                }
170                return true;
171        }
172
173        /**
174         * Searches for the generic {@link Method} declaration whose erased signature
175         * matches that of the supplied bridge method.
176         * @throws IllegalStateException if the generic declaration cannot be found
177         */
178        @Nullable
179        private static Method findGenericDeclaration(Method bridgeMethod) {
180                // Search parent types for method that has same signature as bridge.
181                Class<?> superclass = bridgeMethod.getDeclaringClass().getSuperclass();
182                while (superclass != null && Object.class != superclass) {
183                        Method method = searchForMatch(superclass, bridgeMethod);
184                        if (method != null && !method.isBridge()) {
185                                return method;
186                        }
187                        superclass = superclass.getSuperclass();
188                }
189
190                Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(bridgeMethod.getDeclaringClass());
191                return searchInterfaces(interfaces, bridgeMethod);
192        }
193
194        @Nullable
195        private static Method searchInterfaces(Class<?>[] interfaces, Method bridgeMethod) {
196                for (Class<?> ifc : interfaces) {
197                        Method method = searchForMatch(ifc, bridgeMethod);
198                        if (method != null && !method.isBridge()) {
199                                return method;
200                        }
201                        else {
202                                method = searchInterfaces(ifc.getInterfaces(), bridgeMethod);
203                                if (method != null) {
204                                        return method;
205                                }
206                        }
207                }
208                return null;
209        }
210
211        /**
212         * If the supplied {@link Class} has a declared {@link Method} whose signature matches
213         * that of the supplied {@link Method}, then this matching {@link Method} is returned,
214         * otherwise {@code null} is returned.
215         */
216        @Nullable
217        private static Method searchForMatch(Class<?> type, Method bridgeMethod) {
218                try {
219                        return type.getDeclaredMethod(bridgeMethod.getName(), bridgeMethod.getParameterTypes());
220                }
221                catch (NoSuchMethodException ex) {
222                        return null;
223                }
224        }
225
226        /**
227         * Compare the signatures of the bridge method and the method which it bridges. If
228         * the parameter and return types are the same, it is a 'visibility' bridge method
229         * introduced in Java 6 to fix https://bugs.java.com/view_bug.do?bug_id=6342411.
230         * See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html
231         * @return whether signatures match as described
232         */
233        public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
234                if (bridgeMethod == bridgedMethod) {
235                        return true;
236                }
237                return (bridgeMethod.getReturnType().equals(bridgedMethod.getReturnType()) &&
238                                bridgeMethod.getParameterCount() == bridgedMethod.getParameterCount() &&
239                                Arrays.equals(bridgeMethod.getParameterTypes(), bridgedMethod.getParameterTypes()));
240        }
241
242}