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