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}