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 */ areMethodModifiedCompatibile(JDiffMethod apiMethod , Method reflectedMethod)465 private boolean areMethodModifiedCompatibile(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 mod1 = reflectedMethod.getModifiers() & ~(Modifier.NATIVE | Modifier.SYNCHRONIZED); 479 int mod2 = apiMethod.mModifier & ~(Modifier.NATIVE | Modifier.SYNCHRONIZED); 480 481 // We can ignore FINAL for final classes 482 if ((mModifier & Modifier.FINAL) != 0) { 483 mod1 &= ~Modifier.FINAL; 484 mod2 &= ~Modifier.FINAL; 485 } 486 487 return mod1 == mod2; 488 } 489 490 /** 491 * Checks that the method found through reflection matches the 492 * specification from the API xml file. 493 */ checkMethodCompliance()494 private void checkMethodCompliance() { 495 for (JDiffMethod method : jDiffMethods) { 496 try { 497 // this is because jdiff think a method in an interface is not abstract 498 if (JDiffType.INTERFACE.equals(mClassType)) { 499 method.mModifier |= Modifier.ABSTRACT; 500 } 501 502 Method m = findMatchingMethod(method); 503 if (m == null) { 504 mResultObserver.notifyFailure(FailureType.MISSING_METHOD, 505 method.toReadableString(mAbsoluteClassName), 506 "No method with correct signature found:" + 507 method.toSignatureString()); 508 } else { 509 if (m.isVarArgs()) { 510 method.mModifier |= METHOD_MODIFIER_VAR_ARGS; 511 } 512 if (m.isBridge()) { 513 method.mModifier |= METHOD_MODIFIER_BRIDGE; 514 } 515 if (m.isSynthetic()) { 516 method.mModifier |= METHOD_MODIFIER_SYNTHETIC; 517 } 518 519 // FIXME: A workaround to fix the final mismatch on enumeration 520 if (mClass.isEnum() && method.mName.equals("values")) { 521 return; 522 } 523 524 if (!areMethodModifiedCompatibile(method, m)) { 525 mResultObserver.notifyFailure(FailureType.MISMATCH_METHOD, 526 method.toReadableString(mAbsoluteClassName), 527 "Non-compatible method found when looking for " + 528 method.toSignatureString()); 529 } 530 } 531 } catch (Exception e) { 532 loge("Got exception when checking method compliance", e); 533 mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, 534 method.toReadableString(mAbsoluteClassName), 535 "Exception!"); 536 } 537 } 538 } 539 540 /** 541 * Checks if the two types of methods are the same. 542 * 543 * @param jDiffMethod the jDiffMethod to compare 544 * @param method the reflected method to compare 545 * @return true, if both methods are the same 546 */ matches(JDiffMethod jDiffMethod, Method method)547 private boolean matches(JDiffMethod jDiffMethod, Method method) { 548 // If the method names aren't equal, the methods can't match. 549 if (jDiffMethod.mName.equals(method.getName())) { 550 String jdiffReturnType = jDiffMethod.mReturnType; 551 String reflectionReturnType = typeToString(method.getGenericReturnType()); 552 List<String> jdiffParamList = jDiffMethod.mParamList; 553 554 // Next, compare the return types of the two methods. If 555 // they aren't equal, the methods can't match. 556 if (jdiffReturnType.equals(reflectionReturnType)) { 557 Type[] params = method.getGenericParameterTypes(); 558 // Next, check the method parameters. If they have 559 // different number of parameters, the two methods 560 // can't match. 561 if (jdiffParamList.size() == params.length) { 562 // If any of the parameters don't match, the 563 // methods can't match. 564 for (int i = 0; i < jdiffParamList.size(); i++) { 565 if (!compareParam(jdiffParamList.get(i), params[i])) { 566 return false; 567 } 568 } 569 // We've passed all the tests, the methods do 570 // match. 571 return true; 572 } 573 } 574 } 575 return false; 576 } 577 578 /** 579 * Finds the reflected method specified by the method description. 580 * 581 * @param method description of the method to find 582 * @return the reflected method, or null if not found. 583 */ 584 @SuppressWarnings("unchecked") findMatchingMethod(JDiffMethod method)585 private Method findMatchingMethod(JDiffMethod method) { 586 Method[] methods = mClass.getDeclaredMethods(); 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 */ compareParam(String jdiffParam, Type reflectionParamType)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") checkConstructorCompliance()635 private void checkConstructorCompliance() { 636 for (JDiffConstructor con : jDiffConstructors) { 637 try { 638 Constructor<?> c = findMatchingConstructor(con); 639 if (c == null) { 640 mResultObserver.notifyFailure(FailureType.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 FailureType.MISMATCH_METHOD, 651 con.toReadableString(mAbsoluteClassName), 652 "Non-compatible method found when looking for " + 653 con.toSignatureString()); 654 } 655 } 656 } catch (Exception e) { 657 loge("Got exception when checking constructor compliance", e); 658 mResultObserver.notifyFailure(FailureType.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") findMatchingConstructor(JDiffConstructor jdiffDes)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") checkFieldsCompliance()714 private void checkFieldsCompliance() { 715 for (JDiffField field : jDiffFields) { 716 try { 717 Field f = findMatchingField(field); 718 if (f == null) { 719 mResultObserver.notifyFailure(FailureType.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(FailureType.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().replace('$', '.'); 735 } 736 if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) { 737 mResultObserver.notifyFailure( 738 FailureType.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 loge("Got exception when checking field compliance", e); 747 mResultObserver.notifyFailure( 748 FailureType.CAUGHT_EXCEPTION, 749 field.toReadableString(mAbsoluteClassName), 750 "Exception!"); 751 } 752 } 753 } 754 755 /** 756 * Finds the reflected field specified by the field description. 757 * 758 * @param field the field description to find 759 * @return the reflected field, or null if not found. 760 */ findMatchingField(JDiffField field)761 private Field findMatchingField(JDiffField field) { 762 return mClassFieldMap.get(field.mName); 763 } 764 765 /** 766 * Checks if the class under test has compliant modifiers compared to the API. 767 * 768 * @return true if modifiers are compliant. 769 */ checkClassModifiersCompliance()770 private boolean checkClassModifiersCompliance() { 771 int reflectionModifier = mClass.getModifiers(); 772 int apiModifier = mModifier; 773 774 // If the api class isn't abstract 775 if (((apiModifier & Modifier.ABSTRACT) == 0) && 776 // but the reflected class is 777 ((reflectionModifier & Modifier.ABSTRACT) != 0) && 778 // and it isn't an enum 779 !isEnumType()) { 780 // that is a problem 781 return false; 782 } 783 // ABSTRACT check passed, so mask off ABSTRACT 784 reflectionModifier &= ~Modifier.ABSTRACT; 785 apiModifier &= ~Modifier.ABSTRACT; 786 787 if (isAnnotation()) { 788 reflectionModifier &= ~CLASS_MODIFIER_ANNOTATION; 789 } 790 if (mClass.isInterface()) { 791 reflectionModifier &= ~(Modifier.INTERFACE); 792 } 793 if (isEnumType() && mClass.isEnum()) { 794 reflectionModifier &= ~CLASS_MODIFIER_ENUM; 795 } 796 797 return ((reflectionModifier == apiModifier) && 798 (isEnumType() == mClass.isEnum())); 799 } 800 801 /** 802 * Checks if the class under test is compliant with regards to 803 * annnotations when compared to the API. 804 * 805 * @return true if the class is compliant 806 */ checkClassAnnotationCompliace()807 private boolean checkClassAnnotationCompliace() { 808 if (mClass.isAnnotation()) { 809 // check annotation 810 for (String inter : implInterfaces) { 811 if ("java.lang.annotation.Annotation".equals(inter)) { 812 return true; 813 } 814 } 815 return false; 816 } 817 return true; 818 } 819 820 /** 821 * Checks if the class under test extends the proper classes 822 * according to the API. 823 * 824 * @return true if the class is compliant. 825 */ checkClassExtendsCompliance()826 private boolean checkClassExtendsCompliance() { 827 // Nothing to check if it doesn't extend anything. 828 if (mExtendedClass != null) { 829 Class<?> superClass = mClass.getSuperclass(); 830 831 while (superClass != null) { 832 if (superClass.getCanonicalName().equals(mExtendedClass)) { 833 return true; 834 } 835 superClass = superClass.getSuperclass(); 836 } 837 // Couldn't find a matching superclass. 838 return false; 839 } 840 return true; 841 } 842 843 /** 844 * Checks if the class under test implements the proper interfaces 845 * according to the API. 846 * 847 * @return true if the class is compliant 848 */ checkClassImplementsCompliance()849 private boolean checkClassImplementsCompliance() { 850 Class<?>[] interfaces = mClass.getInterfaces(); 851 Set<String> interFaceSet = new HashSet<String>(); 852 853 for (Class<?> c : interfaces) { 854 interFaceSet.add(c.getCanonicalName()); 855 } 856 857 for (String inter : implInterfaces) { 858 if (!interFaceSet.contains(inter)) { 859 return false; 860 } 861 } 862 return true; 863 } 864 865 /** 866 * Checks that the class found through reflection matches the 867 * specification from the API xml file. 868 */ 869 @SuppressWarnings("unchecked") checkClassCompliance()870 private void checkClassCompliance() { 871 try { 872 mAbsoluteClassName = mPackageName + "." + mShortClassName; 873 mClass = findMatchingClass(); 874 875 if (mClass == null) { 876 // No class found, notify the observer according to the class type 877 if (JDiffType.INTERFACE.equals(mClassType)) { 878 mResultObserver.notifyFailure(FailureType.MISSING_INTERFACE, 879 mAbsoluteClassName, 880 "Classloader is unable to find " + mAbsoluteClassName); 881 } else { 882 mResultObserver.notifyFailure(FailureType.MISSING_CLASS, 883 mAbsoluteClassName, 884 "Classloader is unable to find " + mAbsoluteClassName); 885 } 886 887 return; 888 } 889 if (!checkClassModifiersCompliance()) { 890 logMismatchInterfaceSignature(mAbsoluteClassName, 891 "Non-compatible class found when looking for " + 892 toSignatureString()); 893 return; 894 } 895 896 if (!checkClassAnnotationCompliace()) { 897 logMismatchInterfaceSignature(mAbsoluteClassName, 898 "Annotation mismatch"); 899 return; 900 } 901 902 if (!mClass.isAnnotation()) { 903 // check father class 904 if (!checkClassExtendsCompliance()) { 905 logMismatchInterfaceSignature(mAbsoluteClassName, 906 "Extends mismatch"); 907 return; 908 } 909 910 // check implements interface 911 if (!checkClassImplementsCompliance()) { 912 logMismatchInterfaceSignature(mAbsoluteClassName, 913 "Implements mismatch"); 914 return; 915 } 916 } 917 } catch (Exception e) { 918 loge("Got exception when checking field compliance", e); 919 mResultObserver.notifyFailure( 920 FailureType.CAUGHT_EXCEPTION, 921 mAbsoluteClassName, 922 "Exception!"); 923 } 924 } 925 926 927 /** 928 * Convert the class into a printable signature string. 929 * 930 * @return the signature string 931 */ toSignatureString()932 public String toSignatureString() { 933 StringBuffer sb = new StringBuffer(); 934 935 String accessLevel = convertModifiersToAccessLevel(mModifier); 936 if (!"".equals(accessLevel)) { 937 sb.append(accessLevel).append(" "); 938 } 939 if (!JDiffType.INTERFACE.equals(mClassType)) { 940 String modifierString = convertModifersToModifierString(mModifier); 941 if (!"".equals(modifierString)) { 942 sb.append(modifierString).append(" "); 943 } 944 sb.append("class "); 945 } else { 946 sb.append("interface "); 947 } 948 // class name 949 sb.append(mShortClassName); 950 951 // does it extends something? 952 if (mExtendedClass != null) { 953 sb.append(" extends ").append(mExtendedClass).append(" "); 954 } 955 956 // implements something? 957 if (implInterfaces.size() > 0) { 958 sb.append(" implements "); 959 for (int x = 0; x < implInterfaces.size(); x++) { 960 String interf = implInterfaces.get(x); 961 sb.append(interf); 962 // if not last elements 963 if (x + 1 != implInterfaces.size()) { 964 sb.append(", "); 965 } 966 } 967 } 968 return sb.toString(); 969 } 970 logMismatchInterfaceSignature(String classFullName, String errorMessage)971 private void logMismatchInterfaceSignature(String classFullName, String errorMessage) { 972 if (JDiffType.INTERFACE.equals(mClassType)) { 973 mResultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE, 974 classFullName, 975 errorMessage); 976 } else { 977 mResultObserver.notifyFailure(FailureType.MISMATCH_CLASS, 978 classFullName, 979 errorMessage); 980 } 981 } 982 983 /** 984 * Sees if the class under test is actually an enum. 985 * 986 * @return true if this class is enum 987 */ isEnumType()988 private boolean isEnumType() { 989 return "java.lang.Enum".equals(mExtendedClass); 990 } 991 992 /** 993 * Finds the reflected class for the class under test. 994 * 995 * @return the reflected class, or null if not found. 996 */ 997 @SuppressWarnings("unchecked") findMatchingClass()998 private Class<?> findMatchingClass() { 999 // even if there are no . in the string, split will return an 1000 // array of length 1 1001 String[] classNameParts = mShortClassName.split("\\."); 1002 String currentName = mPackageName + "." + classNameParts[0]; 1003 1004 try { 1005 // Check to see if the class we're looking for is the top 1006 // level class. 1007 Class<?> clz = Class.forName(currentName, 1008 false, 1009 this.getClass().getClassLoader()); 1010 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1011 return clz; 1012 } 1013 1014 // Then it must be an inner class. 1015 for (int x = 1; x < classNameParts.length; x++) { 1016 clz = findInnerClassByName(clz, classNameParts[x]); 1017 if (clz == null) { 1018 return null; 1019 } 1020 if (clz.getCanonicalName().equals(mAbsoluteClassName)) { 1021 return clz; 1022 } 1023 } 1024 } catch (ClassNotFoundException e) { 1025 loge("ClassNotFoundException for " + mPackageName + "." + mShortClassName, e); 1026 return null; 1027 } 1028 return null; 1029 } 1030 1031 /** 1032 * Searches the class for the specified inner class. 1033 * 1034 * @param clz the class to search in. 1035 * @param simpleName the simpleName of the class to find 1036 * @returns the class being searched for, or null if it can't be found. 1037 */ findInnerClassByName(Class<?> clz, String simpleName)1038 private Class<?> findInnerClassByName(Class<?> clz, String simpleName) { 1039 for (Class<?> c : clz.getDeclaredClasses()) { 1040 if (c.getSimpleName().equals(simpleName)) { 1041 return c; 1042 } 1043 } 1044 return null; 1045 } 1046 1047 /** 1048 * Sees if the class under test is actually an annotation. 1049 * 1050 * @return true if this class is Annotation. 1051 */ isAnnotation()1052 private boolean isAnnotation() { 1053 if (implInterfaces.contains("java.lang.annotation.Annotation")) { 1054 return true; 1055 } 1056 return false; 1057 } 1058 1059 /** 1060 * Gets the class name for the class under test. 1061 * 1062 * @return the class name. 1063 */ getClassName()1064 public String getClassName() { 1065 return mShortClassName; 1066 } 1067 1068 /** 1069 * Sets the modifier for the class under test. 1070 * 1071 * @param modifier the modifier 1072 */ setModifier(int modifier)1073 public void setModifier(int modifier) { 1074 mModifier = modifier; 1075 } 1076 1077 /** 1078 * Sets the return type for the class under test. 1079 * 1080 * @param type the return type 1081 */ setType(JDiffType type)1082 public void setType(JDiffType type) { 1083 mClassType = type; 1084 } 1085 1086 /** 1087 * Sets the class that is beign extended for the class under test. 1088 * 1089 * @param extendsClass the class being extended. 1090 */ setExtendsClass(String extendsClass)1091 public void setExtendsClass(String extendsClass) { 1092 mExtendedClass = extendsClass; 1093 } 1094 1095 /** 1096 * Registers a ResultObserver to process the output from the 1097 * compliance testing done in this class. 1098 * 1099 * @param resultObserver the observer to register. 1100 */ registerResultObserver(ResultObserver resultObserver)1101 public void registerResultObserver(ResultObserver resultObserver) { 1102 mResultObserver = resultObserver; 1103 } 1104 1105 /** 1106 * Converts WildcardType array into a jdiff compatible string.. 1107 * This is a helper function for typeToString. 1108 * 1109 * @param types array of types to format. 1110 * @return the jdiff formatted string. 1111 */ concatWildcardTypes(Type[] types)1112 private static String concatWildcardTypes(Type[] types) { 1113 StringBuffer sb = new StringBuffer(); 1114 int elementNum = 0; 1115 for (Type t : types) { 1116 sb.append(typeToString(t)); 1117 if (++elementNum < types.length) { 1118 sb.append(" & "); 1119 } 1120 } 1121 return sb.toString(); 1122 } 1123 1124 /** 1125 * Converts a Type into a jdiff compatible String. The returned 1126 * types from this function should match the same Strings that 1127 * jdiff is providing to us. 1128 * 1129 * @param type the type to convert. 1130 * @return the jdiff formatted string. 1131 */ typeToString(Type type)1132 private static String typeToString(Type type) { 1133 if (type instanceof ParameterizedType) { 1134 ParameterizedType pt = (ParameterizedType) type; 1135 1136 StringBuffer sb = new StringBuffer(); 1137 sb.append(typeToString(pt.getRawType())); 1138 sb.append("<"); 1139 1140 int elementNum = 0; 1141 Type[] types = pt.getActualTypeArguments(); 1142 for (Type t : types) { 1143 sb.append(typeToString(t)); 1144 if (++elementNum < types.length) { 1145 sb.append(", "); 1146 } 1147 } 1148 1149 sb.append(">"); 1150 return sb.toString(); 1151 } else if (type instanceof TypeVariable) { 1152 return ((TypeVariable<?>) type).getName(); 1153 } else if (type instanceof Class) { 1154 return ((Class<?>) type).getCanonicalName(); 1155 } else if (type instanceof GenericArrayType) { 1156 String typeName = typeToString(((GenericArrayType) type).getGenericComponentType()); 1157 return typeName + "[]"; 1158 } else if (type instanceof WildcardType) { 1159 WildcardType wt = (WildcardType) type; 1160 Type[] lowerBounds = wt.getLowerBounds(); 1161 if (lowerBounds.length == 0) { 1162 String name = "? extends " + concatWildcardTypes(wt.getUpperBounds()); 1163 1164 // Special case for ? 1165 if (name.equals("? extends java.lang.Object")) { 1166 return "?"; 1167 } else { 1168 return name; 1169 } 1170 } else { 1171 String name = concatWildcardTypes(wt.getUpperBounds()) + 1172 " super " + 1173 concatWildcardTypes(wt.getLowerBounds()); 1174 // Another special case for ? 1175 name = name.replace("java.lang.Object", "?"); 1176 return name; 1177 } 1178 } else { 1179 throw new RuntimeException("Got an unknown java.lang.Type"); 1180 } 1181 } 1182 1183 /** 1184 * Cleans up jdiff parameters to canonicalize them. 1185 * 1186 * @param paramType the parameter from jdiff. 1187 * @return the scrubbed version of the parameter. 1188 */ scrubJdiffParamType(String paramType)1189 private static String scrubJdiffParamType(String paramType) { 1190 // <? extends java.lang.Object and <?> are the same, so 1191 // canonicalize them to one form. 1192 return paramType.replace("<? extends java.lang.Object>", "<?>"); 1193 } 1194 1195 /** 1196 * Scan a class (an its entire inheritance chain) for fields. 1197 * 1198 * @return a {@link Map} of fieldName to {@link Field} 1199 */ buildFieldMap(Class testClass)1200 private static Map<String, Field> buildFieldMap(Class testClass) { 1201 Map<String, Field> fieldMap = new HashMap<String, Field>(); 1202 // Scan the superclass 1203 if (testClass.getSuperclass() != null) { 1204 fieldMap.putAll(buildFieldMap(testClass.getSuperclass())); 1205 } 1206 1207 // Scan the interfaces 1208 for (Class interfaceClass : testClass.getInterfaces()) { 1209 fieldMap.putAll(buildFieldMap(interfaceClass)); 1210 } 1211 1212 // Check the fields in the test class 1213 for (Field field : testClass.getDeclaredFields()) { 1214 fieldMap.put(field.getName(), field); 1215 } 1216 1217 return fieldMap; 1218 } 1219 loge(String message, Exception exception)1220 private static void loge(String message, Exception exception) { 1221 System.err.println(String.format("%s: %s", message, exception)); 1222 } 1223 } 1224