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