Home | History | Annotate | Download | only in sigtest
      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