1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.core;
18
19 import java.lang.reflect.GenericArrayType;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Type;
22 import java.lang.reflect.TypeVariable;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.springframework.util.Assert;
28 import org.springframework.util.ClassUtils;
29 import org.springframework.util.ReflectionUtils;
30
31 /**
32 * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
33 * {@link Method} being bridged.
34 *
35 * <p>Given a synthetic {@link Method#isBridge bridge Method} returns the {@link Method}
36 * being bridged. A bridge method may be created by the compiler when extending a
37 * parameterized type whose methods have parameterized arguments. During runtime
38 * invocation the bridge {@link Method} may be invoked and/or used via reflection.
39 * When attempting to locate annotations on {@link Method Methods}, it is wise to check
40 * for bridge {@link Method Methods} as appropriate and find the bridged {@link Method}.
41 *
42 * <p>See <a href="http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.4.5">
43 * The Java Language Specification</a> for more details on the use of bridge methods.
44 *
45 * <p>Only usable on JDK 1.5 and higher. Use an appropriate {@link JdkVersion}
46 * check before calling this class if a fallback for JDK 1.4 is desirable.
47 *
48 * @author Rob Harrop
49 * @author Juergen Hoeller
50 * @since 2.0
51 * @see JdkVersion
52 */
53 public abstract class BridgeMethodResolver {
54
55 /**
56 * Find the original method for the supplied {@link Method bridge Method}.
57 * <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
58 * In such a case, the supplied {@link Method} instance is returned directly to the caller.
59 * Callers are <strong>not</strong> required to check for bridging before calling this method.
60 * @throws IllegalStateException if no bridged {@link Method} can be found
61 */
62 public static Method findBridgedMethod(Method bridgeMethod) {
63 Assert.notNull(bridgeMethod, "Method must not be null");
64
65 if (!bridgeMethod.isBridge()) {
66 return bridgeMethod;
67 }
68
69 // Gather all methods with matching name and parameter size.
70 List candidateMethods = new ArrayList();
71 Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass());
72 for (int i = 0; i < methods.length; i++) {
73 Method candidateMethod = methods[i];
74 if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) {
75 candidateMethods.add(candidateMethod);
76 }
77 }
78
79 Method result;
80 // Now perform simple quick checks.
81 if (candidateMethods.size() == 1) {
82 result = (Method) candidateMethods.get(0);
83 }
84 else {
85 result = searchCandidates(candidateMethods, bridgeMethod);
86 }
87
88 if (result == null) {
89 throw new IllegalStateException(
90 "Unable to locate bridged method for bridge method '" + bridgeMethod + "'");
91 }
92
93 return result;
94 }
95
96 /**
97 * Searches for the bridged method in the given candidates.
98 * @param candidateMethods the List of candidate Methods
99 * @param bridgeMethod the bridge method
100 * @return the bridged method, or <code>null</code> if none found
101 */
102 private static Method searchCandidates(List candidateMethods, Method bridgeMethod) {
103 Map typeParameterMap = GenericTypeResolver.getTypeVariableMap(bridgeMethod.getDeclaringClass());
104 for (int i = 0; i < candidateMethods.size(); i++) {
105 Method candidateMethod = (Method) candidateMethods.get(i);
106 if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) {
107 return candidateMethod;
108 }
109 }
110 return null;
111 }
112
113 /**
114 * Returns <code>true</code> if the supplied '<code>candidateMethod</code>' can be
115 * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged}
116 * by the supplied {@link Method bridge Method}. This method performs inexpensive
117 * checks and can be used quickly filter for a set of possible matches.
118 */
119 private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
120 return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
121 candidateMethod.getName().equals(bridgeMethod.getName()) &&
122 candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length);
123 }
124
125 /**
126 * Determines whether or not the bridge {@link Method} is the bridge for the
127 * supplied candidate {@link Method}.
128 */
129 static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Map typeVariableMap) {
130 if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) {
131 return true;
132 }
133 Method method = findGenericDeclaration(bridgeMethod);
134 return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap));
135 }
136
137 /**
138 * Searches for the generic {@link Method} declaration whose erased signature
139 * matches that of the supplied bridge method.
140 * @throws IllegalStateException if the generic declaration cannot be found
141 */
142 private static Method findGenericDeclaration(Method bridgeMethod) {
143 // Search parent types for method that has same signature as bridge.
144 Class superclass = bridgeMethod.getDeclaringClass().getSuperclass();
145 while (!Object.class.equals(superclass)) {
146 Method method = searchForMatch(superclass, bridgeMethod);
147 if (method != null && !method.isBridge()) {
148 return method;
149 }
150 superclass = superclass.getSuperclass();
151 }
152
153 // Search interfaces.
154 Class[] interfaces = ClassUtils.getAllInterfacesForClass(bridgeMethod.getDeclaringClass());
155 for (int i = 0; i < interfaces.length; i++) {
156 Class anInterface = interfaces[i];
157 Method method = searchForMatch(anInterface, bridgeMethod);
158 if (method != null && !method.isBridge()) {
159 return method;
160 }
161 }
162
163 return null;
164 }
165
166 /**
167 * Returns <code>true</code> if the {@link Type} signature of both the supplied
168 * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method}
169 * are equal after resolving all {@link TypeVariable TypeVariables} using the supplied
170 * TypeVariable Map, otherwise returns <code>false</code>.
171 */
172 private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Map typeVariableMap) {
173 Type[] genericParameters = genericMethod.getGenericParameterTypes();
174 Class[] candidateParameters = candidateMethod.getParameterTypes();
175 if (genericParameters.length != candidateParameters.length) {
176 return false;
177 }
178 for (int i = 0; i < genericParameters.length; i++) {
179 Type genericParameter = genericParameters[i];
180 Class candidateParameter = candidateParameters[i];
181 if (candidateParameter.isArray()) {
182 // An array type: compare the component type.
183 Type rawType = GenericTypeResolver.getRawType(genericParameter, typeVariableMap);
184 if (rawType instanceof GenericArrayType) {
185 if (!candidateParameter.getComponentType().equals(
186 GenericTypeResolver.resolveType(((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) {
187 return false;
188 }
189 break;
190 }
191 }
192 // A non-array type: compare the type itself.
193 if (!candidateParameter.equals(GenericTypeResolver.resolveType(genericParameter, typeVariableMap))) {
194 return false;
195 }
196 }
197 return true;
198 }
199
200 /**
201 * If the supplied {@link Class} has a declared {@link Method} whose signature matches
202 * that of the supplied {@link Method}, then this matching {@link Method} is returned,
203 * otherwise <code>null</code> is returned.
204 */
205 private static Method searchForMatch(Class type, Method bridgeMethod) {
206 return ReflectionUtils.findMethod(type, bridgeMethod.getName(), bridgeMethod.getParameterTypes());
207 }
208
209 }