• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
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 package android.signature.cts;
17 
18 import android.signature.cts.JDiffClassDescription.JDiffConstructor;
19 import android.signature.cts.JDiffClassDescription.JDiffMethod;
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.AnnotatedElement;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.GenericArrayType;
25 import java.lang.reflect.Member;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.lang.reflect.ParameterizedType;
29 import java.lang.reflect.Type;
30 import java.lang.reflect.TypeVariable;
31 import java.lang.reflect.WildcardType;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 
42 /**
43  * Uses reflection to obtain runtime representations of elements in the API.
44  */
45 public class ReflectionHelper {
46     private static final String TAG = "ReflectionHelper";
47 
48     /**
49      * Finds the reflected class for the class under test.
50      *
51      * @param classDescription the description of the class to find.
52      * @return the reflected class, or null if not found.
53      */
findMatchingClass(JDiffClassDescription classDescription, ClassProvider classProvider)54     public static Class<?> findMatchingClass(JDiffClassDescription classDescription,
55             ClassProvider classProvider) throws ClassNotFoundException {
56         // even if there are no . in the string, split will return an
57         // array of length 1
58         String shortClassName = classDescription.getShortClassName();
59         String[] classNameParts = shortClassName.split("\\.");
60         String packageName = classDescription.getPackageName();
61         String outermostClassName = packageName + "." + classNameParts[0];
62         int firstInnerClassNameIndex = 0;
63 
64         return searchForClass(classProvider, classDescription.getAbsoluteClassName(),
65                 outermostClassName, classNameParts,
66                 firstInnerClassNameIndex);
67     }
68 
searchForClass( ClassProvider classProvider, String absoluteClassName, String outermostClassName, String[] classNameParts, int outerClassNameIndex)69     private static Class<?> searchForClass(
70             ClassProvider classProvider,
71             String absoluteClassName,
72             String outermostClassName, String[] classNameParts,
73             int outerClassNameIndex) throws ClassNotFoundException {
74 
75         Class<?> clz = classProvider.getClass(outermostClassName);
76         if (clz.getCanonicalName().equals(absoluteClassName)) {
77             return clz;
78         }
79 
80         // Then it must be an inner class.
81         for (int x = outerClassNameIndex + 1; x < classNameParts.length; x++) {
82             clz = findInnerClassByName(clz, classNameParts[x]);
83             if (clz == null) {
84                 return null;
85             }
86             if (clz.getCanonicalName().equals(absoluteClassName)) {
87                 return clz;
88             }
89         }
90         return null;
91     }
92 
findMatchingClass(String absoluteClassName, ClassProvider classProvider)93     static Class<?> findMatchingClass(String absoluteClassName, ClassProvider classProvider)
94             throws ClassNotFoundException {
95 
96         String[] classNameParts = absoluteClassName.split("\\.");
97         StringBuilder builder = new StringBuilder();
98         String separator = "";
99         int start;
100         for (start = 0; start < classNameParts.length; start++) {
101             String classNamePart = classNameParts[start];
102             builder.append(separator).append(classNamePart);
103             separator = ".";
104             if (Character.isUpperCase(classNamePart.charAt(0))) {
105                 break;
106             }
107         }
108         String outermostClassName = builder.toString();
109 
110         return searchForClass(classProvider, absoluteClassName, outermostClassName, classNameParts,
111                 start);
112     }
113 
114     /**
115      * Searches the class for the specified inner class.
116      *
117      * @param clz the class to search in.
118      * @param simpleName the simpleName of the class to find
119      * @return the class being searched for, or null if it can't be found.
120      */
findInnerClassByName(Class<?> clz, String simpleName)121     private static Class<?> findInnerClassByName(Class<?> clz, String simpleName) {
122         for (Class<?> c : clz.getDeclaredClasses()) {
123             if (c.getSimpleName().equals(simpleName)) {
124                 return c;
125             }
126         }
127         return null;
128     }
129 
130     /**
131      * Searches available constructor.
132      *
133      * @param runtimeClass the class in which to search.
134      * @param jdiffDes constructor description to find.
135      * @param mismatchReasons a map from rejected constructor to the reason it was rejected.
136      * @return reflected constructor, or null if not found.
137      */
findMatchingConstructor(Class<?> runtimeClass, JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons)138     static Constructor<?> findMatchingConstructor(Class<?> runtimeClass,
139             JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons) {
140 
141         try {
142             return findMatchingConstructorImpl(runtimeClass, jdiffDes, mismatchReasons);
143         } catch (NoClassDefFoundError e) {
144             LogHelper.loge(TAG + ": Could not retrieve constructors of " + runtimeClass, e);
145             return null;
146         }
147     }
148 
findMatchingConstructorImpl(Class<?> runtimeClass, JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons)149     static Constructor<?> findMatchingConstructorImpl(Class<?> runtimeClass,
150             JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons) {
151         for (Constructor<?> c : runtimeClass.getDeclaredConstructors()) {
152             Type[] params = c.getGenericParameterTypes();
153             boolean isStaticClass = ((runtimeClass.getModifiers() & Modifier.STATIC) != 0);
154 
155             int startParamOffset = 0;
156             int numberOfParams = params.length;
157 
158             // non-static inner class -> skip implicit parent pointer
159             // as first arg
160             if (runtimeClass.isMemberClass() && !isStaticClass && params.length >= 1) {
161                 startParamOffset = 1;
162                 --numberOfParams;
163             }
164 
165             ArrayList<String> jdiffParamList = jdiffDes.mParamList;
166             if (jdiffParamList.size() == numberOfParams) {
167                 boolean isFound = true;
168                 // i counts jdiff params, j counts reflected params
169                 int i = 0;
170                 int j = startParamOffset;
171                 while (i < jdiffParamList.size()) {
172                     String expectedParameter = jdiffParamList.get(i);
173                     Type actualParameter = params[j];
174                     if (!compareParam(expectedParameter, actualParameter,
175                             DefaultTypeComparator.INSTANCE)) {
176                         mismatchReasons.put(c,
177                                 String.format("parameter %d mismatch: expected (%s), found (%s)",
178                                         i,
179                                         expectedParameter,
180                                         actualParameter));
181                         isFound = false;
182                         break;
183                     }
184                     ++i;
185                     ++j;
186                 }
187                 if (isFound) {
188                     return c;
189                 }
190             } else {
191                 mismatchReasons.put(c,
192                         String.format("parameter list length mismatch: expected %d, found %d",
193                                 jdiffParamList.size(),
194                                 params.length));
195             }
196         }
197         return null;
198     }
199 
200     /**
201      * Compares the parameter from the API and the parameter from
202      * reflection.
203      *
204      * @param jdiffParam param parsed from the API xml file.
205      * @param reflectionParamType param gotten from the Java reflection.
206      * @param typeComparator compares two types to determine if they are equal.
207      * @return True if the two params match, otherwise return false.
208      */
compareParam(String jdiffParam, Type reflectionParamType, TypeComparator typeComparator)209     private static boolean compareParam(String jdiffParam, Type reflectionParamType,
210             TypeComparator typeComparator) {
211         if (jdiffParam == null) {
212             return false;
213         }
214 
215         String reflectionParam = typeToString(reflectionParamType);
216         // Most things aren't varargs, so just do a simple compare
217         // first.
218         if (typeComparator.compare(jdiffParam, reflectionParam)) {
219             return true;
220         }
221 
222         // Check for varargs.  jdiff reports varargs as ..., while
223         // reflection reports them as []
224         int jdiffParamEndOffset = jdiffParam.indexOf("...");
225         int reflectionParamEndOffset = reflectionParam != null
226                 ? reflectionParam.lastIndexOf("[]") : -1;
227         if (jdiffParamEndOffset != -1 && reflectionParamEndOffset != -1) {
228             jdiffParam = jdiffParam.substring(0, jdiffParamEndOffset);
229             reflectionParam = reflectionParam.substring(0, reflectionParamEndOffset);
230             return typeComparator.compare(jdiffParam, reflectionParam);
231         }
232 
233         return false;
234     }
235 
236     /**
237      * Finds the reflected method specified by the method description.
238      *
239      * @param runtimeClass the class in which to search.
240      * @param method description of the method to find
241      * @param mismatchReasons a map from rejected method to the reason it was rejected, only
242      *     contains methods with the same name.
243      * @return the reflected method, or null if not found.
244      */
findMatchingMethod( Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons)245     static Method findMatchingMethod(
246             Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons) {
247         try {
248             return findMatchingMethodImpl(runtimeClass, method, mismatchReasons);
249         } catch (NoClassDefFoundError e) {
250             LogHelper.loge(TAG + ": Could not retrieve methods of " + runtimeClass, e);
251             return null;
252         }
253     }
254 
findMatchingMethodImpl( Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons)255     static Method findMatchingMethodImpl(
256             Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons) {
257 
258         // Search through the class to find the methods just in case the method was actually
259         // declared in a superclass which is not part of the API and so was made to appear as if
260         // it was declared in each of the hidden class' subclasses. Cannot use getMethods() as that
261         // will only return public methods and the API includes protected methods.
262         Class<?> currentClass = runtimeClass;
263         while (currentClass != null) {
264             Method[] reflectedMethods = currentClass.getDeclaredMethods();
265 
266             for (Method reflectedMethod : reflectedMethods) {
267                 // If the method names aren't equal, the methods can't match.
268                 if (!method.mName.equals(reflectedMethod.getName())) {
269                     continue;
270                 }
271 
272                 if (matchesSignature(method, reflectedMethod, mismatchReasons)) {
273                     return reflectedMethod;
274                 }
275             }
276 
277             currentClass = currentClass.getSuperclass();
278         }
279 
280         return null;
281     }
282 
283     /**
284      * Checks if the two types of methods are the same.
285      *
286      * @param jDiffMethod the jDiffMethod to compare
287      * @param reflectedMethod the reflected method to compare
288      * @return true, if both methods are the same
289      */
matches(JDiffClassDescription.JDiffMethod jDiffMethod, Method reflectedMethod)290     static boolean matches(JDiffClassDescription.JDiffMethod jDiffMethod,
291             Method reflectedMethod) {
292         // If the method names aren't equal, the methods can't match.
293         if (!jDiffMethod.mName.equals(reflectedMethod.getName())) {
294             return false;
295         }
296 
297         Map<Method, String> ignoredReasons = new HashMap<>();
298         return matchesSignature(jDiffMethod, reflectedMethod, ignoredReasons);
299     }
300 
301     /**
302      * Checks if the two types of methods are the same.
303      *
304      * @param jDiffMethod the jDiffMethod to compare
305      * @param reflectedMethod the reflected method to compare
306      * @param mismatchReasons map from method to reason it did not match, used when reporting
307      *     missing methods.
308      * @return true, if both methods are the same
309      */
matchesSignature(JDiffMethod jDiffMethod, Method reflectedMethod, Map<Method, String> mismatchReasons)310     static boolean matchesSignature(JDiffMethod jDiffMethod, Method reflectedMethod,
311             Map<Method, String> mismatchReasons) {
312         // If the method is a bridge then use a special comparator for comparing types as
313         // bridge methods created for generic methods may not have generic signatures.
314         // See b/123558763 for more information.
315         TypeComparator typeComparator = reflectedMethod.isBridge()
316                 ? BridgeTypeComparator.INSTANCE : DefaultTypeComparator.INSTANCE;
317 
318         String jdiffReturnType = jDiffMethod.mReturnType;
319         String reflectionReturnType = typeToString(reflectedMethod.getGenericReturnType());
320 
321         // Next, compare the return types of the two methods.  If
322         // they aren't equal, the methods can't match.
323         if (!typeComparator.compare(jdiffReturnType, reflectionReturnType)) {
324             mismatchReasons.put(reflectedMethod,
325                     String.format("return type mismatch: expected %s, found %s", jdiffReturnType,
326                             reflectionReturnType));
327             return false;
328         }
329 
330         List<String> jdiffParamList = jDiffMethod.mParamList;
331         Type[] params = reflectedMethod.getGenericParameterTypes();
332 
333         // Next, check the method parameters.  If they have different
334         // parameter lengths, the two methods can't match.
335         if (jdiffParamList.size() != params.length) {
336             mismatchReasons.put(reflectedMethod,
337                     String.format("parameter list length mismatch: expected %s, found %s",
338                             jdiffParamList.size(),
339                             params.length));
340             return false;
341         }
342 
343         boolean piecewiseParamsMatch = true;
344 
345         // Compare method parameters piecewise and return true if they all match.
346         for (int i = 0; i < jdiffParamList.size(); i++) {
347             piecewiseParamsMatch &= compareParam(jdiffParamList.get(i), params[i], typeComparator);
348         }
349         if (piecewiseParamsMatch) {
350             return true;
351         }
352 
353         /* NOTE: There are cases where piecewise method parameter checking
354          * fails even though the strings are equal, so compare entire strings
355          * against each other. This is not done by default to avoid a
356          * TransactionTooLargeException.
357          * Additionally, this can fail anyway due to extra
358          * information dug up by reflection.
359          *
360          * TODO: fix parameter equality checking and reflection matching
361          * See https://b.corp.google.com/issues/27726349
362          */
363 
364         StringBuilder reflectedMethodParams = new StringBuilder("");
365         StringBuilder jdiffMethodParams = new StringBuilder("");
366 
367         String sep = "";
368         for (int i = 0; i < jdiffParamList.size(); i++) {
369             jdiffMethodParams.append(sep).append(jdiffParamList.get(i));
370             reflectedMethodParams.append(sep).append(params[i].getTypeName());
371             sep = ", ";
372         }
373 
374         String jDiffFName = jdiffMethodParams.toString();
375         String refName = reflectedMethodParams.toString();
376 
377         boolean signatureMatches = jDiffFName.equals(refName);
378         if (!signatureMatches) {
379             mismatchReasons.put(reflectedMethod,
380                     String.format("parameter signature mismatch: expected (%s), found (%s)",
381                             jDiffFName,
382                             refName));
383         }
384 
385         return signatureMatches;
386     }
387 
388     /**
389      * Converts WildcardType array into a jdiff compatible string..
390      * This is a helper function for typeToString.
391      *
392      * @param types array of types to format.
393      * @return the jdiff formatted string.
394      */
concatWildcardTypes(Type[] types)395     private static String concatWildcardTypes(Type[] types) {
396         StringBuilder sb = new StringBuilder();
397         int elementNum = 0;
398         for (Type t : types) {
399             sb.append(typeToString(t));
400             if (++elementNum < types.length) {
401                 sb.append(" & ");
402             }
403         }
404         return sb.toString();
405     }
406 
407     /**
408      * Converts a Type into a jdiff compatible String.  The returned
409      * types from this function should match the same Strings that
410      * jdiff is providing to us.
411      *
412      * @param type the type to convert.
413      * @return the jdiff formatted string.
414      */
typeToString(Type type)415     public static String typeToString(Type type) {
416         if (type instanceof ParameterizedType) {
417             ParameterizedType pt = (ParameterizedType) type;
418 
419             StringBuilder sb = new StringBuilder();
420             sb.append(typeToString(pt.getRawType()));
421             sb.append("<");
422 
423             int elementNum = 0;
424             Type[] types = pt.getActualTypeArguments();
425             for (Type t : types) {
426                 sb.append(typeToString(t));
427                 if (++elementNum < types.length) {
428                     // Must match separator used in
429                     // android.signature.cts.KtHelper.toDefaultTypeString.
430                     sb.append(",");
431                 }
432             }
433 
434             sb.append(">");
435             return sb.toString();
436         } else if (type instanceof TypeVariable) {
437             return ((TypeVariable<?>) type).getName();
438         } else if (type instanceof Class) {
439             return ((Class<?>) type).getCanonicalName();
440         } else if (type instanceof GenericArrayType) {
441             String typeName = typeToString(((GenericArrayType) type).getGenericComponentType());
442             return typeName + "[]";
443         } else if (type instanceof WildcardType) {
444             WildcardType wt = (WildcardType) type;
445             Type[] lowerBounds = wt.getLowerBounds();
446             if (lowerBounds.length == 0) {
447                 String name = "? extends " + concatWildcardTypes(wt.getUpperBounds());
448 
449                 // Special case for ?
450                 if (name.equals("? extends java.lang.Object")) {
451                     return "?";
452                 } else {
453                     return name;
454                 }
455             } else {
456                 String name = concatWildcardTypes(wt.getUpperBounds()) +
457                         " super " +
458                         concatWildcardTypes(wt.getLowerBounds());
459                 // Another special case for ?
460                 name = name.replace("java.lang.Object", "?");
461                 return name;
462             }
463         } else {
464             throw new RuntimeException("Got an unknown java.lang.Type");
465         }
466     }
467 
468     private final static Pattern REPEATING_ANNOTATION_PATTERN =
469             Pattern.compile("@.*\\(value=\\[(.*)\\]\\)");
470 
hasMatchingAnnotation(AnnotatedElement elem, String annotationSpec)471     public static boolean hasMatchingAnnotation(AnnotatedElement elem, String annotationSpec) {
472         for (Annotation a : elem.getAnnotations()) {
473             if (a.toString().equals(annotationSpec)) {
474                 return true;
475             }
476             // It could be a repeating annotation. In that case, a.toString() returns
477             // "@MyAnnotation$Container(value=[@MyAnnotation(A), @MyAnnotation(B)])"
478             // Then, iterate over @MyAnnotation(A) and @MyAnnotation(B).
479             Matcher m = REPEATING_ANNOTATION_PATTERN.matcher(a.toString());
480             if (m.matches()) {
481                 for (String token : m.group(1).split(", ")) {
482                     if (token.equals(annotationSpec)) {
483                         return true;
484                     }
485                 }
486             }
487         }
488         return false;
489     }
490 
491     /**
492      * Returns a list of constructors which are annotated with the given annotation class.
493      */
getAnnotatedConstructors(Class<?> clazz, String annotationSpec)494     public static Set<Constructor<?>> getAnnotatedConstructors(Class<?> clazz,
495             String annotationSpec) {
496         try {
497             return getAnnotatedConstructorsImpl(clazz, annotationSpec);
498         } catch (NoClassDefFoundError e) {
499             LogHelper.loge(TAG + ": Could not retrieve constructors of " + clazz
500                 + " annotated with " + annotationSpec, e);
501             return Collections.emptySet();
502         }
503     }
504 
getAnnotatedConstructorsImpl(Class<?> clazz, String annotationSpec)505     private static Set<Constructor<?>> getAnnotatedConstructorsImpl(Class<?> clazz,
506         String annotationSpec) {
507         Set<Constructor<?>> result = new HashSet<>();
508         if (annotationSpec != null) {
509             for (Constructor<?> c : clazz.getDeclaredConstructors()) {
510                 if (hasMatchingAnnotation(c, annotationSpec)) {
511                     // TODO(b/71630695): currently, some API members are not annotated, because
512                     // a member is automatically added to the API set if it is in a class with
513                     // annotation and it is not @hide. <member>.getDeclaringClass().
514                     // isAnnotationPresent(annotationClass) won't help because it will then
515                     // incorrectly include non-API members which are marked as @hide;
516                     // @hide isn't visible at runtime. Until the issue is fixed, we should
517                     // omit those automatically added API members from the test.
518                     result.add(c);
519                 }
520             }
521         }
522         return result;
523     }
524 
525     /**
526      * Returns a list of methods which are annotated with the given annotation class.
527      */
getAnnotatedMethods(Class<?> clazz, String annotationSpec)528     public static Set<Method> getAnnotatedMethods(Class<?> clazz, String annotationSpec) {
529         try {
530             return getAnnotatedMethodsImpl(clazz, annotationSpec);
531         } catch (NoClassDefFoundError e) {
532             LogHelper.loge(TAG + ": Could not retrieve methods of " + clazz
533                 + " annotated with " + annotationSpec, e);
534             return Collections.emptySet();
535         }
536     }
537 
getAnnotatedMethodsImpl(Class<?> clazz, String annotationSpec)538     private static Set<Method> getAnnotatedMethodsImpl(Class<?> clazz, String annotationSpec) {
539         Set<Method> result = new HashSet<>();
540         if (annotationSpec != null) {
541             for (Method m : clazz.getDeclaredMethods()) {
542                 if (hasMatchingAnnotation(m, annotationSpec)) {
543                     // TODO(b/71630695): see getAnnotatedConstructors for details
544                     result.add(m);
545                 }
546             }
547         }
548         return result;
549     }
550 
551     /**
552      * Returns a list of fields which are annotated with the given annotation class.
553      */
getAnnotatedFields(Class<?> clazz, String annotationSpec)554     public static Set<Field> getAnnotatedFields(Class<?> clazz, String annotationSpec) {
555         try {
556             return getAnnotatedFieldsImpl(clazz, annotationSpec);
557         } catch (NoClassDefFoundError e) {
558             LogHelper.loge(TAG + ": Could not retrieve fields of " + clazz
559                 + " annotated with " + annotationSpec, e);
560             return Collections.emptySet();
561         }
562     }
563 
getAnnotatedFieldsImpl(Class<?> clazz, String annotationSpec)564     private static Set<Field> getAnnotatedFieldsImpl(Class<?> clazz, String annotationSpec) {
565         Set<Field> result = new HashSet<>();
566         if (annotationSpec != null) {
567             for (Field f : clazz.getDeclaredFields()) {
568                 if (hasMatchingAnnotation(f, annotationSpec)) {
569                     // TODO(b/71630695): see getAnnotatedConstructors for details
570                     result.add(f);
571                 }
572             }
573         }
574         return result;
575     }
576 
isInAnnotatedClass(Member m, String annotationSpec)577     private static boolean isInAnnotatedClass(Member m, String annotationSpec) {
578         Class<?> clazz = m.getDeclaringClass();
579         do {
580             if (hasMatchingAnnotation(clazz, annotationSpec)) {
581                 return true;
582             }
583         } while ((clazz = clazz.getDeclaringClass()) != null);
584         return false;
585     }
586 
isAnnotatedOrInAnnotatedClass(Field field, String annotationSpec)587     public static boolean isAnnotatedOrInAnnotatedClass(Field field, String annotationSpec) {
588         if (annotationSpec == null) {
589             return true;
590         }
591         return hasMatchingAnnotation(field, annotationSpec)
592                 || isInAnnotatedClass(field, annotationSpec);
593     }
594 
isAnnotatedOrInAnnotatedClass(Constructor<?> constructor, String annotationSpec)595     public static boolean isAnnotatedOrInAnnotatedClass(Constructor<?> constructor,
596             String annotationSpec) {
597         if (annotationSpec == null) {
598             return true;
599         }
600         return hasMatchingAnnotation(constructor, annotationSpec)
601                 || isInAnnotatedClass(constructor, annotationSpec);
602     }
603 
isAnnotatedOrInAnnotatedClass(Method method, String annotationSpec)604     public static boolean isAnnotatedOrInAnnotatedClass(Method method, String annotationSpec) {
605         if (annotationSpec == null) {
606             return true;
607         }
608         return hasMatchingAnnotation(method, annotationSpec)
609                 || isInAnnotatedClass(method, annotationSpec);
610     }
611 
isOverridingAnnotatedMethod(Method method, String annotationSpec)612     public static boolean isOverridingAnnotatedMethod(Method method, String annotationSpec) {
613         Class<?> clazz = method.getDeclaringClass();
614         while (!(clazz = clazz.getSuperclass()).equals(Object.class)) {
615             try {
616                 Method overriddenMethod;
617                 overriddenMethod = clazz.getDeclaredMethod(method.getName(),
618                         method.getParameterTypes());
619                 if (overriddenMethod != null) {
620                     return isAnnotatedOrInAnnotatedClass(overriddenMethod, annotationSpec);
621                 }
622             } catch (NoSuchMethodException e) {
623                 continue;
624             } catch (SecurityException e) {
625                 throw new RuntimeException(
626                         "Error while searching for overridden method. " + method.toString(), e);
627             }
628         }
629         return false;
630     }
631 
findRequiredClass(JDiffClassDescription classDescription, ClassProvider classProvider)632     static Class<?> findRequiredClass(JDiffClassDescription classDescription,
633             ClassProvider classProvider) {
634         try {
635             return findMatchingClass(classDescription, classProvider);
636         } catch (ClassNotFoundException e) {
637             LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
638             return null;
639         }
640     }
641 
642     /**
643      * Compare the string representation of types for equality.
644      */
645     interface TypeComparator {
compare(String apiType, String reflectedType)646         boolean compare(String apiType, String reflectedType);
647     }
648 
649     /**
650      * Compare the types using their default signature, i.e. generic for generic methods, otherwise
651      * basic types.
652      */
653     static class DefaultTypeComparator implements TypeComparator {
654         static final TypeComparator INSTANCE = new DefaultTypeComparator();
655         @Override
compare(String apiType, String reflectedType)656         public boolean compare(String apiType, String reflectedType) {
657             return apiType.equals(reflectedType);
658         }
659     }
660 
661     /**
662      * Comparator for the types of bridge methods.
663      *
664      * <p>Bridge methods may not have generic signatures so compare as for
665      * {@link DefaultTypeComparator}, but if they do not match and the api type is
666      * generic then fall back to comparing their raw types.
667      */
668     static class BridgeTypeComparator implements TypeComparator {
669         static final TypeComparator INSTANCE = new BridgeTypeComparator();
670         @Override
compare(String apiType, String reflectedType)671         public boolean compare(String apiType, String reflectedType) {
672             if (DefaultTypeComparator.INSTANCE.compare(apiType, reflectedType)) {
673                 return true;
674             }
675 
676             // If the method is a bridge method and the return types are generic then compare the
677             // non generic types as bridge methods do not have generic types.
678             int index = apiType.indexOf('<');
679             if (index != -1) {
680                 String rawReturnType = apiType.substring(0, index);
681                 return rawReturnType.equals(reflectedType);
682             }
683             return false;
684         }
685     }
686 }
687