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