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}