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 17package android.tests.sigtest; 18 19import java.lang.reflect.Constructor; 20import java.lang.reflect.Field; 21import java.lang.reflect.GenericArrayType; 22import java.lang.reflect.Method; 23import java.lang.reflect.Modifier; 24import java.lang.reflect.ParameterizedType; 25import java.lang.reflect.Type; 26import java.lang.reflect.TypeVariable; 27import java.lang.reflect.WildcardType; 28import java.util.ArrayList; 29import java.util.HashSet; 30import java.util.List; 31import java.util.Set; 32 33/** 34 * Represents class descriptions loaded from a jdiff xml file. Used 35 * for CTS SignatureTests. 36 */ 37public class JDiffClassDescription { 38 /** Indicates that the class is an annotation. */ 39 private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000; 40 /** Indicates that the class is an enum. */ 41 private static final int CLASS_MODIFIER_ENUM = 0x00004000; 42 43 /** Indicates that the method is a bridge method. */ 44 private static final int METHOD_MODIFIER_BRIDGE = 0x00000040; 45 /** Indicates that the method is takes a variable number of arguments. */ 46 private static final int METHOD_MODIFIER_VAR_ARGS = 0x00000080; 47 /** Indicates that the method is a synthetic method. */ 48 private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000; 49 50 public enum JDiffType { 51 INTERFACE, CLASS 52 } 53 54 @SuppressWarnings("unchecked") 55 private Class<?> mClass; 56 57 private String mPackageName; 58 private String mShortClassName; 59 60 /** 61 * Package name + short class name 62 */ 63 private String mAbsoluteClassName; 64 65 private int mModifier; 66 67 private String mExtendedClass; 68 private List<String> implInterfaces = new ArrayList<String>(); 69 private List<JDiffField> jDiffFields = new ArrayList<JDiffField>(); 70 private List<JDiffMethod> jDiffMethods = new ArrayList<JDiffMethod>(); 71 private List<JDiffConstructor> jDiffConstructors = new ArrayList<JDiffConstructor>(); 72 73 private ResultObserver mResultObserver; 74 private JDiffType mClassType; 75 76 /** 77 * Creates a new JDiffClassDescription. 78 * 79 * @param pkg the java package this class will end up in. 80 * @param className the name of the class. 81 */ 82 public JDiffClassDescription(String pkg, String className) { 83 this(pkg, className, new ResultObserver() { 84 public void notifyFailure(SignatureTestActivity.FAILURE_TYPE type, 85 String name, 86 String errorMessage) { 87 // This is a null result observer that doesn't do anything. 88 } 89 }); 90 } 91 92 /** 93 * Creates a new JDiffClassDescription with the specified results 94 * observer. 95 * 96 * @param pkg the java package this class belongs in. 97 * @param className the name of the class. 98 * @param resultObserver the resultObserver to get results with. 99 */ 100 public JDiffClassDescription(String pkg, 101 String className, 102 ResultObserver resultObserver) { 103 mPackageName = pkg; 104 mShortClassName = className; 105 mResultObserver = resultObserver; 106 } 107 108 /** 109 * adds implemented interface name. 110 * 111 * @param iname name of interface 112 */ 113 public void addImplInterface(String iname) { 114 implInterfaces.add(iname); 115 } 116 117 /** 118 * Adds a field. 119 * 120 * @param field the field to be added. 121 */ 122 public void addField(JDiffField field) { 123 jDiffFields.add(field); 124 } 125 126 /** 127 * Adds a method. 128 * 129 * @param method the method to be added. 130 */ 131 public void addMethod(JDiffMethod method) { 132 jDiffMethods.add(method); 133 } 134 135 /** 136 * Adds a constructor. 137 * 138 * @param tc the constructor to be added. 139 */ 140 public void addConstructor(JDiffConstructor tc) { 141 jDiffConstructors.add(tc); 142 } 143 144 static String convertModifiersToAccessLevel(int modifiers) { 145 String accessLevel = ""; 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 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 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 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 */ 259 public String toReadableString(String className) { 260 return className + "#" + mName + "(" + mFieldType + ")"; 261 } 262 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 404 protected String getReturnType() { 405 return mReturnType; 406 } 407 } 408 409 /** 410 * Represents a constructor. 411 */ 412 public static final class JDiffConstructor extends JDiffMethod { 413 public JDiffConstructor(String name, int modifier) { 414 super(name, modifier, null); 415 } 416 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 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 */ 441 public void checkSignatureCompliance() { 442 checkClassCompliance(); 443 if (mClass != null) { 444 checkFieldsCompliance(); 445 checkConstructorCompliance(); 446 checkMethodCompliance(); 447 } 448 } 449 450 /** 451 * Checks to ensure that the modifiers value for two methods are 452 * compatible. 453 * 454 * Allowable differences are: 455 * - synchronized is allowed to be removed from an apiMethod 456 * that has it 457 * - the native modified is ignored 458 * 459 * @param apiMethod the method read from the api file. 460 * @param reflectedMethod the method found via reflections. 461 */ 462 private boolean areMethodModifiedCompatibile(JDiffMethod apiMethod , 463 Method reflectedMethod) { 464 465 // If the apiMethod isn't synchronized 466 if (((apiMethod.mModifier & Modifier.SYNCHRONIZED) == 0) && 467 // but the reflected method is 468 ((reflectedMethod.getModifiers() & Modifier.SYNCHRONIZED) != 0)) { 469 // that is a problem 470 return false; 471 } 472 473 // Mask off NATIVE since it is a don't care. Also mask off 474 // SYNCHRONIZED since we've already handled that check. 475 int mod1 = reflectedMethod.getModifiers() & ~(Modifier.NATIVE | Modifier.SYNCHRONIZED); 476 int mod2 = apiMethod.mModifier & ~(Modifier.NATIVE | Modifier.SYNCHRONIZED); 477 478 // We can ignore FINAL for final classes 479 if ((mModifier & Modifier.FINAL) != 0) { 480 mod1 &= ~Modifier.FINAL; 481 mod2 &= ~Modifier.FINAL; 482 } 483 484 return mod1 == mod2; 485 } 486 487 /** 488 * Checks that the method found through reflection matches the 489 * specification from the API xml file. 490 */ 491 private void checkMethodCompliance() { 492 for (JDiffMethod method : jDiffMethods) { 493 try { 494 // this is because jdiff think a method in an interface is not abstract 495 if (JDiffType.INTERFACE.equals(mClassType)) { 496 method.mModifier |= Modifier.ABSTRACT; 497 } 498 499 Method m = findMatchingMethod(method); 500 if (m == null) { 501 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.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 (!areMethodModifiedCompatibile(method, m)) { 522 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISMATCH_METHOD, 523 method.toReadableString(mAbsoluteClassName), 524 "Non-compatible method found when looking for " + 525 method.toSignatureString()); 526 } 527 } 528 } catch (Exception e) { 529 SignatureTestLog.e("Got exception when checking method compliance", e); 530 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.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 */ 544 private boolean matches(JDiffMethod jDiffMethod, Method method) { 545 // If the method names aren't equal, the methods can't match. 546 if (jDiffMethod.mName.equals(method.getName())) { 547 String jdiffReturnType = jDiffMethod.mReturnType; 548 String reflectionReturnType = typeToString(method.getGenericReturnType()); 549 List<String> jdiffParamList = jDiffMethod.mParamList; 550 551 // Next, compare the return types of the two methods. If 552 // they aren't equal, the methods can't match. 553 if (jdiffReturnType.equals(reflectionReturnType)) { 554 Type[] params = method.getGenericParameterTypes(); 555 // Next, check the method parameters. If they have 556 // different number of parameters, the two methods 557 // can't match. 558 if (jdiffParamList.size() == params.length) { 559 // If any of the parameters don't match, the 560 // methods can't match. 561 for (int i = 0; i < jdiffParamList.size(); i++) { 562 if (!compareParam(jdiffParamList.get(i), params[i])) { 563 return false; 564 } 565 } 566 // We've passed all the tests, the methods do 567 // match. 568 return true; 569 } 570 } 571 } 572 return false; 573 } 574 575 /** 576 * Finds the reflected method specified by the method description. 577 * 578 * @param method description of the method to find 579 * @return the reflected method, or null if not found. 580 */ 581 @SuppressWarnings("unchecked") 582 private Method findMatchingMethod(JDiffMethod method) { 583 Method[] methods = mClass.getDeclaredMethods(); 584 boolean found = false; 585 586 for (Method m : methods) { 587 if (matches(method, m)) { 588 return m; 589 } 590 } 591 592 return null; 593 } 594 595 /** 596 * Compares the parameter from the API and the parameter from 597 * reflection. 598 * 599 * @param jdiffParam param parsed from the API xml file. 600 * @param reflectionParamType param gotten from the Java reflection. 601 * @return True if the two params match, otherwise return false. 602 */ 603 private static boolean compareParam(String jdiffParam, Type reflectionParamType) { 604 if (jdiffParam == null) { 605 return false; 606 } 607 608 String reflectionParam = typeToString(reflectionParamType); 609 // Most things aren't varargs, so just do a simple compare 610 // first. 611 if (jdiffParam.equals(reflectionParam)) { 612 return true; 613 } 614 615 // Check for varargs. jdiff reports varargs as ..., while 616 // reflection reports them as [] 617 int jdiffParamEndOffset = jdiffParam.indexOf("..."); 618 int reflectionParamEndOffset = reflectionParam.indexOf("[]"); 619 if (jdiffParamEndOffset != -1 && reflectionParamEndOffset != -1) { 620 jdiffParam = jdiffParam.substring(0, jdiffParamEndOffset); 621 reflectionParam = reflectionParam.substring(0, reflectionParamEndOffset); 622 return jdiffParam.equals(reflectionParam); 623 } 624 625 return false; 626 } 627 628 /** 629 * Checks whether the constructor parsed from API xml file and 630 * Java reflection are compliant. 631 */ 632 @SuppressWarnings("unchecked") 633 private void checkConstructorCompliance() { 634 for (JDiffConstructor con : jDiffConstructors) { 635 try { 636 Constructor<?> c = findMatchingConstructor(con); 637 if (c == null) { 638 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISSING_METHOD, 639 con.toReadableString(mAbsoluteClassName), 640 "No method with correct signature found:" + 641 con.toSignatureString()); 642 } else { 643 if (c.isVarArgs()) {// some method's parameter are variable args 644 con.mModifier |= METHOD_MODIFIER_VAR_ARGS; 645 } 646 if (c.getModifiers() != con.mModifier) { 647 mResultObserver.notifyFailure( 648 SignatureTestActivity.FAILURE_TYPE.MISMATCH_METHOD, 649 con.toReadableString(mAbsoluteClassName), 650 "Non-compatible method found when looking for " + 651 con.toSignatureString()); 652 } 653 } 654 } catch (Exception e) { 655 SignatureTestLog.e("Got exception when checking constructor compliance", e); 656 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.CAUGHT_EXCEPTION, 657 con.toReadableString(mAbsoluteClassName), 658 "Exception!"); 659 } 660 } 661 } 662 663 /** 664 * Searches available constructor. 665 * 666 * @param jdiffDes constructor description to find. 667 * @return reflected constructor, or null if not found. 668 */ 669 @SuppressWarnings("unchecked") 670 private Constructor<?> findMatchingConstructor(JDiffConstructor jdiffDes) { 671 for (Constructor<?> c : mClass.getDeclaredConstructors()) { 672 Type[] params = c.getGenericParameterTypes(); 673 boolean isStaticClass = ((mClass.getModifiers() & Modifier.STATIC) != 0); 674 675 int startParamOffset = 0; 676 int numberOfParams = params.length; 677 678 // non-static inner class -> skip implicit parent pointer 679 // as first arg 680 if (mClass.isMemberClass() && !isStaticClass && params.length >= 1) { 681 startParamOffset = 1; 682 --numberOfParams; 683 } 684 685 ArrayList<String> jdiffParamList = jdiffDes.mParamList; 686 if (jdiffParamList.size() == numberOfParams) { 687 boolean isFound = true; 688 // i counts jdiff params, j counts reflected params 689 int i = 0; 690 int j = startParamOffset; 691 while (i < jdiffParamList.size()) { 692 if (!compareParam(jdiffParamList.get(i), params[j])) { 693 isFound = false; 694 break; 695 } 696 ++i; 697 ++j; 698 } 699 if (isFound) { 700 return c; 701 } 702 } 703 } 704 return null; 705 } 706 707 /** 708 * Checks all fields in test class for compliance with the API 709 * xml. 710 */ 711 @SuppressWarnings("unchecked") 712 private void checkFieldsCompliance() { 713 for (JDiffField field : jDiffFields) { 714 try { 715 Field f = findMatchingField(field); 716 if (f == null) { 717 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISSING_FIELD, 718 field.toReadableString(mAbsoluteClassName), 719 "No field with correct signature found:" + 720 field.toSignatureString()); 721 } else if (f.getModifiers() != field.mModifier) { 722 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISMATCH_FIELD, 723 field.toReadableString(mAbsoluteClassName), 724 "Non-compatible field modifiers found when looking for " + 725 field.toSignatureString()); 726 } else if (!f.getType().getCanonicalName().equals(field.mFieldType)) { 727 // type name does not match, but this might be a generic 728 String genericTypeName = null; 729 Type type = f.getGenericType(); 730 if (type != null) { 731 genericTypeName = type instanceof Class ? ((Class) type).getName() : 732 type.toString(); 733 } 734 if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) { 735 mResultObserver.notifyFailure( 736 SignatureTestActivity.FAILURE_TYPE.MISMATCH_FIELD, 737 field.toReadableString(mAbsoluteClassName), 738 "Non-compatible field type found when looking for " + 739 field.toSignatureString()); 740 } 741 } 742 743 } catch (Exception e) { 744 SignatureTestLog.e("Got exception when checking field compliance", e); 745 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.CAUGHT_EXCEPTION, 746 field.toReadableString(mAbsoluteClassName), 747 "Exception!"); 748 } 749 } 750 } 751 752 /** 753 * Finds the reflected field specified by the field description. 754 * 755 * @param field the field description to find 756 * @return the reflected field, or null if not found. 757 */ 758 private Field findMatchingField(JDiffField field){ 759 Field[] fields = mClass.getDeclaredFields(); 760 for (Field f : fields) { 761 if (f.getName().equals(field.mName)) { 762 return f; 763 } 764 } 765 return null; 766 } 767 768 /** 769 * Checks if the class under test has compliant modifiers compared to the API. 770 * 771 * @return true if modifiers are compliant. 772 */ 773 private boolean checkClassModifiersCompliance() { 774 int reflectionModifier = mClass.getModifiers(); 775 int apiModifier = mModifier; 776 777 // If the api class isn't abstract 778 if (((apiModifier & Modifier.ABSTRACT) == 0) && 779 // but the reflected class is 780 ((reflectionModifier & Modifier.ABSTRACT) != 0) && 781 // and it isn't an enum 782 !isEnumType()) { 783 // that is a problem 784 return false; 785 } 786 // ABSTRACT check passed, so mask off ABSTRACT 787 reflectionModifier &= ~Modifier.ABSTRACT; 788 apiModifier &= ~Modifier.ABSTRACT; 789 790 if (isAnnotation()) { 791 reflectionModifier &= ~CLASS_MODIFIER_ANNOTATION; 792 } 793 if (mClass.isInterface()) { 794 reflectionModifier &= ~(Modifier.INTERFACE); 795 } 796 if (isEnumType() && mClass.isEnum()) { 797 reflectionModifier &= ~CLASS_MODIFIER_ENUM; 798 } 799 800 return ((reflectionModifier == apiModifier) && 801 (isEnumType() == mClass.isEnum())); 802 } 803 804 /** 805 * Checks if the class under test is compliant with regards to 806 * annnotations when compared to the API. 807 * 808 * @return true if the class is compliant 809 */ 810 private boolean checkClassAnnotationCompliace() { 811 if (mClass.isAnnotation()) { 812 // check annotation 813 for (String inter : implInterfaces) { 814 if ("java.lang.annotation.Annotation".equals(inter)) { 815 return true; 816 } 817 } 818 return false; 819 } 820 return true; 821 } 822 823 /** 824 * Checks if the class under test extends the proper classes 825 * according to the API. 826 * 827 * @return true if the class is compliant. 828 */ 829 private boolean checkClassExtendsCompliance() { 830 // Nothing to check if it doesn't extend anything. 831 if (mExtendedClass != null) { 832 Class<?> superClass = mClass.getSuperclass(); 833 if (superClass == null) { 834 // API indicates superclass, reflection doesn't. 835 return false; 836 } 837 838 if (superClass.getCanonicalName().equals(mExtendedClass)) { 839 return true; 840 } 841 842 if (mAbsoluteClassName.equals("android.hardware.SensorManager")) { 843 // FIXME: Please see Issue 1496822 for more information 844 return true; 845 } 846 return false; 847 } 848 return true; 849 } 850 851 /** 852 * Checks if the class under test implements the proper interfaces 853 * according to the API. 854 * 855 * @return true if the class is compliant 856 */ 857 private boolean checkClassImplementsCompliance() { 858 Class<?>[] interfaces = mClass.getInterfaces(); 859 Set<String> interFaceSet = new HashSet<String>(); 860 861 for (Class<?> c : interfaces) { 862 interFaceSet.add(c.getCanonicalName()); 863 } 864 865 for (String inter : implInterfaces) { 866 if (!interFaceSet.contains(inter)) { 867 return false; 868 } 869 } 870 return true; 871 } 872 873 /** 874 * Checks that the class found through reflection matches the 875 * specification from the API xml file. 876 */ 877 @SuppressWarnings("unchecked") 878 private void checkClassCompliance() { 879 try { 880 mAbsoluteClassName = mPackageName + "." + mShortClassName; 881 mClass = findMatchingClass(); 882 883 if (mClass == null) { 884 // No class found, notify the observer according to the class type 885 if (JDiffType.INTERFACE.equals(mClassType)) { 886 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISSING_INTERFACE, 887 mAbsoluteClassName, 888 "Classloader is unable to find " + mAbsoluteClassName); 889 } else { 890 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISSING_CLASS, 891 mAbsoluteClassName, 892 "Classloader is unable to find " + mAbsoluteClassName); 893 } 894 895 return; 896 } 897 if (!checkClassModifiersCompliance()) { 898 logMismatchInterfaceSignature(mAbsoluteClassName, 899 "Non-compatible class found when looking for " + 900 toSignatureString()); 901 return; 902 } 903 904 if (!checkClassAnnotationCompliace()) { 905 logMismatchInterfaceSignature(mAbsoluteClassName, 906 "Annotation mismatch"); 907 return; 908 } 909 910 if (!mClass.isAnnotation()) { 911 // check father class 912 if (!checkClassExtendsCompliance()) { 913 logMismatchInterfaceSignature(mAbsoluteClassName, 914 "Extends mismatch"); 915 return; 916 } 917 918 // check implements interface 919 if (!checkClassImplementsCompliance()) { 920 logMismatchInterfaceSignature(mAbsoluteClassName, 921 "Implements mismatch"); 922 return; 923 } 924 } 925 } catch (Exception e) { 926 SignatureTestLog.e("Got exception when checking field compliance", e); 927 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.CAUGHT_EXCEPTION, 928 mAbsoluteClassName, 929 "Exception!"); 930 } 931 } 932 933 934 /** 935 * Convert the class into a printable signature string. 936 * 937 * @return the signature string 938 */ 939 public String toSignatureString() { 940 StringBuffer sb = new StringBuffer(); 941 942 String accessLevel = convertModifiersToAccessLevel(mModifier); 943 if (!"".equals(accessLevel)) { 944 sb.append(accessLevel).append(" "); 945 } 946 if (!JDiffType.INTERFACE.equals(mClassType)) { 947 String modifierString = convertModifersToModifierString(mModifier); 948 if (!"".equals(modifierString)) { 949 sb.append(modifierString).append(" "); 950 } 951 sb.append("class "); 952 } else { 953 sb.append("interface "); 954 } 955 // class name 956 sb.append(mShortClassName); 957 958 // does it extends something? 959 if (mExtendedClass != null) { 960 sb.append(" extends ").append(mExtendedClass).append(" "); 961 } 962 963 // implements something? 964 if (implInterfaces.size() > 0) { 965 sb.append(" implements "); 966 for (int x = 0; x < implInterfaces.size(); x++) { 967 String interf = implInterfaces.get(x); 968 sb.append(interf); 969 // if not last elements 970 if (x + 1 != implInterfaces.size()) { 971 sb.append(", "); 972 } 973 } 974 } 975 return sb.toString(); 976 } 977 978 private void logMismatchInterfaceSignature(String classFullName, String errorMessage) { 979 if (JDiffType.INTERFACE.equals(mClassType)) { 980 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISMATCH_INTERFACE, 981 classFullName, 982 errorMessage); 983 } else { 984 mResultObserver.notifyFailure(SignatureTestActivity.FAILURE_TYPE.MISMATCH_CLASS, 985 classFullName, 986 errorMessage); 987 } 988 } 989 990 /** 991 * Sees if the class under test is actually an enum. 992 * 993 * @return true if this class is enum 994 */ 995 private boolean isEnumType() { 996 return "java.lang.Enum".equals(mExtendedClass); 997 } 998 999 /** 1000 * Finds the reflected class for the class under test. 1001 * 1002 * @return the reflected class, or null if not found. 1003 */ 1004 @SuppressWarnings("unchecked") 1005 private Class<?> findMatchingClass() { 1006 // even if there are no . in the string, split will return an 1007 // array of length 1 1008 String[] classNameParts = mShortClassName.split("\\."); 1009 String currentName = mPackageName + "." + classNameParts[0]; 1010 1011 try { 1012 // Check to see if the class we're looking for is the top 1013 // level class. 1014 Class<?> clz = Class.forName(currentName, 1015 false, 1016 this.getClass().getClassLoader()); 1017 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1018 return clz; 1019 } 1020 1021 // Then it must be an inner class. 1022 for (int x = 1; x < classNameParts.length; x++) { 1023 clz = findInnerClassByName(clz, classNameParts[x]); 1024 if (clz == null) { 1025 return null; 1026 } 1027 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1028 return clz; 1029 } 1030 } 1031 } catch (ClassNotFoundException e) { 1032 SignatureTestLog.e("ClassNotFoundException for " + mPackageName + "." + mShortClassName, e); 1033 return null; 1034 } 1035 return null; 1036 } 1037 1038 /** 1039 * Searches the class for the specified inner class. 1040 * 1041 * @param clz the class to search in. 1042 * @param simpleName the simpleName of the class to find 1043 * @returns the class being searched for, or null if it can't be found. 1044 */ 1045 private Class<?> findInnerClassByName(Class<?> clz, String simpleName) { 1046 for (Class<?> c : clz.getDeclaredClasses()) { 1047 if (c.getSimpleName().equals(simpleName)) { 1048 return c; 1049 } 1050 } 1051 return null; 1052 } 1053 1054 /** 1055 * Sees if the class under test is actually an annotation. 1056 * 1057 * @return true if this class is Annotation. 1058 */ 1059 private boolean isAnnotation() { 1060 if (implInterfaces.contains("java.lang.annotation.Annotation")) { 1061 return true; 1062 } 1063 return false; 1064 } 1065 1066 /** 1067 * Gets the class name for the class under test. 1068 * 1069 * @return the class name. 1070 */ 1071 public String getClassName() { 1072 return mShortClassName; 1073 } 1074 1075 /** 1076 * Sets the modifier for the class under test. 1077 * 1078 * @param modifier the modifier 1079 */ 1080 public void setModifier(int modifier) { 1081 mModifier = modifier; 1082 } 1083 1084 /** 1085 * Sets the return type for the class under test. 1086 * 1087 * @param type the return type 1088 */ 1089 public void setType(JDiffType type) { 1090 mClassType = type; 1091 } 1092 1093 /** 1094 * Sets the class that is beign extended for the class under test. 1095 * 1096 * @param extendsClass the class being extended. 1097 */ 1098 public void setExtendsClass(String extendsClass) { 1099 mExtendedClass = extendsClass; 1100 } 1101 1102 /** 1103 * Registers a ResultObserver to process the output from the 1104 * compliance testing done in this class. 1105 * 1106 * @param resultObserver the observer to register. 1107 */ 1108 public void registerResultObserver(ResultObserver resultObserver) { 1109 mResultObserver = resultObserver; 1110 } 1111 1112 /** 1113 * Converts WildcardType array into a jdiff compatible string.. 1114 * This is a helper function for typeToString. 1115 * 1116 * @param types array of types to format. 1117 * @return the jdiff formatted string. 1118 */ 1119 private static String concatWildcardTypes(Type[] types) { 1120 StringBuffer sb = new StringBuffer(); 1121 int elementNum = 0; 1122 for (Type t : types) { 1123 sb.append(typeToString(t)); 1124 if (++elementNum < types.length) { 1125 sb.append(" & "); 1126 } 1127 } 1128 return sb.toString(); 1129 } 1130 1131 /** 1132 * Converts a Type into a jdiff compatible String. The returned 1133 * types from this function should match the same Strings that 1134 * jdiff is providing to us. 1135 * 1136 * @param type the type to convert. 1137 * @return the jdiff formatted string. 1138 */ 1139 private static String typeToString(Type type) { 1140 if (type instanceof ParameterizedType) { 1141 ParameterizedType pt = (ParameterizedType) type; 1142 1143 StringBuffer sb = new StringBuffer(); 1144 sb.append(typeToString(pt.getRawType())); 1145 sb.append("<"); 1146 1147 int elementNum = 0; 1148 Type[] types = pt.getActualTypeArguments(); 1149 for (Type t : types) { 1150 sb.append(typeToString(t)); 1151 if (++elementNum < types.length) { 1152 sb.append(", "); 1153 } 1154 } 1155 1156 sb.append(">"); 1157 return sb.toString(); 1158 } else if (type instanceof TypeVariable) { 1159 return ((TypeVariable<?>) type).getName(); 1160 } else if (type instanceof Class) { 1161 return ((Class<?>) type).getCanonicalName(); 1162 } else if (type instanceof GenericArrayType) { 1163 String typeName = typeToString(((GenericArrayType) type).getGenericComponentType()); 1164 return typeName + "[]"; 1165 } else if (type instanceof WildcardType) { 1166 WildcardType wt = (WildcardType) type; 1167 Type[] lowerBounds = wt.getLowerBounds(); 1168 if (lowerBounds.length == 0) { 1169 String name = "? extends " + concatWildcardTypes(wt.getUpperBounds()); 1170 1171 // Special case for ? 1172 if (name.equals("? extends java.lang.Object")) { 1173 return "?"; 1174 } else { 1175 return name; 1176 } 1177 } else { 1178 String name = concatWildcardTypes(wt.getUpperBounds()) + 1179 " super " + 1180 concatWildcardTypes(wt.getLowerBounds()); 1181 // Another special case for ? 1182 name = name.replace("java.lang.Object", "?"); 1183 return name; 1184 } 1185 } else { 1186 throw new RuntimeException("Got an unknown java.lang.Type"); 1187 } 1188 } 1189 1190 /** 1191 * Cleans up jdiff parameters to canonicalize them. 1192 * 1193 * @param paramType the parameter from jdiff. 1194 * @return the scrubbed version of the parameter. 1195 */ 1196 private static String scrubJdiffParamType(String paramType) { 1197 // <? extends java.lang.Object and <?> are the same, so 1198 // canonicalize them to one form. 1199 return paramType.replace("<? extends java.lang.Object>", "<?>"); 1200 } 1201} 1202