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