1 /* 2 * Copyright (C) 2008 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 17 package android.signature.cts; 18 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.GenericArrayType; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.lang.reflect.ParameterizedType; 25 import java.lang.reflect.Type; 26 import java.lang.reflect.TypeVariable; 27 import java.lang.reflect.WildcardType; 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * Represents class descriptions loaded from a jdiff xml file. Used 37 * for CTS SignatureTests. 38 */ 39 public class JDiffClassDescription { 40 /** Indicates that the class is an annotation. */ 41 private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000; 42 /** Indicates that the class is an enum. */ 43 private static final int CLASS_MODIFIER_ENUM = 0x00004000; 44 45 /** Indicates that the method is a bridge method. */ 46 private static final int METHOD_MODIFIER_BRIDGE = 0x00000040; 47 /** Indicates that the method is takes a variable number of arguments. */ 48 private static final int METHOD_MODIFIER_VAR_ARGS = 0x00000080; 49 /** Indicates that the method is a synthetic method. */ 50 private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000; 51 52 public enum JDiffType { 53 INTERFACE, CLASS 54 } 55 56 @SuppressWarnings("unchecked") 57 private Class<?> mClass; 58 // A map of field name to field of the fields contained in {@code mClass} 59 private Map<String, Field> mClassFieldMap; 60 61 private String mPackageName; 62 private String mShortClassName; 63 64 /** 65 * Package name + short class name 66 */ 67 private String mAbsoluteClassName; 68 69 private int mModifier; 70 71 private String mExtendedClass; 72 private List<String> implInterfaces = new ArrayList<String>(); 73 private List<JDiffField> jDiffFields = new ArrayList<JDiffField>(); 74 private List<JDiffMethod> jDiffMethods = new ArrayList<JDiffMethod>(); 75 private List<JDiffConstructor> jDiffConstructors = new ArrayList<JDiffConstructor>(); 76 77 private ResultObserver mResultObserver; 78 private JDiffType mClassType; 79 80 /** 81 * Creates a new JDiffClassDescription. 82 * 83 * @param pkg the java package this class will end up in. 84 * @param className the name of the class. 85 */ JDiffClassDescription(String pkg, String className)86 public JDiffClassDescription(String pkg, String className) { 87 this(pkg, className, new ResultObserver() { 88 @Override 89 public void notifyFailure(FailureType type, String name, String errorMessage) { 90 // This is a null result observer that doesn't do anything. 91 } 92 }); 93 } 94 95 /** 96 * Creates a new JDiffClassDescription with the specified results 97 * observer. 98 * 99 * @param pkg the java package this class belongs in. 100 * @param className the name of the class. 101 * @param resultObserver the resultObserver to get results with. 102 */ JDiffClassDescription(String pkg, String className, ResultObserver resultObserver)103 public JDiffClassDescription(String pkg, String className, ResultObserver resultObserver) { 104 mPackageName = pkg; 105 mShortClassName = className; 106 mResultObserver = resultObserver; 107 } 108 109 /** 110 * adds implemented interface name. 111 * 112 * @param iname name of interface 113 */ addImplInterface(String iname)114 public void addImplInterface(String iname) { 115 implInterfaces.add(iname); 116 } 117 118 /** 119 * Adds a field. 120 * 121 * @param field the field to be added. 122 */ addField(JDiffField field)123 public void addField(JDiffField field) { 124 jDiffFields.add(field); 125 } 126 127 /** 128 * Adds a method. 129 * 130 * @param method the method to be added. 131 */ addMethod(JDiffMethod method)132 public void addMethod(JDiffMethod method) { 133 jDiffMethods.add(method); 134 } 135 136 /** 137 * Adds a constructor. 138 * 139 * @param tc the constructor to be added. 140 */ addConstructor(JDiffConstructor tc)141 public void addConstructor(JDiffConstructor tc) { 142 jDiffConstructors.add(tc); 143 } 144 convertModifiersToAccessLevel(int modifiers)145 static String convertModifiersToAccessLevel(int modifiers) { 146 if ((modifiers & Modifier.PUBLIC) != 0) { 147 return "public"; 148 } else if ((modifiers & Modifier.PRIVATE) != 0) { 149 return "private"; 150 } else if ((modifiers & Modifier.PROTECTED) != 0) { 151 return "protected"; 152 } else { 153 // package protected 154 return ""; 155 } 156 } 157 convertModifersToModifierString(int modifiers)158 static String convertModifersToModifierString(int modifiers) { 159 StringBuffer sb = new StringBuffer(); 160 boolean isFirst = true; 161 162 // order taken from Java Language Spec, sections 8.1.1, 8.3.1, and 8.4.3 163 if ((modifiers & Modifier.ABSTRACT) != 0) { 164 if (isFirst) { 165 isFirst = false; 166 } else { 167 sb.append(" "); 168 } 169 sb.append("abstract"); 170 } 171 if ((modifiers & Modifier.STATIC) != 0) { 172 if (isFirst) { 173 isFirst = false; 174 } else { 175 sb.append(" "); 176 } 177 sb.append("static"); 178 } 179 if ((modifiers & Modifier.FINAL) != 0) { 180 if (isFirst) { 181 isFirst = false; 182 } else { 183 sb.append(" "); 184 } 185 sb.append("final"); 186 } 187 if ((modifiers & Modifier.TRANSIENT) != 0) { 188 if (isFirst) { 189 isFirst = false; 190 } else { 191 sb.append(" "); 192 } 193 sb.append("transient"); 194 } 195 if ((modifiers & Modifier.VOLATILE) != 0) { 196 if (isFirst) { 197 isFirst = false; 198 } else { 199 sb.append(" "); 200 } 201 sb.append("volatile"); 202 } 203 if ((modifiers & Modifier.SYNCHRONIZED) != 0) { 204 if (isFirst) { 205 isFirst = false; 206 } else { 207 sb.append(" "); 208 } 209 sb.append("synchronized"); 210 } 211 if ((modifiers & Modifier.NATIVE) != 0) { 212 if (isFirst) { 213 isFirst = false; 214 } else { 215 sb.append(" "); 216 } 217 sb.append("native"); 218 } 219 if ((modifiers & Modifier.STRICT) != 0) { 220 if (isFirst) { 221 isFirst = false; 222 } else { 223 sb.append(" "); 224 } 225 sb.append("strictfp"); 226 } 227 228 return sb.toString(); 229 } 230 231 public abstract static class JDiffElement { 232 final String mName; 233 int mModifier; 234 JDiffElement(String name, int modifier)235 public JDiffElement(String name, int modifier) { 236 mName = name; 237 mModifier = modifier; 238 } 239 } 240 241 /** 242 * Represents a field. 243 */ 244 public static final class JDiffField extends JDiffElement { 245 private String mFieldType; 246 JDiffField(String name, String fieldType, int modifier)247 public JDiffField(String name, String fieldType, int modifier) { 248 super(name, modifier); 249 250 mFieldType = fieldType; 251 } 252 253 /** 254 * Make a readable string according to the class name specified. 255 * 256 * @param className The specified class name. 257 * @return A readable string to represent this field along with the class name. 258 */ toReadableString(String className)259 public String toReadableString(String className) { 260 return className + "#" + mName + "(" + mFieldType + ")"; 261 } 262 toSignatureString()263 public String toSignatureString() { 264 StringBuffer sb = new StringBuffer(); 265 266 // access level 267 String accesLevel = convertModifiersToAccessLevel(mModifier); 268 if (!"".equals(accesLevel)) { 269 sb.append(accesLevel).append(" "); 270 } 271 272 String modifierString = convertModifersToModifierString(mModifier); 273 if (!"".equals(modifierString)) { 274 sb.append(modifierString).append(" "); 275 } 276 277 sb.append(mFieldType).append(" "); 278 279 sb.append(mName); 280 281 return sb.toString(); 282 } 283 } 284 285 /** 286 * Represents a method. 287 */ 288 public static class JDiffMethod extends JDiffElement { 289 protected String mReturnType; 290 protected ArrayList<String> mParamList; 291 protected ArrayList<String> mExceptionList; 292 JDiffMethod(String name, int modifier, String returnType)293 public JDiffMethod(String name, int modifier, String returnType) { 294 super(name, modifier); 295 296 if (returnType == null) { 297 mReturnType = "void"; 298 } else { 299 mReturnType = scrubJdiffParamType(returnType); 300 } 301 302 mParamList = new ArrayList<String>(); 303 mExceptionList = new ArrayList<String>(); 304 } 305 306 /** 307 * Adds a parameter. 308 * 309 * @param param parameter type 310 */ addParam(String param)311 public void addParam(String param) { 312 mParamList.add(scrubJdiffParamType(param)); 313 } 314 315 /** 316 * Adds an exception. 317 * 318 * @param exceptionName name of exception 319 */ addException(String exceptionName)320 public void addException(String exceptionName) { 321 mExceptionList.add(exceptionName); 322 } 323 324 /** 325 * Makes a readable string according to the class name specified. 326 * 327 * @param className The specified class name. 328 * @return A readable string to represent this method along with the class name. 329 */ toReadableString(String className)330 public String toReadableString(String className) { 331 return className + "#" + mName + "(" + convertParamList(mParamList) + ")"; 332 } 333 334 /** 335 * Converts a parameter array to a string 336 * 337 * @param params the array to convert 338 * @return converted parameter string 339 */ convertParamList(final ArrayList<String> params)340 private static String convertParamList(final ArrayList<String> params) { 341 342 StringBuffer paramList = new StringBuffer(); 343 344 if (params != null) { 345 for (String str : params) { 346 paramList.append(str + ", "); 347 } 348 if (params.size() > 0) { 349 paramList.delete(paramList.length() - 2, paramList.length()); 350 } 351 } 352 353 return paramList.toString(); 354 } 355 toSignatureString()356 public String toSignatureString() { 357 StringBuffer sb = new StringBuffer(); 358 359 // access level 360 String accesLevel = convertModifiersToAccessLevel(mModifier); 361 if (!"".equals(accesLevel)) { 362 sb.append(accesLevel).append(" "); 363 } 364 365 String modifierString = convertModifersToModifierString(mModifier); 366 if (!"".equals(modifierString)) { 367 sb.append(modifierString).append(" "); 368 } 369 370 String returnType = getReturnType(); 371 if (!"".equals(returnType)) { 372 sb.append(returnType).append(" "); 373 } 374 375 sb.append(mName); 376 sb.append("("); 377 for (int x = 0; x < mParamList.size(); x++) { 378 sb.append(mParamList.get(x)); 379 if (x + 1 != mParamList.size()) { 380 sb.append(", "); 381 } 382 } 383 sb.append(")"); 384 385 // does it throw? 386 if (mExceptionList.size() > 0) { 387 sb.append(" throws "); 388 for (int x = 0; x < mExceptionList.size(); x++) { 389 sb.append(mExceptionList.get(x)); 390 if (x + 1 != mExceptionList.size()) { 391 sb.append(", "); 392 } 393 } 394 } 395 396 return sb.toString(); 397 } 398 399 /** 400 * Gets the return type. 401 * 402 * @return the return type of this method. 403 */ getReturnType()404 protected String getReturnType() { 405 return mReturnType; 406 } 407 } 408 409 /** 410 * Represents a constructor. 411 */ 412 public static final class JDiffConstructor extends JDiffMethod { JDiffConstructor(String name, int modifier)413 public JDiffConstructor(String name, int modifier) { 414 super(name, modifier, null); 415 } 416 JDiffConstructor(String name, String[] param, int modifier)417 public JDiffConstructor(String name, String[] param, int modifier) { 418 super(name, modifier, null); 419 420 for (int i = 0; i < param.length; i++) { 421 addParam(param[i]); 422 } 423 } 424 425 /** 426 * Gets the return type. 427 * 428 * @return the return type of this method. 429 */ 430 @Override getReturnType()431 protected String getReturnType() { 432 // Constructors have no return type. 433 return ""; 434 } 435 } 436 437 /** 438 * Checks test class's name, modifier, fields, constructors, and 439 * methods. 440 */ checkSignatureCompliance()441 public void checkSignatureCompliance() { 442 checkClassCompliance(); 443 if (mClass != null) { 444 mClassFieldMap = buildFieldMap(mClass); 445 checkFieldsCompliance(); 446 checkConstructorCompliance(); 447 checkMethodCompliance(); 448 } else { 449 mClassFieldMap = null; 450 } 451 } 452 453 /** 454 * Checks to ensure that the modifiers value for two methods are 455 * compatible. 456 * 457 * Allowable differences are: 458 * - synchronized is allowed to be removed from an apiMethod 459 * that has it 460 * - the native modified is ignored 461 * 462 * @param apiMethod the method read from the api file. 463 * @param reflectedMethod the method found via reflections. 464 */ areMethodsModifiedCompatible(JDiffMethod apiMethod , Method reflectedMethod)465 private boolean areMethodsModifiedCompatible(JDiffMethod apiMethod , 466 Method reflectedMethod) { 467 468 // If the apiMethod isn't synchronized 469 if (((apiMethod.mModifier & Modifier.SYNCHRONIZED) == 0) && 470 // but the reflected method is 471 ((reflectedMethod.getModifiers() & Modifier.SYNCHRONIZED) != 0)) { 472 // that is a problem 473 return false; 474 } 475 476 // Mask off NATIVE since it is a don't care. Also mask off 477 // SYNCHRONIZED since we've already handled that check. 478 int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT); 479 int mod1 = reflectedMethod.getModifiers() & ~ignoredMods; 480 int mod2 = apiMethod.mModifier & ~ignoredMods; 481 482 // We can ignore FINAL for classes 483 if ((mModifier & Modifier.FINAL) != 0) { 484 mod1 &= ~Modifier.FINAL; 485 mod2 &= ~Modifier.FINAL; 486 } 487 488 return mod1 == mod2; 489 } 490 491 /** 492 * Checks that the method found through reflection matches the 493 * specification from the API xml file. 494 */ checkMethodCompliance()495 private void checkMethodCompliance() { 496 for (JDiffMethod method : jDiffMethods) { 497 try { 498 499 Method m = findMatchingMethod(method); 500 if (m == null) { 501 mResultObserver.notifyFailure(FailureType.MISSING_METHOD, 502 method.toReadableString(mAbsoluteClassName), 503 "No method with correct signature found:" + 504 method.toSignatureString()); 505 } else { 506 if (m.isVarArgs()) { 507 method.mModifier |= METHOD_MODIFIER_VAR_ARGS; 508 } 509 if (m.isBridge()) { 510 method.mModifier |= METHOD_MODIFIER_BRIDGE; 511 } 512 if (m.isSynthetic()) { 513 method.mModifier |= METHOD_MODIFIER_SYNTHETIC; 514 } 515 516 // FIXME: A workaround to fix the final mismatch on enumeration 517 if (mClass.isEnum() && method.mName.equals("values")) { 518 return; 519 } 520 521 if (!areMethodsModifiedCompatible(method, m)) { 522 mResultObserver.notifyFailure(FailureType.MISMATCH_METHOD, 523 method.toReadableString(mAbsoluteClassName), 524 "Non-compatible method found when looking for " + 525 method.toSignatureString()); 526 } 527 } 528 } catch (Exception e) { 529 loge("Got exception when checking method compliance", e); 530 mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, 531 method.toReadableString(mAbsoluteClassName), 532 "Exception!"); 533 } 534 } 535 } 536 537 /** 538 * Checks if the two types of methods are the same. 539 * 540 * @param jDiffMethod the jDiffMethod to compare 541 * @param method the reflected method to compare 542 * @return true, if both methods are the same 543 */ matches(JDiffMethod jDiffMethod, Method reflectedMethod)544 private boolean matches(JDiffMethod jDiffMethod, Method reflectedMethod) { 545 // If the method names aren't equal, the methods can't match. 546 if (!jDiffMethod.mName.equals(reflectedMethod.getName())) { 547 return false; 548 } 549 String jdiffReturnType = jDiffMethod.mReturnType; 550 String reflectionReturnType = typeToString(reflectedMethod.getGenericReturnType()); 551 List<String> jdiffParamList = jDiffMethod.mParamList; 552 553 // Next, compare the return types of the two methods. If 554 // they aren't equal, the methods can't match. 555 if (!jdiffReturnType.equals(reflectionReturnType)) { 556 return false; 557 } 558 559 Type[] params = reflectedMethod.getGenericParameterTypes(); 560 561 // Next, check the method parameters. If they have different 562 // parameter lengths, the two methods can't match. 563 if (jdiffParamList.size() != params.length) { 564 return false; 565 } 566 567 boolean piecewiseParamsMatch = true; 568 569 // Compare method parameters piecewise and return true if they all match. 570 for (int i = 0; i < jdiffParamList.size(); i++) { 571 piecewiseParamsMatch &= compareParam(jdiffParamList.get(i), params[i]); 572 } 573 if (piecewiseParamsMatch) { 574 return true; 575 } 576 577 /** NOTE: There are cases where piecewise method parameter checking 578 * fails even though the strings are equal, so compare entire strings 579 * against each other. This is not done by default to avoid a 580 * TransactionTooLargeException. 581 * Additionally, this can fail anyway due to extra 582 * information dug up by reflection. 583 * 584 * TODO: fix parameter equality checking and reflection matching 585 * See https://b.corp.google.com/issues/27726349 586 */ 587 588 StringBuilder reflectedMethodParams = new StringBuilder(""); 589 StringBuilder jdiffMethodParams = new StringBuilder(""); 590 591 for (int i = 0; i < jdiffParamList.size(); i++) { 592 jdiffMethodParams.append(jdiffParamList.get(i)); 593 reflectedMethodParams.append(params[i]); 594 } 595 596 String jDiffFName = jdiffMethodParams.toString(); 597 String refName = reflectedMethodParams.toString(); 598 599 return jDiffFName.equals(refName); 600 } 601 602 /** 603 * Finds the reflected method specified by the method description. 604 * 605 * @param method description of the method to find 606 * @return the reflected method, or null if not found. 607 */ 608 @SuppressWarnings("unchecked") findMatchingMethod(JDiffMethod method)609 private Method findMatchingMethod(JDiffMethod method) { 610 Method[] methods = mClass.getDeclaredMethods(); 611 612 for (Method m : methods) { 613 if (matches(method, m)) { 614 return m; 615 } 616 } 617 618 return null; 619 } 620 621 /** 622 * Compares the parameter from the API and the parameter from 623 * reflection. 624 * 625 * @param jdiffParam param parsed from the API xml file. 626 * @param reflectionParamType param gotten from the Java reflection. 627 * @return True if the two params match, otherwise return false. 628 */ compareParam(String jdiffParam, Type reflectionParamType)629 private static boolean compareParam(String jdiffParam, Type reflectionParamType) { 630 if (jdiffParam == null) { 631 return false; 632 } 633 634 String reflectionParam = typeToString(reflectionParamType); 635 // Most things aren't varargs, so just do a simple compare 636 // first. 637 if (jdiffParam.equals(reflectionParam)) { 638 return true; 639 } 640 641 // Check for varargs. jdiff reports varargs as ..., while 642 // reflection reports them as [] 643 int jdiffParamEndOffset = jdiffParam.indexOf("..."); 644 int reflectionParamEndOffset = reflectionParam.indexOf("[]"); 645 if (jdiffParamEndOffset != -1 && reflectionParamEndOffset != -1) { 646 jdiffParam = jdiffParam.substring(0, jdiffParamEndOffset); 647 reflectionParam = reflectionParam.substring(0, reflectionParamEndOffset); 648 return jdiffParam.equals(reflectionParam); 649 } 650 651 return false; 652 } 653 654 /** 655 * Checks whether the constructor parsed from API xml file and 656 * Java reflection are compliant. 657 */ 658 @SuppressWarnings("unchecked") checkConstructorCompliance()659 private void checkConstructorCompliance() { 660 for (JDiffConstructor con : jDiffConstructors) { 661 try { 662 Constructor<?> c = findMatchingConstructor(con); 663 if (c == null) { 664 mResultObserver.notifyFailure(FailureType.MISSING_METHOD, 665 con.toReadableString(mAbsoluteClassName), 666 "No method with correct signature found:" + 667 con.toSignatureString()); 668 } else { 669 if (c.isVarArgs()) {// some method's parameter are variable args 670 con.mModifier |= METHOD_MODIFIER_VAR_ARGS; 671 } 672 if (c.getModifiers() != con.mModifier) { 673 mResultObserver.notifyFailure( 674 FailureType.MISMATCH_METHOD, 675 con.toReadableString(mAbsoluteClassName), 676 "Non-compatible method found when looking for " + 677 con.toSignatureString()); 678 } 679 } 680 } catch (Exception e) { 681 loge("Got exception when checking constructor compliance", e); 682 mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, 683 con.toReadableString(mAbsoluteClassName), 684 "Exception!"); 685 } 686 } 687 } 688 689 /** 690 * Searches available constructor. 691 * 692 * @param jdiffDes constructor description to find. 693 * @return reflected constructor, or null if not found. 694 */ 695 @SuppressWarnings("unchecked") findMatchingConstructor(JDiffConstructor jdiffDes)696 private Constructor<?> findMatchingConstructor(JDiffConstructor jdiffDes) { 697 for (Constructor<?> c : mClass.getDeclaredConstructors()) { 698 Type[] params = c.getGenericParameterTypes(); 699 boolean isStaticClass = ((mClass.getModifiers() & Modifier.STATIC) != 0); 700 701 int startParamOffset = 0; 702 int numberOfParams = params.length; 703 704 // non-static inner class -> skip implicit parent pointer 705 // as first arg 706 if (mClass.isMemberClass() && !isStaticClass && params.length >= 1) { 707 startParamOffset = 1; 708 --numberOfParams; 709 } 710 711 ArrayList<String> jdiffParamList = jdiffDes.mParamList; 712 if (jdiffParamList.size() == numberOfParams) { 713 boolean isFound = true; 714 // i counts jdiff params, j counts reflected params 715 int i = 0; 716 int j = startParamOffset; 717 while (i < jdiffParamList.size()) { 718 if (!compareParam(jdiffParamList.get(i), params[j])) { 719 isFound = false; 720 break; 721 } 722 ++i; 723 ++j; 724 } 725 if (isFound) { 726 return c; 727 } 728 } 729 } 730 return null; 731 } 732 733 /** 734 * Checks all fields in test class for compliance with the API 735 * xml. 736 */ 737 @SuppressWarnings("unchecked") checkFieldsCompliance()738 private void checkFieldsCompliance() { 739 for (JDiffField field : jDiffFields) { 740 try { 741 Field f = findMatchingField(field); 742 if (f == null) { 743 mResultObserver.notifyFailure(FailureType.MISSING_FIELD, 744 field.toReadableString(mAbsoluteClassName), 745 "No field with correct signature found:" + 746 field.toSignatureString()); 747 } else if (f.getModifiers() != field.mModifier) { 748 mResultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 749 field.toReadableString(mAbsoluteClassName), 750 "Non-compatible field modifiers found when looking for " + 751 field.toSignatureString()); 752 } else if (!f.getType().getCanonicalName().equals(field.mFieldType)) { 753 // type name does not match, but this might be a generic 754 String genericTypeName = null; 755 Type type = f.getGenericType(); 756 if (type != null) { 757 genericTypeName = type instanceof Class ? ((Class) type).getName() : 758 type.toString().replace('$', '.'); 759 } 760 if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) { 761 mResultObserver.notifyFailure( 762 FailureType.MISMATCH_FIELD, 763 field.toReadableString(mAbsoluteClassName), 764 "Non-compatible field type found when looking for " + 765 field.toSignatureString()); 766 } 767 } 768 769 } catch (Exception e) { 770 loge("Got exception when checking field compliance", e); 771 mResultObserver.notifyFailure( 772 FailureType.CAUGHT_EXCEPTION, 773 field.toReadableString(mAbsoluteClassName), 774 "Exception!"); 775 } 776 } 777 } 778 779 /** 780 * Finds the reflected field specified by the field description. 781 * 782 * @param field the field description to find 783 * @return the reflected field, or null if not found. 784 */ findMatchingField(JDiffField field)785 private Field findMatchingField(JDiffField field) { 786 return mClassFieldMap.get(field.mName); 787 } 788 789 /** 790 * Checks if the class under test has compliant modifiers compared to the API. 791 * 792 * @return true if modifiers are compliant. 793 */ checkClassModifiersCompliance()794 private boolean checkClassModifiersCompliance() { 795 int reflectionModifier = mClass.getModifiers(); 796 int apiModifier = mModifier; 797 798 // If the api class isn't abstract 799 if (((apiModifier & Modifier.ABSTRACT) == 0) && 800 // but the reflected class is 801 ((reflectionModifier & Modifier.ABSTRACT) != 0) && 802 // and it isn't an enum 803 !isEnumType()) { 804 // that is a problem 805 return false; 806 } 807 // ABSTRACT check passed, so mask off ABSTRACT 808 reflectionModifier &= ~Modifier.ABSTRACT; 809 apiModifier &= ~Modifier.ABSTRACT; 810 811 if (isAnnotation()) { 812 reflectionModifier &= ~CLASS_MODIFIER_ANNOTATION; 813 } 814 if (mClass.isInterface()) { 815 reflectionModifier &= ~(Modifier.INTERFACE); 816 } 817 if (isEnumType() && mClass.isEnum()) { 818 reflectionModifier &= ~CLASS_MODIFIER_ENUM; 819 } 820 821 return ((reflectionModifier == apiModifier) && 822 (isEnumType() == mClass.isEnum())); 823 } 824 825 /** 826 * Checks if the class under test is compliant with regards to 827 * annnotations when compared to the API. 828 * 829 * @return true if the class is compliant 830 */ checkClassAnnotationCompliace()831 private boolean checkClassAnnotationCompliace() { 832 if (mClass.isAnnotation()) { 833 // check annotation 834 for (String inter : implInterfaces) { 835 if ("java.lang.annotation.Annotation".equals(inter)) { 836 return true; 837 } 838 } 839 return false; 840 } 841 return true; 842 } 843 844 /** 845 * Checks if the class under test extends the proper classes 846 * according to the API. 847 * 848 * @return true if the class is compliant. 849 */ checkClassExtendsCompliance()850 private boolean checkClassExtendsCompliance() { 851 // Nothing to check if it doesn't extend anything. 852 if (mExtendedClass != null) { 853 Class<?> superClass = mClass.getSuperclass(); 854 855 while (superClass != null) { 856 if (superClass.getCanonicalName().equals(mExtendedClass)) { 857 return true; 858 } 859 superClass = superClass.getSuperclass(); 860 } 861 // Couldn't find a matching superclass. 862 return false; 863 } 864 return true; 865 } 866 867 /** 868 * Checks if the class under test implements the proper interfaces 869 * according to the API. 870 * 871 * @return true if the class is compliant 872 */ checkClassImplementsCompliance()873 private boolean checkClassImplementsCompliance() { 874 Class<?>[] interfaces = mClass.getInterfaces(); 875 Set<String> interFaceSet = new HashSet<String>(); 876 877 for (Class<?> c : interfaces) { 878 interFaceSet.add(c.getCanonicalName()); 879 } 880 881 for (String inter : implInterfaces) { 882 if (!interFaceSet.contains(inter)) { 883 return false; 884 } 885 } 886 return true; 887 } 888 889 /** 890 * Checks that the class found through reflection matches the 891 * specification from the API xml file. 892 */ 893 @SuppressWarnings("unchecked") checkClassCompliance()894 private void checkClassCompliance() { 895 try { 896 mAbsoluteClassName = mPackageName + "." + mShortClassName; 897 mClass = findMatchingClass(); 898 899 if (mClass == null) { 900 // No class found, notify the observer according to the class type 901 if (JDiffType.INTERFACE.equals(mClassType)) { 902 mResultObserver.notifyFailure(FailureType.MISSING_INTERFACE, 903 mAbsoluteClassName, 904 "Classloader is unable to find " + mAbsoluteClassName); 905 } else { 906 mResultObserver.notifyFailure(FailureType.MISSING_CLASS, 907 mAbsoluteClassName, 908 "Classloader is unable to find " + mAbsoluteClassName); 909 } 910 911 return; 912 } 913 if (!checkClassModifiersCompliance()) { 914 logMismatchInterfaceSignature(mAbsoluteClassName, 915 "Non-compatible class found when looking for " + 916 toSignatureString()); 917 return; 918 } 919 920 if (!checkClassAnnotationCompliace()) { 921 logMismatchInterfaceSignature(mAbsoluteClassName, 922 "Annotation mismatch"); 923 return; 924 } 925 926 if (!mClass.isAnnotation()) { 927 // check father class 928 if (!checkClassExtendsCompliance()) { 929 logMismatchInterfaceSignature(mAbsoluteClassName, 930 "Extends mismatch"); 931 return; 932 } 933 934 // check implements interface 935 if (!checkClassImplementsCompliance()) { 936 logMismatchInterfaceSignature(mAbsoluteClassName, 937 "Implements mismatch"); 938 return; 939 } 940 } 941 } catch (Exception e) { 942 loge("Got exception when checking field compliance", e); 943 mResultObserver.notifyFailure( 944 FailureType.CAUGHT_EXCEPTION, 945 mAbsoluteClassName, 946 "Exception!"); 947 } 948 } 949 950 951 /** 952 * Convert the class into a printable signature string. 953 * 954 * @return the signature string 955 */ toSignatureString()956 public String toSignatureString() { 957 StringBuffer sb = new StringBuffer(); 958 959 String accessLevel = convertModifiersToAccessLevel(mModifier); 960 if (!"".equals(accessLevel)) { 961 sb.append(accessLevel).append(" "); 962 } 963 if (!JDiffType.INTERFACE.equals(mClassType)) { 964 String modifierString = convertModifersToModifierString(mModifier); 965 if (!"".equals(modifierString)) { 966 sb.append(modifierString).append(" "); 967 } 968 sb.append("class "); 969 } else { 970 sb.append("interface "); 971 } 972 // class name 973 sb.append(mShortClassName); 974 975 // does it extends something? 976 if (mExtendedClass != null) { 977 sb.append(" extends ").append(mExtendedClass).append(" "); 978 } 979 980 // implements something? 981 if (implInterfaces.size() > 0) { 982 sb.append(" implements "); 983 for (int x = 0; x < implInterfaces.size(); x++) { 984 String interf = implInterfaces.get(x); 985 sb.append(interf); 986 // if not last elements 987 if (x + 1 != implInterfaces.size()) { 988 sb.append(", "); 989 } 990 } 991 } 992 return sb.toString(); 993 } 994 logMismatchInterfaceSignature(String classFullName, String errorMessage)995 private void logMismatchInterfaceSignature(String classFullName, String errorMessage) { 996 if (JDiffType.INTERFACE.equals(mClassType)) { 997 mResultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE, 998 classFullName, 999 errorMessage); 1000 } else { 1001 mResultObserver.notifyFailure(FailureType.MISMATCH_CLASS, 1002 classFullName, 1003 errorMessage); 1004 } 1005 } 1006 1007 /** 1008 * Sees if the class under test is actually an enum. 1009 * 1010 * @return true if this class is enum 1011 */ isEnumType()1012 private boolean isEnumType() { 1013 return "java.lang.Enum".equals(mExtendedClass); 1014 } 1015 1016 /** 1017 * Finds the reflected class for the class under test. 1018 * 1019 * @return the reflected class, or null if not found. 1020 */ 1021 @SuppressWarnings("unchecked") findMatchingClass()1022 private Class<?> findMatchingClass() { 1023 // even if there are no . in the string, split will return an 1024 // array of length 1 1025 String[] classNameParts = mShortClassName.split("\\."); 1026 String currentName = mPackageName + "." + classNameParts[0]; 1027 1028 try { 1029 // Check to see if the class we're looking for is the top 1030 // level class. 1031 Class<?> clz = Class.forName(currentName, 1032 false, 1033 this.getClass().getClassLoader()); 1034 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1035 return clz; 1036 } 1037 1038 // Then it must be an inner class. 1039 for (int x = 1; x < classNameParts.length; x++) { 1040 clz = findInnerClassByName(clz, classNameParts[x]); 1041 if (clz == null) { 1042 return null; 1043 } 1044 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1045 return clz; 1046 } 1047 } 1048 } catch (ClassNotFoundException e) { 1049 loge("ClassNotFoundException for " + mPackageName + "." + mShortClassName, e); 1050 return null; 1051 } 1052 return null; 1053 } 1054 1055 /** 1056 * Searches the class for the specified inner class. 1057 * 1058 * @param clz the class to search in. 1059 * @param simpleName the simpleName of the class to find 1060 * @returns the class being searched for, or null if it can't be found. 1061 */ findInnerClassByName(Class<?> clz, String simpleName)1062 private Class<?> findInnerClassByName(Class<?> clz, String simpleName) { 1063 for (Class<?> c : clz.getDeclaredClasses()) { 1064 if (c.getSimpleName().equals(simpleName)) { 1065 return c; 1066 } 1067 } 1068 return null; 1069 } 1070 1071 /** 1072 * Sees if the class under test is actually an annotation. 1073 * 1074 * @return true if this class is Annotation. 1075 */ isAnnotation()1076 private boolean isAnnotation() { 1077 if (implInterfaces.contains("java.lang.annotation.Annotation")) { 1078 return true; 1079 } 1080 return false; 1081 } 1082 1083 /** 1084 * Gets the class name for the class under test. 1085 * 1086 * @return the class name. 1087 */ getClassName()1088 public String getClassName() { 1089 return mShortClassName; 1090 } 1091 1092 /** 1093 * Sets the modifier for the class under test. 1094 * 1095 * @param modifier the modifier 1096 */ setModifier(int modifier)1097 public void setModifier(int modifier) { 1098 mModifier = modifier; 1099 } 1100 1101 /** 1102 * Sets the return type for the class under test. 1103 * 1104 * @param type the return type 1105 */ setType(JDiffType type)1106 public void setType(JDiffType type) { 1107 mClassType = type; 1108 } 1109 1110 /** 1111 * Sets the class that is beign extended for the class under test. 1112 * 1113 * @param extendsClass the class being extended. 1114 */ setExtendsClass(String extendsClass)1115 public void setExtendsClass(String extendsClass) { 1116 mExtendedClass = extendsClass; 1117 } 1118 1119 /** 1120 * Registers a ResultObserver to process the output from the 1121 * compliance testing done in this class. 1122 * 1123 * @param resultObserver the observer to register. 1124 */ registerResultObserver(ResultObserver resultObserver)1125 public void registerResultObserver(ResultObserver resultObserver) { 1126 mResultObserver = resultObserver; 1127 } 1128 1129 /** 1130 * Converts WildcardType array into a jdiff compatible string.. 1131 * This is a helper function for typeToString. 1132 * 1133 * @param types array of types to format. 1134 * @return the jdiff formatted string. 1135 */ concatWildcardTypes(Type[] types)1136 private static String concatWildcardTypes(Type[] types) { 1137 StringBuffer sb = new StringBuffer(); 1138 int elementNum = 0; 1139 for (Type t : types) { 1140 sb.append(typeToString(t)); 1141 if (++elementNum < types.length) { 1142 sb.append(" & "); 1143 } 1144 } 1145 return sb.toString(); 1146 } 1147 1148 /** 1149 * Converts a Type into a jdiff compatible String. The returned 1150 * types from this function should match the same Strings that 1151 * jdiff is providing to us. 1152 * 1153 * @param type the type to convert. 1154 * @return the jdiff formatted string. 1155 */ typeToString(Type type)1156 private static String typeToString(Type type) { 1157 if (type instanceof ParameterizedType) { 1158 ParameterizedType pt = (ParameterizedType) type; 1159 1160 StringBuffer sb = new StringBuffer(); 1161 sb.append(typeToString(pt.getRawType())); 1162 sb.append("<"); 1163 1164 int elementNum = 0; 1165 Type[] types = pt.getActualTypeArguments(); 1166 for (Type t : types) { 1167 sb.append(typeToString(t)); 1168 if (++elementNum < types.length) { 1169 sb.append(", "); 1170 } 1171 } 1172 1173 sb.append(">"); 1174 return sb.toString(); 1175 } else if (type instanceof TypeVariable) { 1176 return ((TypeVariable<?>) type).getName(); 1177 } else if (type instanceof Class) { 1178 return ((Class<?>) type).getCanonicalName(); 1179 } else if (type instanceof GenericArrayType) { 1180 String typeName = typeToString(((GenericArrayType) type).getGenericComponentType()); 1181 return typeName + "[]"; 1182 } else if (type instanceof WildcardType) { 1183 WildcardType wt = (WildcardType) type; 1184 Type[] lowerBounds = wt.getLowerBounds(); 1185 if (lowerBounds.length == 0) { 1186 String name = "? extends " + concatWildcardTypes(wt.getUpperBounds()); 1187 1188 // Special case for ? 1189 if (name.equals("? extends java.lang.Object")) { 1190 return "?"; 1191 } else { 1192 return name; 1193 } 1194 } else { 1195 String name = concatWildcardTypes(wt.getUpperBounds()) + 1196 " super " + 1197 concatWildcardTypes(wt.getLowerBounds()); 1198 // Another special case for ? 1199 name = name.replace("java.lang.Object", "?"); 1200 return name; 1201 } 1202 } else { 1203 throw new RuntimeException("Got an unknown java.lang.Type"); 1204 } 1205 } 1206 1207 /** 1208 * Cleans up jdiff parameters to canonicalize them. 1209 * 1210 * @param paramType the parameter from jdiff. 1211 * @return the scrubbed version of the parameter. 1212 */ scrubJdiffParamType(String paramType)1213 private static String scrubJdiffParamType(String paramType) { 1214 // <? extends java.lang.Object and <?> are the same, so 1215 // canonicalize them to one form. 1216 return paramType.replace("<? extends java.lang.Object>", "<?>"); 1217 } 1218 1219 /** 1220 * Scan a class (an its entire inheritance chain) for fields. 1221 * 1222 * @return a {@link Map} of fieldName to {@link Field} 1223 */ buildFieldMap(Class testClass)1224 private static Map<String, Field> buildFieldMap(Class testClass) { 1225 Map<String, Field> fieldMap = new HashMap<String, Field>(); 1226 // Scan the superclass 1227 if (testClass.getSuperclass() != null) { 1228 fieldMap.putAll(buildFieldMap(testClass.getSuperclass())); 1229 } 1230 1231 // Scan the interfaces 1232 for (Class interfaceClass : testClass.getInterfaces()) { 1233 fieldMap.putAll(buildFieldMap(interfaceClass)); 1234 } 1235 1236 // Check the fields in the test class 1237 for (Field field : testClass.getDeclaredFields()) { 1238 fieldMap.put(field.getName(), field); 1239 } 1240 1241 return fieldMap; 1242 } 1243 loge(String message, Exception exception)1244 private static void loge(String message, Exception exception) { 1245 System.err.println(String.format("%s: %s", message, exception)); 1246 } 1247 } 1248