| 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; |
| } |
| } |