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