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