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