summaryrefslogtreecommitdiffstats
path: root/dexcomparator/src/com/android/jack/DexAnnotationsComparator.java
diff options
context:
space:
mode:
Diffstat (limited to 'dexcomparator/src/com/android/jack/DexAnnotationsComparator.java')
-rw-r--r--dexcomparator/src/com/android/jack/DexAnnotationsComparator.java697
1 files changed, 697 insertions, 0 deletions
diff --git a/dexcomparator/src/com/android/jack/DexAnnotationsComparator.java b/dexcomparator/src/com/android/jack/DexAnnotationsComparator.java
new file mode 100644
index 00000000..df7b2a7b
--- /dev/null
+++ b/dexcomparator/src/com/android/jack/DexAnnotationsComparator.java
@@ -0,0 +1,697 @@
+package com.android.jack;
+
+import com.android.jack.dx.rop.code.AccessFlags;
+
+import org.jf.dexlib.AnnotationDirectoryItem;
+import org.jf.dexlib.AnnotationDirectoryItem.MethodAnnotation;
+import org.jf.dexlib.AnnotationItem;
+import org.jf.dexlib.AnnotationSetItem;
+import org.jf.dexlib.AnnotationSetRefList;
+import org.jf.dexlib.ClassDataItem;
+import org.jf.dexlib.ClassDataItem.EncodedField;
+import org.jf.dexlib.ClassDataItem.EncodedMethod;
+import org.jf.dexlib.ClassDefItem;
+import org.jf.dexlib.DexFile;
+import org.jf.dexlib.FieldIdItem;
+import org.jf.dexlib.ItemType;
+import org.jf.dexlib.MethodIdItem;
+import org.jf.dexlib.ProtoIdItem;
+import org.jf.dexlib.Section;
+import org.jf.dexlib.StringIdItem;
+import org.jf.dexlib.TypeIdItem;
+import org.jf.dexlib.TypeListItem;
+import org.jf.dexlib.EncodedValue.AnnotationEncodedSubValue;
+import org.jf.dexlib.EncodedValue.ArrayEncodedSubValue;
+import org.jf.dexlib.EncodedValue.ArrayEncodedValue;
+import org.jf.dexlib.EncodedValue.EncodedValue;
+import org.jf.dexlib.EncodedValue.EnumEncodedValue;
+import org.jf.dexlib.EncodedValue.IntEncodedValue;
+import org.jf.dexlib.EncodedValue.MethodEncodedValue;
+import org.jf.dexlib.EncodedValue.StringEncodedValue;
+import org.jf.dexlib.EncodedValue.TypeEncodedValue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * This tool compares 2 dex files. The candidate is compared to the reference dex file, and MUST
+ * have at least all the features present in the reference. (Class, super class, fields, methods)
+ */
+public class DexAnnotationsComparator {
+
+ @Nonnull
+ private static final String MEMBER_CLASSES_DESCRIPTOR = "Ldalvik/annotation/MemberClasses;";
+ @Nonnull
+ private final Logger logger;
+ @Nonnull
+ private final DexFile referenceDexFile;
+ @Nonnull
+ private final DexFile candidateDexFile;
+ @Nonnull
+ private static final Level ERROR_LEVEL = Level.SEVERE;
+ @Nonnull
+ private static final Level DEBUG_LEVEL = Level.FINE;
+
+ private static final boolean IGNORE_ANONYMOUS_CLASSES = true;
+ private static final boolean TOLERATE_MISSING_SYNTHETICS = true;
+ private static final boolean TOLERATE_MISSING_INITS = true;
+ private static final boolean TOLERATE_MISSING_CLINITS = true;
+
+ public DexAnnotationsComparator(
+ @Nonnull File referenceFile, @Nonnull File candidateFile) throws IOException {
+ logger = Logger.getLogger(this.getClass().getName());
+ logger.setLevel(ERROR_LEVEL);
+
+ referenceDexFile = new DexFile(referenceFile);
+ candidateDexFile = new DexFile(candidateFile);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void compare() throws DifferenceFoundException {
+
+ /* Reuse delegate instance for maximum memory saving */
+ CompareElementAnnotation compareElementAnnotation =
+ new CompareElementAnnotation(logger);
+
+ /* build a lookup table for candidate classes */
+ HashMap<String, ClassDefItem> candidateClassDefItemLookUpTable =
+ new HashMap<String, ClassDefItem>();
+ Section<ClassDefItem> candidateClassDefSection =
+ candidateDexFile.getSectionForType(ItemType.TYPE_CLASS_DEF_ITEM);
+ for (ClassDefItem classDefItem : candidateClassDefSection.getItems()) {
+ candidateClassDefItemLookUpTable.put(
+ classDefItem.getClassType().getTypeDescriptor(), classDefItem);
+ }
+
+ Section<ClassDefItem> classDefSection =
+ referenceDexFile.getSectionForType(ItemType.TYPE_CLASS_DEF_ITEM);
+
+ for (ClassDefItem classDefItem : classDefSection.getItems()) {
+
+ ClassDefItem candidateClassDefItem =
+ candidateClassDefItemLookUpTable.get(classDefItem.getClassType().getTypeDescriptor());
+ String className = classDefItem.getClassType().getTypeDescriptor();
+
+ if (!IGNORE_ANONYMOUS_CLASSES || !isAnonymousTypeName(className)) {
+
+ /* class */
+ if (candidateClassDefItem != null) {
+
+ logger.log(DEBUG_LEVEL, "Class {0} OK", className);
+ int accessFlags = classDefItem.getAccessFlags();
+ boolean synthetic = isSynthetic(accessFlags);
+
+ if (synthetic && TOLERATE_MISSING_SYNTHETICS) {
+ continue;
+ }
+
+ /* check ClassData field */
+ ClassDataItem classDataItem = classDefItem.getClassData();
+ ClassDataItem candidateClassDataItem = candidateClassDefItem.getClassData();
+
+ if (classDataItem == null && candidateClassDataItem == null) {
+ logger.log(DEBUG_LEVEL, "ClassData of {0} OK: both are null", className);
+ } else if (classDataItem == null || candidateClassDataItem == null) {
+ String errorString =
+ ((classDataItem == null) ? ("reference's is null and not candidate's")
+ : ("candidate's is null and not reference's"));
+ throw new DifferenceFoundException(
+ "ClassDatas of '" + className + "' do not match: " + errorString);
+ } else {
+ /* Annotation pre check */
+ boolean doCheckAnnotations = false;
+ AnnotationDirectoryItem annotationDirectoryItem = classDefItem.getAnnotations();
+ AnnotationDirectoryItem candidateAnnotationDirectoryItem =
+ candidateClassDefItem.getAnnotations();
+
+ if (annotationDirectoryItem == null && candidateAnnotationDirectoryItem == null) {
+ logger.log(DEBUG_LEVEL, "AnnotationDirectoryItem of {0} OK: both are null", className);
+ } else if (annotationDirectoryItem == null || candidateAnnotationDirectoryItem == null) {
+ String errorString =
+ ((annotationDirectoryItem == null) ? ("reference's is null and not candidate's")
+ : ("candidate's is null and not reference's"));
+
+ logger.log(ERROR_LEVEL, "AnnotationDirectoryItem of {0} NOK: {1}",
+ new Object[] {className, errorString});
+
+ if (annotationDirectoryItem != null) {
+ boolean containsOnlyAnnotationsOnSyntheticMethods = true;
+ for (MethodAnnotation anno : annotationDirectoryItem.getMethodAnnotations()) {
+ containsOnlyAnnotationsOnSyntheticMethods =
+ containsOnlyAnnotationsOnSyntheticMethods
+ && isSynthetic(getMethodAccessFlags(classDataItem, anno.method));
+
+ }
+
+ if (containsOnlyAnnotationsOnSyntheticMethods) {
+ continue;
+ }
+ }
+
+ throw new DifferenceFoundException(
+ "AnnotationDirectoryItems of '" + className + "' do not match: " + errorString);
+ } else {
+ logger.log(DEBUG_LEVEL, "AnnotationDirectoryItem of {0}: not null", className);
+ doCheckAnnotations = true;
+ }
+
+ /* Class Annotations */
+ {
+ if (doCheckAnnotations) {
+ checkClassAnnotations(annotationDirectoryItem, candidateAnnotationDirectoryItem,
+ compareElementAnnotation, className);
+ }
+ } /* class annotation */
+
+ /* Field Annotations */
+ {
+ if (doCheckAnnotations) {
+ compareElementAnnotation.reset();
+ compareElementAnnotation.isCandidate = true;
+ assert candidateAnnotationDirectoryItem != null;
+ processFields(compareElementAnnotation, candidateAnnotationDirectoryItem,
+ candidateClassDataItem.getStaticFields());
+ processFields(compareElementAnnotation, candidateAnnotationDirectoryItem,
+ candidateClassDataItem.getInstanceFields());
+ compareElementAnnotation.isCandidate = false;
+ assert annotationDirectoryItem != null;
+ processFields(compareElementAnnotation, annotationDirectoryItem,
+ classDataItem.getStaticFields());
+ processFields(compareElementAnnotation, annotationDirectoryItem,
+ classDataItem.getInstanceFields());
+ }
+ }
+
+ /* Method and parameter Annotations */
+ {
+ if (doCheckAnnotations) {
+ compareElementAnnotation.reset();
+ compareElementAnnotation.isCandidate = true;
+ assert candidateAnnotationDirectoryItem != null;
+ processMethods(compareElementAnnotation, candidateAnnotationDirectoryItem,
+ candidateClassDataItem.getDirectMethods());
+ processMethods(compareElementAnnotation, candidateAnnotationDirectoryItem,
+ candidateClassDataItem.getVirtualMethods());
+ compareElementAnnotation.isCandidate = false;
+ assert annotationDirectoryItem != null;
+ compareElementAnnotation.classData = classDataItem;
+ processMethods(compareElementAnnotation, annotationDirectoryItem,
+ classDataItem.getDirectMethods());
+ processMethods(compareElementAnnotation, annotationDirectoryItem,
+ classDataItem.getVirtualMethods());
+ }
+ }
+ } /* check ClassData field */
+
+ classDataItem = null;
+ candidateClassDataItem = null;
+ } else /* Class */ {
+ logger.log(ERROR_LEVEL, "Class {0} NOK: missing", className);
+
+ if (!TOLERATE_MISSING_SYNTHETICS || !isSynthetic(classDefItem.getAccessFlags())) {
+ throw new DifferenceFoundException("Class " + className + " was not found in candidate.");
+ }
+ }
+ }
+ } /* for each class */
+
+ }
+
+ private void processMethods(@Nonnull CompareElementAnnotation compareElementAnnotation,
+ @Nonnull AnnotationDirectoryItem annotationDirectoryItem,
+ @Nonnull List<EncodedMethod> methods) {
+ for (EncodedMethod method : methods) {
+ AnnotationSetItem methodAnnotations =
+ annotationDirectoryItem.getMethodAnnotations(method.method);
+ if (methodAnnotations != null) {
+ compareElementAnnotation.processMethodAnnotations(method.method, methodAnnotations);
+ }
+ AnnotationSetRefList parameterAnnotations =
+ annotationDirectoryItem.getParameterAnnotations(method.method);
+ if (parameterAnnotations != null) {
+ compareElementAnnotation.processParameterAnnotations(method.method, parameterAnnotations);
+ }
+ }
+ }
+
+ private void processFields(@Nonnull CompareElementAnnotation compareElementAnnotation,
+ @Nonnull AnnotationDirectoryItem annotationDirectoryItem,
+ @Nonnull List<EncodedField> fields) {
+ for (EncodedField field : fields) {
+ AnnotationSetItem fieldAnnotations = annotationDirectoryItem.getFieldAnnotations(field.field);
+ if (fieldAnnotations != null) {
+ compareElementAnnotation.processFieldAnnotations(field.field, fieldAnnotations);
+ }
+ }
+ }
+
+ private void checkClassAnnotations(AnnotationDirectoryItem annotationDirectoryItem,
+ AnnotationDirectoryItem candidateAnnotationDirectoryItem,
+ CompareElementAnnotation compareElementAnnotationDelegate, String className)
+ throws DifferenceFoundException {
+ assert annotationDirectoryItem != null;
+ assert candidateAnnotationDirectoryItem != null;
+ AnnotationSetItem classAnnotations = annotationDirectoryItem.getClassAnnotations();
+ AnnotationSetItem candidateClassAnnotations =
+ candidateAnnotationDirectoryItem.getClassAnnotations();
+
+ if (classAnnotations == null && candidateClassAnnotations == null) {
+ logger.log(DEBUG_LEVEL, "ClassAnnotations of {0} OK: both are null", className);
+ } else if (classAnnotations == null || candidateClassAnnotations == null) {
+ String errorString =
+ ((classAnnotations == null) ? ("reference's is null and not candidate's")
+ : ("candidate's is null and not reference's"));
+
+ logger.log(ERROR_LEVEL, "ClassAnnotations of {0} NOK: {1}",
+ new Object[] {className, errorString});
+
+ boolean onlyAnonymousMemberClassesAnnot = true;
+ if (classAnnotations != null) {
+ AnnotationItem[] annots = classAnnotations.getAnnotations();
+ for (AnnotationItem annot : annots) {
+ TypeIdItem type = annot.getEncodedAnnotation().annotationType;
+ boolean isMemberClassesAnnot =
+ type.getTypeDescriptor().equals(MEMBER_CLASSES_DESCRIPTOR);
+ if (isMemberClassesAnnot) {
+ onlyAnonymousMemberClassesAnnot &=
+ checkMemberClassesAnnotationOnlyHasAnonymousOrSynthetics(
+ annot.getEncodedAnnotation());
+ } else {
+ onlyAnonymousMemberClassesAnnot = false;
+ }
+ if (!onlyAnonymousMemberClassesAnnot) {
+ break;
+ }
+ }
+ }
+
+ if (!onlyAnonymousMemberClassesAnnot) {
+ throw new DifferenceFoundException(
+ "ClassAnnotations of '" + className + "' do not match: " + errorString);
+ } else {
+ return;
+ }
+ } else {
+ compareElementAnnotationDelegate.reset();
+ compareElementAnnotationDelegate.isCandidate = true;
+ compareElementAnnotationDelegate.processGeneric(
+ "class " + className,
+ candidateClassAnnotations);
+ compareElementAnnotationDelegate.isCandidate = false;
+ compareElementAnnotationDelegate.processGeneric(
+ "class " + className, classAnnotations);
+ }
+ }
+
+ private static boolean checkMemberClassesAnnotationOnlyHasAnonymousOrSynthetics(
+ AnnotationEncodedSubValue encodedAnnotation) {
+ boolean result = true;
+ for (EncodedValue value : encodedAnnotation.values) {
+ for (EncodedValue subvalue : ((ArrayEncodedValue) value).values) {
+ TypeIdItem typeId = ((TypeEncodedValue) subvalue).value;
+ result &= isAnonymousTypeName(typeId.getTypeDescriptor());
+ if (!result) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compares annotations for each element (field method) in reference. Please re use instance for
+ * performance's sake (call reset() beforehand)
+ *
+ */
+ private static class CompareElementAnnotation {
+
+ public boolean isCandidate = false;
+ @CheckForNull
+ public ClassDataItem classData = null;
+
+ @Nonnull
+ private final HashMap<String, AnnotationSetItem> candidateElementAnnotations =
+ new HashMap<String, AnnotationSetItem>();
+
+ @Nonnull
+ private final Logger logger;
+
+ public CompareElementAnnotation(Logger logger) {
+ this.logger = logger;
+ }
+
+ public void processGeneric(@Nonnull String elementString,
+ @Nonnull AnnotationSetItem annotations) throws DifferenceFoundException {
+
+ if (isCandidate) {
+ /* fill lookup table */
+ candidateElementAnnotations.put(elementString, annotations);
+ } else {
+
+ /* reference : compare against lookup table */
+ AnnotationSetItem candidateAnnotationsForField =
+ candidateElementAnnotations.get(elementString);
+ AnnotationItem[] candidatesAnnots =
+ (candidateAnnotationsForField != null) ? (candidateAnnotationsForField.getAnnotations())
+ : (new AnnotationItem[] {});
+ AnnotationItem[] refAnnots = annotations.getAnnotations();
+
+ for (int i = 0; i < refAnnots.length; i++) {
+ //for (AnnotationItem annot : refAnnots) {
+ AnnotationItem annot = refAnnots[i];
+
+ // if the annotation is a MemberClass annotation that only contains anonymous classes,
+ // it may not be in our candidate so we ignore it
+ boolean isMemberClassAnnotation = annot
+ .getEncodedAnnotation().annotationType.getTypeDescriptor()
+ .equals(MEMBER_CLASSES_DESCRIPTOR);
+ if (IGNORE_ANONYMOUS_CLASSES && isMemberClassAnnotation) {
+ if (checkMemberClassesAnnotationOnlyHasAnonymousOrSynthetics(annot
+ .getEncodedAnnotation())) {
+ continue;
+ }
+ }
+ boolean isAnnotFound = false;
+ for (AnnotationItem candidateAnnot : candidatesAnnots) {
+ /* found the same annotation for this element? (based on its type) */
+ if (compareTypeIds(annot.getEncodedAnnotation().annotationType,
+ candidateAnnot.getEncodedAnnotation().annotationType)) {
+ checkEncodedAnnotations(annot.getEncodedAnnotation(),
+ candidateAnnot.getEncodedAnnotation(), elementString);
+ isAnnotFound = true;
+ break;
+ }
+ }
+
+ if (!isAnnotFound) {
+ if ((!TOLERATE_MISSING_INITS || !elementString.contains("<init>")) &&
+ (!TOLERATE_MISSING_CLINITS || !elementString.contains("<clinit>"))) {
+ throw new DifferenceFoundException("NOK: for " + elementString
+ + ": Could not find annotation "
+ + annot.getEncodedAnnotation().annotationType.getTypeDescriptor()
+ + " in candidate.");
+ }
+ }
+ }
+ }
+ }
+
+ private boolean compareValues(EncodedValue encodedValue, EncodedValue candEncodedValue,
+ String elementString, String type, String name) throws DifferenceFoundException {
+ boolean isEqual = false;
+ if (encodedValue.getValueType().value == candEncodedValue.getValueType().value) {
+ if (encodedValue instanceof StringEncodedValue) {
+ isEqual = ((StringEncodedValue) encodedValue).value.getStringValue()
+ .equals(((StringEncodedValue) candEncodedValue).value.getStringValue());
+ } else if (encodedValue instanceof TypeEncodedValue) {
+ isEqual = compareTypeIds(
+ ((TypeEncodedValue) encodedValue).value, ((TypeEncodedValue) candEncodedValue).value);
+ } else if (encodedValue instanceof EnumEncodedValue) {
+ isEqual = compareFieldIds(
+ ((EnumEncodedValue) encodedValue).value, ((EnumEncodedValue) candEncodedValue).value);
+ } else if (encodedValue instanceof MethodEncodedValue) {
+ isEqual = compareMethodIds(((MethodEncodedValue) encodedValue).value,
+ ((MethodEncodedValue) candEncodedValue).value);
+ } else if (encodedValue instanceof AnnotationEncodedSubValue) {
+ AnnotationEncodedSubValue encodedAnnotation =
+ (AnnotationEncodedSubValue) encodedValue;
+ AnnotationEncodedSubValue candEncodedAnnotation =
+ (AnnotationEncodedSubValue) candEncodedValue;
+ boolean sameAnnotationType = compareTypeIds(
+ encodedAnnotation.annotationType, candEncodedAnnotation.annotationType);
+ if (sameAnnotationType) {
+ checkEncodedAnnotations(encodedAnnotation, candEncodedAnnotation, elementString);
+ isEqual = true;
+ } else {
+ throw new AssertionError("sub annotation types do not match");
+ }
+ } else if (encodedValue instanceof ArrayEncodedSubValue) {
+ ArrayEncodedSubValue arrayEncodedValue = (ArrayEncodedSubValue) encodedValue;
+ ArrayEncodedSubValue candArrayEncodedValue = (ArrayEncodedSubValue) candEncodedValue;
+ isEqual = true;
+ boolean isMemberClass = type.equals(MEMBER_CLASSES_DESCRIPTOR);
+
+ List<EncodedValue> refEncodedValues = Arrays.asList(arrayEncodedValue.values);
+ List<EncodedValue> candEncodedValues = Arrays.asList(candArrayEncodedValue.values);
+ Collections.sort(refEncodedValues);
+ Collections.sort(candEncodedValues);
+ Iterator<EncodedValue> refEncodedValuesIterator = refEncodedValues.iterator();
+ Iterator<EncodedValue> candEncodedValuesIterator = candEncodedValues.iterator();
+
+ while (refEncodedValuesIterator.hasNext()) {
+ if (!candEncodedValuesIterator.hasNext()) {
+ isEqual = false;
+ break;
+ }
+ EncodedValue refValue = refEncodedValuesIterator.next();
+
+ // our reference may have synthetic anonymous classes that we should ignore
+ if (isMemberClass) {
+ TypeEncodedValue typeRefValue = (TypeEncodedValue) refValue;
+ if (isAnonymousTypeName(typeRefValue.value.getTypeDescriptor())) {
+ continue;
+ }
+ }
+
+ EncodedValue candValue = candEncodedValuesIterator.next();
+ isEqual = compareValues(refValue, candValue, elementString, type, name);
+ if (!isEqual) {
+ break;
+ }
+ }
+ if (candEncodedValuesIterator.hasNext()) {
+ isEqual = false;
+ }
+ } else {
+ isEqual = encodedValue.compareTo(candEncodedValue) == 0;
+ }
+
+ if (!isEqual) {
+ // print access flags of InnerClass annotations
+ if (type.equals("Ldalvik/annotation/InnerClass;") && name.equals("accessFlags")) {
+ int flags = ((IntEncodedValue) encodedValue).value;
+ int candFlags = ((IntEncodedValue) candEncodedValue).value;
+ logger.log(DEBUG_LEVEL, "Difference in access flags of InnerClass annotations of "
+ + elementString + ": ref=0x" + Integer.toHexString(flags) + " - cand=0x"
+ + Integer.toHexString(candFlags));
+ }
+
+ throw new DifferenceFoundException("NOK: for " + elementString + ":annotation "
+ + type
+ + ", name = " + name
+ + " is present but values differ");
+ }
+ } else {
+ throw new DifferenceFoundException("NOK: for " + elementString + ":annotation "
+ + type
+ + ", name = " + name
+ + ", encoded values have different types");
+ }
+ return isEqual;
+ }
+
+ private boolean compareMethodIds(MethodIdItem value, MethodIdItem value2) {
+ boolean isEqual = compareTypeIds(value.getContainingClass(), value2.getContainingClass());
+ isEqual &= compareProtoIds(value.getPrototype(), value2.getPrototype());
+ isEqual &=
+ value.getMethodName().getStringValue().equals(value2.getMethodName().getStringValue());
+ return isEqual;
+ }
+
+ private boolean compareProtoIds(ProtoIdItem value, ProtoIdItem value2) {
+ boolean isEqual = false;
+ if (value == null && value2 == null) {
+ isEqual = true;
+ } else if (value != null && value2 != null) {
+ if (compareTypeIds(value.getReturnType(), value2.getReturnType())) {
+ TypeListItem tyleList = value.getParameters();
+ TypeListItem tyleList2 = value2.getParameters();
+ if (tyleList == null && tyleList2 == null) {
+ isEqual = true;
+ } else if (tyleList != null && tyleList2 != null) {
+ if (tyleList.getTypeCount() == tyleList2.getTypeCount()) {
+ Iterator<TypeIdItem> tyleListIter = tyleList.getTypes().iterator();
+ Iterator<TypeIdItem> tyleListIter2 = tyleList2.getTypes().iterator();
+ isEqual = true;
+ while (isEqual && tyleListIter.hasNext()) {
+ isEqual &= compareTypeIds(tyleListIter.next(), tyleListIter2.next());
+ }
+ }
+ }
+ }
+ }
+ return isEqual;
+ }
+
+ private boolean compareFieldIds(FieldIdItem value, FieldIdItem value2) {
+ boolean isEqual = compareTypeIds(value.getContainingClass(), value2.getContainingClass());
+ isEqual &= compareTypeIds(value.getFieldType(), value2.getFieldType());
+ isEqual &=
+ value.getFieldName().getStringValue().equals(value2.getFieldName().getStringValue());
+ return isEqual;
+ }
+
+ private boolean compareTypeIds(TypeIdItem annotationType, TypeIdItem annotationType2) {
+ return annotationType.getTypeDescriptor().equals(annotationType2.getTypeDescriptor());
+ }
+
+ private void checkEncodedAnnotations(AnnotationEncodedSubValue encodedAnnotation,
+ AnnotationEncodedSubValue encodedAnnotation2, String elementString)
+ throws DifferenceFoundException {
+ assert compareTypeIds(encodedAnnotation.annotationType, encodedAnnotation2.annotationType);
+
+ /* check every name and values for this annotation */
+ for (int i = 0; i < encodedAnnotation.names.length; i++) {
+ StringIdItem name = encodedAnnotation.names[i];
+ boolean isAnnotNameFound = false;
+ for (int j = 0; !isAnnotNameFound && j < encodedAnnotation2.names.length; j++) {
+ StringIdItem candidateName = encodedAnnotation2.names[j];
+ if (name.getStringValue().equals(candidateName.getStringValue())) {
+ EncodedValue subSubValue = encodedAnnotation.values[i];
+ EncodedValue candSubSubValue = encodedAnnotation2.values[j];
+ boolean isEqual = compareValues(subSubValue, candSubSubValue, elementString,
+ encodedAnnotation.annotationType.getTypeDescriptor(), name.getStringValue());
+ if (!isEqual) {
+ throw new DifferenceFoundException("NOK: for " + elementString + ":annotation "
+ + encodedAnnotation.annotationType.getTypeDescriptor()
+ + ", value type differs : reference = "
+ + encodedAnnotation.getValueType().toString() + ", candidate = "
+ + encodedAnnotation2.getValueType().toString());
+ }
+ isAnnotNameFound = true;
+ break; // We found the right name, stop searching
+ }
+ }
+
+ if (!isAnnotNameFound) {
+ throw new DifferenceFoundException("NOK: for " + elementString + ": Could not find name "
+ + name.getStringValue() + " for annotation "
+ + encodedAnnotation.annotationType.getTypeDescriptor() + " in candidate.");
+ }
+ }
+ }
+
+ public void processFieldAnnotations(FieldIdItem field, AnnotationSetItem fieldAnnotations) {
+ try {
+ processGeneric("field " + field.getFieldString(), fieldAnnotations);
+ } catch (DifferenceFoundException e) {
+ //TODO
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void processMethodAnnotations(MethodIdItem method, AnnotationSetItem annotations) {
+ try {
+ String elementString = "method " + method.getMethodString();
+
+ if (isCandidate) {
+ /* fill lookup table */
+ candidateElementAnnotations.put(elementString, annotations);
+ } else {
+ assert classData != null;
+ boolean synthetic = isSynthetic(getMethodAccessFlags(classData, method));
+
+ /* reference : compare against lookup table */
+ AnnotationSetItem candidateAnnotationsForField =
+ candidateElementAnnotations.get(elementString);
+ AnnotationItem[] candidatesAnnots =
+ (candidateAnnotationsForField != null) ? (candidateAnnotationsForField
+ .getAnnotations())
+ : (new AnnotationItem[] {});
+ AnnotationItem[] refAnnots = annotations.getAnnotations();
+
+ for (AnnotationItem annot : refAnnots) {
+ boolean isAnnotFound = false;
+ for (AnnotationItem candidateAnnot : candidatesAnnots) {
+ /* found the same annotation for this element? (based on its type) */
+ if (compareTypeIds(annot.getEncodedAnnotation().annotationType,
+ candidateAnnot.getEncodedAnnotation().annotationType)) {
+ checkEncodedAnnotations(annot.getEncodedAnnotation(),
+ candidateAnnot.getEncodedAnnotation(), elementString);
+ isAnnotFound = true;
+ break;
+ }
+ }
+
+ //TODO
+ if (!isAnnotFound) {
+ if ((!TOLERATE_MISSING_SYNTHETICS || !synthetic) &&
+ (!TOLERATE_MISSING_INITS || !elementString.contains("<init>")) &&
+ (!TOLERATE_MISSING_CLINITS || !elementString.contains("<clinit>"))) {
+ throw new DifferenceFoundException("NOK: for " + elementString
+ + ": Could not find annotation "
+ + annot.getEncodedAnnotation().annotationType.getTypeDescriptor()
+ + " in candidate.");
+ }
+ }
+ }
+ }
+ } catch (DifferenceFoundException e) {
+ //TODO
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void processParameterAnnotations(
+ MethodIdItem method, AnnotationSetRefList parameterAnnotations) {
+ int cptParam = 0;
+ for (AnnotationSetItem asi : parameterAnnotations.getAnnotationSets()) {
+ try {
+ processGeneric("param " + cptParam + " " + method.getMethodString(), asi);
+ } catch (DifferenceFoundException e) {
+ // TODO
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void reset() {
+ isCandidate = false;
+ candidateElementAnnotations.clear();
+ }
+ }
+
+ private static boolean isAnonymousTypeName(String typeName) {
+ //TODO(benoitlamarche): use Annotations to determine if the class is anonymous
+ int location = typeName.lastIndexOf('$');
+ if (location != -1) {
+ String num = typeName.substring(location + 1, typeName.length() - 1);
+ try {
+ Integer.parseInt(num);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean isSynthetic(int modifier) {
+ return ((modifier & AccessFlags.ACC_SYNTHETIC) == AccessFlags.ACC_SYNTHETIC);
+ }
+
+ private static int getMethodAccessFlags(ClassDataItem classData, MethodIdItem methodId) {
+ for (EncodedMethod encodedMethod : classData.getDirectMethods()) {
+ if (encodedMethod.method == methodId) {
+ return encodedMethod.accessFlags;
+ }
+ }
+ for (EncodedMethod encodedMethod : classData.getVirtualMethods()) {
+ if (encodedMethod.method == methodId) {
+ return encodedMethod.accessFlags;
+ }
+ }
+ return -1;
+ }
+} \ No newline at end of file