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