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