001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.ws.util.spring; 015 016import java.lang.reflect.GenericArrayType; 017import java.lang.reflect.Method; 018import java.lang.reflect.ParameterizedType; 019import java.lang.reflect.Type; 020import java.lang.reflect.TypeVariable; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026/** 027 * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the 028 * {@link Method} being bridged. 029 * <p>Given a synthetic {@link Method#isBridge bridge Method} returns the {@link Method} 030 * being bridged. A bridge method may be created by the compiler when extending a 031 * parameterized type whose methods have parameterized arguments. During runtime 032 * invocation the bridge {@link Method} may be invoked and/or used via reflection. 033 * When attempting to locate annotations on {@link Method Methods}, it is wise to check 034 * for bridge {@link Method Methods} as appropriate and find the bridged {@link Method}. 035 * <p>See <a href="http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.4.5"> 036 * The Java Language Specification</a> for more details on the use of bridge methods. 037 * <p>Only usable on JDK 1.5 and higher. Use an appropriate {@link JdkVersion} 038 * check before calling this class, if a fallback for JDK 1.4 is desirable. 039 * 040 * @author Rob Harrop 041 * @author Juergen Hoeller 042 * @see JdkVersion 043 * @since 2.0 044 */ 045public abstract class BridgeMethodResolver { 046 047 /** 048 * Find the original method for the supplied {@link Method bridge Method}. 049 * <p>It is safe to call this method passing in a non-bridge {@link Method} instance. 050 * In such a case, the supplied {@link Method} instance is returned directly to the caller. 051 * Callers are <strong>not</strong> required to check for bridging before calling this method. 052 * 053 * @throws IllegalStateException if no bridged {@link Method} can be found 054 */ 055 public static Method findBridgedMethod(Method bridgeMethod) { 056 Assert.notNull(bridgeMethod, "Method must not be null"); 057 058 if (!bridgeMethod.isBridge()) { 059 return bridgeMethod; 060 } 061 062 // Gather all methods with matching name and parameter size. 063 List candidateMethods = new ArrayList(); 064 Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass()); 065 for (int i = 0; i < methods.length; i++) { 066 Method candidateMethod = methods[i]; 067 if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) { 068 candidateMethods.add(candidateMethod); 069 } 070 } 071 072 Method result; 073 // Now perform simple quick checks. 074 if (candidateMethods.size() == 1) { 075 result = (Method) candidateMethods.get(0); 076 } else { 077 result = searchCandidates(candidateMethods, bridgeMethod); 078 } 079 080 if (result == null) { 081 throw new IllegalStateException( 082 "Unable to locate bridged method for bridge method '" + bridgeMethod + "'"); 083 } 084 085 return result; 086 } 087 088 /** 089 * Search for the bridged method in the given candidates. 090 * 091 * @param candidateMethods the List of candidate Methods 092 * @param bridgeMethod the bridge method 093 * 094 * @return the bridged method, or <code>null</code> if none found 095 */ 096 private static Method searchCandidates(List candidateMethods, Method bridgeMethod) { 097 Map typeParameterMap = createTypeVariableMap(bridgeMethod.getDeclaringClass()); 098 for (int i = 0; i < candidateMethods.size(); i++) { 099 Method candidateMethod = (Method) candidateMethods.get(i); 100 if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) { 101 return candidateMethod; 102 } 103 } 104 return null; 105 } 106 107 /** 108 * Return <code>true</code> if the supplied '<code>candidateMethod</code>' can be 109 * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged} 110 * by the supplied {@link Method bridge Method}. This method performs inexpensive 111 * checks and can be used quickly filter for a set of possible matches. 112 */ 113 private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) { 114 return (!candidateMethod.isBridge() 115 && !candidateMethod.equals(bridgeMethod) 116 && candidateMethod.getName().equals(bridgeMethod.getName()) 117 && candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length); 118 } 119 120 /** 121 * Determine whether or not the bridge {@link Method} is the bridge for the 122 * supplied candidate {@link Method}. 123 */ 124 static boolean isBridgeMethodFor( 125 Method bridgeMethod, Method candidateMethod, Map typeVariableMap) { 126 if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) { 127 return true; 128 } 129 Method method = findGenericDeclaration(bridgeMethod); 130 return (method != null ? isResolvedTypeMatch(method, candidateMethod, typeVariableMap) : false); 131 } 132 133 /** 134 * Search for the generic {@link Method} declaration whose erased signature 135 * matches that of the supplied bridge method. 136 * 137 * @throws IllegalStateException if the generic declaration cannot be found 138 */ 139 private static Method findGenericDeclaration(Method bridgeMethod) { 140 // Search parent types for method that has same signature as bridge. 141 Class superclass = bridgeMethod.getDeclaringClass().getSuperclass(); 142 while (!Object.class.equals(superclass)) { 143 Method method = searchForMatch(superclass, bridgeMethod); 144 if (method != null && !method.isBridge()) { 145 return method; 146 } 147 superclass = superclass.getSuperclass(); 148 } 149 150 // Search interfaces. 151 Class[] interfaces = ClassUtils.getAllInterfacesForClass(bridgeMethod.getDeclaringClass()); 152 for (int i = 0; i < interfaces.length; i++) { 153 Class anInterface = interfaces[i]; 154 Method method = searchForMatch(anInterface, bridgeMethod); 155 if (method != null && !method.isBridge()) { 156 return method; 157 } 158 } 159 160 return null; 161 } 162 163 /** 164 * Return <code>true</code> if the {@link Type} signature of both the supplied 165 * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method} 166 * are equal after resolving all {@link TypeVariable TypeVariables} using the supplied 167 * {@link #createTypeVariableMap TypeVariable Map}, otherwise returns <code>false</code>. 168 */ 169 private static boolean isResolvedTypeMatch( 170 Method genericMethod, Method candidateMethod, Map typeVariableMap) { 171 Type[] genericParameters = genericMethod.getGenericParameterTypes(); 172 Class[] candidateParameters = candidateMethod.getParameterTypes(); 173 if (genericParameters.length != candidateParameters.length) { 174 return false; 175 } 176 for (int i = 0; i < genericParameters.length; i++) { 177 Type genericParameter = genericParameters[i]; 178 Class candidateParameter = candidateParameters[i]; 179 if (candidateParameter.isArray()) { 180 // An array type: compare the component type. 181 Type rawType = getRawType(genericParameter, typeVariableMap); 182 if (rawType instanceof GenericArrayType) { 183 if (!candidateParameter 184 .getComponentType() 185 .equals( 186 getRawType( 187 ((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) { 188 return false; 189 } 190 break; 191 } 192 } 193 // A non-array type: compare the type itself. 194 if (!candidateParameter.equals(getRawType(genericParameter, typeVariableMap))) { 195 return false; 196 } 197 } 198 return true; 199 } 200 201 /** 202 * Determine the raw type for the given generic parameter type. 203 */ 204 private static Type getRawType(Type genericType, Map typeVariableMap) { 205 if (genericType instanceof TypeVariable) { 206 TypeVariable tv = (TypeVariable) genericType; 207 Type result = (Type) typeVariableMap.get(tv); 208 return (result != null ? result : Object.class); 209 } else if (genericType instanceof ParameterizedType) { 210 return ((ParameterizedType) genericType).getRawType(); 211 } else { 212 return genericType; 213 } 214 } 215 216 /** 217 * If the supplied {@link Class} has a declared {@link Method} whose signature matches 218 * that of the supplied {@link Method}, then this matching {@link Method} is returned, 219 * otherwise <code>null</code> is returned. 220 */ 221 private static Method searchForMatch(Class type, Method bridgeMethod) { 222 return ReflectionUtils.findMethod( 223 type, bridgeMethod.getName(), bridgeMethod.getParameterTypes()); 224 } 225 226 /** 227 * Build a mapping of {@link TypeVariable#getName TypeVariable names} to concrete 228 * {@link Class} for the specified {@link Class}. Searches all super types, 229 * enclosing types and interfaces. 230 */ 231 static Map createTypeVariableMap(Class cls) { 232 Map typeVariableMap = new HashMap(); 233 234 // interfaces 235 extractTypeVariablesFromGenericInterfaces(cls.getGenericInterfaces(), typeVariableMap); 236 237 // super class 238 Type genericType = cls.getGenericSuperclass(); 239 Class type = cls.getSuperclass(); 240 while (!Object.class.equals(type)) { 241 if (genericType instanceof ParameterizedType) { 242 ParameterizedType pt = (ParameterizedType) genericType; 243 populateTypeMapFromParameterizedType(pt, typeVariableMap); 244 } 245 extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), typeVariableMap); 246 genericType = type.getGenericSuperclass(); 247 type = type.getSuperclass(); 248 } 249 250 // enclosing class 251 type = cls; 252 while (type.isMemberClass()) { 253 genericType = type.getGenericSuperclass(); 254 if (genericType instanceof ParameterizedType) { 255 ParameterizedType pt = (ParameterizedType) genericType; 256 populateTypeMapFromParameterizedType(pt, typeVariableMap); 257 } 258 type = type.getEnclosingClass(); 259 } 260 261 return typeVariableMap; 262 } 263 264 private static void extractTypeVariablesFromGenericInterfaces( 265 Type[] genericInterfaces, Map typeVariableMap) { 266 for (int i = 0; i < genericInterfaces.length; i++) { 267 Type genericInterface = genericInterfaces[i]; 268 if (genericInterface instanceof ParameterizedType) { 269 ParameterizedType pt = (ParameterizedType) genericInterface; 270 populateTypeMapFromParameterizedType(pt, typeVariableMap); 271 if (pt.getRawType() instanceof Class) { 272 extractTypeVariablesFromGenericInterfaces( 273 ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap); 274 } 275 } else if (genericInterface instanceof Class) { 276 extractTypeVariablesFromGenericInterfaces( 277 ((Class) genericInterface).getGenericInterfaces(), typeVariableMap); 278 } 279 } 280 } 281 282 /** 283 * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType} 284 * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} -> 285 * concrete type to the supplied {@link Map}. 286 * <p>Consider this case: 287 * <pre class="code> 288 * public interface Foo<S, T> { 289 * .. 290 * } 291 * public class FooImpl implements Foo<String, Integer> { 292 * .. 293 * }</pre> 294 * For '<code>FooImpl</code>' the following mappings would be added to the {@link Map}: 295 * {S=java.lang.String, T=java.lang.Integer}. 296 */ 297 private static void populateTypeMapFromParameterizedType( 298 ParameterizedType type, Map typeVariableMap) { 299 if (type.getRawType() instanceof Class) { 300 Type[] actualTypeArguments = type.getActualTypeArguments(); 301 TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters(); 302 for (int i = 0; i < actualTypeArguments.length; i++) { 303 Type actualTypeArgument = actualTypeArguments[i]; 304 TypeVariable variable = typeVariables[i]; 305 if (actualTypeArgument instanceof Class) { 306 typeVariableMap.put(variable, actualTypeArgument); 307 } else if (actualTypeArgument instanceof GenericArrayType) { 308 typeVariableMap.put(variable, actualTypeArgument); 309 } else if (actualTypeArgument instanceof ParameterizedType) { 310 typeVariableMap.put(variable, ((ParameterizedType) actualTypeArgument).getRawType()); 311 } else if (actualTypeArgument instanceof TypeVariable) { 312 // We have a type that is parameterized at instantiation time 313 // the nearest match on the bridge method will be the bounded type. 314 TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument; 315 Type resolvedType = (Type) typeVariableMap.get(typeVariableArgument); 316 if (resolvedType == null) { 317 resolvedType = extractClassForTypeVariable(typeVariableArgument); 318 } 319 if (resolvedType != null) { 320 typeVariableMap.put(variable, resolvedType); 321 } 322 } 323 } 324 } 325 } 326 327 /** 328 * Extracts the bound '<code>Class</code>' for a give {@link TypeVariable}. 329 */ 330 private static Class extractClassForTypeVariable(TypeVariable typeVariable) { 331 Type[] bounds = typeVariable.getBounds(); 332 Type result = null; 333 if (bounds.length > 0) { 334 Type bound = bounds[0]; 335 if (bound instanceof ParameterizedType) { 336 result = ((ParameterizedType) bound).getRawType(); 337 } else if (bound instanceof Class) { 338 result = bound; 339 } else if (bound instanceof TypeVariable) { 340 result = extractClassForTypeVariable((TypeVariable) bound); 341 } 342 } 343 return (result instanceof Class ? (Class) result : null); 344 } 345}