diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java index 3947cb42..8b9e4219 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java @@ -31,6 +31,9 @@ package org.jf.baksmali.Adaptors; import org.jf.dexlib.Util.Utf8Utils; import org.jf.util.IndentingWriter; import org.jf.dexlib.*; +import static org.jf.dexlib.AnnotationDirectoryItem.FieldAnnotation; +import static org.jf.dexlib.AnnotationDirectoryItem.MethodAnnotation; +import static org.jf.dexlib.AnnotationDirectoryItem.ParameterAnnotation; import org.jf.dexlib.Code.Analysis.ValidationException; import org.jf.dexlib.Code.Format.Instruction21c; import org.jf.dexlib.Code.Format.Instruction41c; @@ -74,28 +77,29 @@ public class ClassDefinition { return; } - methodAnnotationsMap = new SparseArray(annotationDirectory.getMethodAnnotationCount()); - annotationDirectory.iterateMethodAnnotations(new AnnotationDirectoryItem.MethodAnnotationIteratorDelegate() { - public void processMethodAnnotations(MethodIdItem method, AnnotationSetItem methodAnnotations) { - methodAnnotationsMap.put(method.getIndex(), methodAnnotations); + int fieldAnnotationCount = annotationDirectory.getFieldAnnotationCount(); + fieldAnnotationsMap = new SparseArray(fieldAnnotationCount); + if (fieldAnnotationCount > 0) { + for (FieldAnnotation fieldAnnotation: annotationDirectory.getFieldAnnotations()) { + fieldAnnotationsMap.put(fieldAnnotation.field.getIndex(), fieldAnnotation.annotationSet); } - }); + } - fieldAnnotationsMap = new SparseArray(annotationDirectory.getFieldAnnotationCount()); - annotationDirectory.iterateFieldAnnotations(new AnnotationDirectoryItem.FieldAnnotationIteratorDelegate() { - public void processFieldAnnotations(FieldIdItem field, AnnotationSetItem fieldAnnotations) { - fieldAnnotationsMap.put(field.getIndex(), fieldAnnotations); + int methodAnnotationCount = annotationDirectory.getMethodAnnotationCount(); + methodAnnotationsMap = new SparseArray(methodAnnotationCount); + if (methodAnnotationCount > 0) { + for (MethodAnnotation methodAnnotation: annotationDirectory.getMethodAnnotations()) { + methodAnnotationsMap.put(methodAnnotation.method.getIndex(), methodAnnotation.annotationSet); } - }); + } - parameterAnnotationsMap = new SparseArray( - annotationDirectory.getParameterAnnotationCount()); - annotationDirectory.iterateParameterAnnotations( - new AnnotationDirectoryItem.ParameterAnnotationIteratorDelegate() { - public void processParameterAnnotations(MethodIdItem method, AnnotationSetRefList parameterAnnotations) { - parameterAnnotationsMap.put(method.getIndex(), parameterAnnotations); + int parameterAnnotationCount = annotationDirectory.getParameterAnnotationCount(); + parameterAnnotationsMap = new SparseArray(parameterAnnotationCount); + if (parameterAnnotationCount > 0) { + for (ParameterAnnotation parameterAnnotation: annotationDirectory.getParameterAnnotations()) { + parameterAnnotationsMap.put(parameterAnnotation.method.getIndex(), parameterAnnotation.annotationSet); } - }); + } } private void findFieldsSetInStaticConstructor() { diff --git a/dexlib/pom.xml b/dexlib/pom.xml index c315164c..b07c0840 100644 --- a/dexlib/pom.xml +++ b/dexlib/pom.xml @@ -24,5 +24,15 @@ junit 4.6 + + com.google.code.findbugs + jsr305 + 1.3.9 + + + com.google.collections + google-collections + 1.0 + \ No newline at end of file diff --git a/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java b/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java index cd436441..165702a7 100644 --- a/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java +++ b/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java @@ -28,32 +28,36 @@ package org.jf.dexlib; +import com.google.common.base.Preconditions; import org.jf.dexlib.Util.AnnotatedOutput; import org.jf.dexlib.Util.ExceptionWithContext; import org.jf.dexlib.Util.Input; +import org.jf.dexlib.Util.ReadOnlyArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; import java.util.List; public class AnnotationDirectoryItem extends Item { + @Nullable private AnnotationSetItem classAnnotations; - - private FieldIdItem[] fieldAnnotationFields; - private AnnotationSetItem[] fieldAnnotations; - - private MethodIdItem[] methodAnnotationMethods; - private AnnotationSetItem[] methodAnnotations; - - private MethodIdItem[] parameterAnnotationMethods; - private AnnotationSetRefList[] parameterAnnotations; + @Nullable + private FieldAnnotation[] fieldAnnotations; + @Nullable + private MethodAnnotation[] methodAnnotations; + @Nullable + private ParameterAnnotation[] parameterAnnotations; /** * typically each AnnotationDirectoryItem will have a distinct parent. The only case that isn't true is when * the AnnotationDirectoryItem *only* contains class annotations, with no other type of annotation. In that * case, the same AnnotationDirectoryItem could be referenced from multiple classes. - * This isn't a problem though, because this field is only used in compareTo to determine the sort order, - * which handles it as a special case + * This isn't a problem though, because this field is only used in compareTo to determine the sort order, which + * which knows it should handle a null value as a special case */ + @Nullable private ClassDefItem parent = null; /** @@ -68,32 +72,43 @@ public class AnnotationDirectoryItem extends Item { * Creates a new AnnotationDirectoryItem with the given values * @param dexFile The DexFile that this item belongs to * @param classAnnotations The annotations associated with the overall class - * @param fieldAnnotationFields An array of FieldIdItem objects that the annotations in - * fieldAnnotations are associated with - * @param fieldAnnotations An array of AnnotationSetItem objects that contain the annotations for the - * fields in fieldAnnotationFields - * @param methodAnnotationMethods An array of MethodIdItem objects that the annotations in - * methodAnnotations are associated with - * @param methodAnnotations An array of AnnotationSetItem objects that contain the annotations for the - * methods in methodAnnotationMethods - * @param parameterAnnotationMethods An array of MethodIdItem objects that the annotations in - * parameterAnnotations are associated with - * @param parameterAnnotations An array of AnnotationSetRefList objects that contain the parameter - * annotations for the methods in parameterAnnotationMethods + * @param fieldAnnotations A list of FieldAnnotation objects that contain the field annotations for + * this class + * @param methodAnnotations A list of MethodAnnotation objects that contain the method annotations for + * this class + * @param parameterAnnotations A list of ParameterAnnotation objects that contain the parameter + * annotations for the methods in this class */ - private AnnotationDirectoryItem(DexFile dexFile, AnnotationSetItem classAnnotations, - FieldIdItem[] fieldAnnotationFields, AnnotationSetItem[] fieldAnnotations, - MethodIdItem[] methodAnnotationMethods, AnnotationSetItem[] methodAnnotations, - MethodIdItem[] parameterAnnotationMethods, - AnnotationSetRefList[] parameterAnnotations) { + private AnnotationDirectoryItem(DexFile dexFile, @Nullable AnnotationSetItem classAnnotations, + @Nullable List fieldAnnotations, + @Nullable List methodAnnotations, + @Nullable List parameterAnnotations) { super(dexFile); this.classAnnotations = classAnnotations; - this.fieldAnnotationFields = fieldAnnotationFields; - this.fieldAnnotations = fieldAnnotations; - this.methodAnnotationMethods = methodAnnotationMethods; - this.methodAnnotations = methodAnnotations; - this.parameterAnnotationMethods = parameterAnnotationMethods; - this.parameterAnnotations = parameterAnnotations; + + if (fieldAnnotations == null || fieldAnnotations.size() == 0) { + this.fieldAnnotations = null; + } else { + this.fieldAnnotations = new FieldAnnotation[fieldAnnotations.size()]; + this.fieldAnnotations = fieldAnnotations.toArray(this.fieldAnnotations); + Arrays.sort(this.fieldAnnotations); + } + + if (methodAnnotations == null || methodAnnotations.size() == 0) { + this.methodAnnotations = null; + } else { + this.methodAnnotations = new MethodAnnotation[methodAnnotations.size()]; + this.methodAnnotations = methodAnnotations.toArray(this.methodAnnotations); + Arrays.sort(this.methodAnnotations); + } + + if (parameterAnnotations == null || parameterAnnotations.size() == 0) { + this.parameterAnnotations = null; + } else { + this.parameterAnnotations = new ParameterAnnotation[parameterAnnotations.size()]; + this.parameterAnnotations = parameterAnnotations.toArray(this.parameterAnnotations); + Arrays.sort(this.parameterAnnotations); + } } /** @@ -113,55 +128,8 @@ public class AnnotationDirectoryItem extends Item { List fieldAnnotations, List methodAnnotations, List parameterAnnotations) { - FieldIdItem[] fieldAnnotationFields = null; - AnnotationSetItem[] fieldAnnotationsArray = null; - MethodIdItem[] methodAnnotationMethods = null; - AnnotationSetItem[] methodAnnotationsArray = null; - MethodIdItem[] parameterAnnotationMethods = null; - AnnotationSetRefList[] parameterAnnotationsArray = null; - - if (fieldAnnotations != null && fieldAnnotations.size() > 0) { - fieldAnnotationFields = new FieldIdItem[fieldAnnotations.size()]; - fieldAnnotationsArray = new AnnotationSetItem[fieldAnnotations.size()]; - - Collections.sort(fieldAnnotations); - - int index = 0; - for (FieldAnnotation fieldAnnotation: fieldAnnotations) { - fieldAnnotationFields[index] = fieldAnnotation.field; - fieldAnnotationsArray[index++] = fieldAnnotation.annotationSet; - } - } - - if (methodAnnotations != null && methodAnnotations.size() > 0) { - methodAnnotationMethods = new MethodIdItem[methodAnnotations.size()]; - methodAnnotationsArray = new AnnotationSetItem[methodAnnotations.size()]; - - Collections.sort(methodAnnotations); - - int index = 0; - for (MethodAnnotation methodAnnotation: methodAnnotations) { - methodAnnotationMethods[index] = methodAnnotation.method; - methodAnnotationsArray[index++] = methodAnnotation.annotationSet; - } - } - - if (parameterAnnotations != null && parameterAnnotations.size() > 0) { - parameterAnnotationMethods = new MethodIdItem[parameterAnnotations.size()]; - parameterAnnotationsArray = new AnnotationSetRefList[parameterAnnotations.size()]; - - Collections.sort(parameterAnnotations); - - int index = 0; - for (ParameterAnnotation parameterAnnotation: parameterAnnotations) { - parameterAnnotationMethods[index] = parameterAnnotation.method; - parameterAnnotationsArray[index++] = parameterAnnotation.annotationSet; - } - } - AnnotationDirectoryItem annotationDirectoryItem = new AnnotationDirectoryItem(dexFile, classAnnotations, - fieldAnnotationFields, fieldAnnotationsArray, methodAnnotationMethods, methodAnnotationsArray, - parameterAnnotationMethods, parameterAnnotationsArray); + fieldAnnotations, methodAnnotations, parameterAnnotations); return dexFile.AnnotationDirectoriesSection.intern(annotationDirectoryItem); } @@ -169,45 +137,67 @@ public class AnnotationDirectoryItem extends Item { protected void readItem(Input in, ReadContext readContext) { classAnnotations = (AnnotationSetItem)readContext.getOptionalOffsettedItemByOffset( ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt()); - fieldAnnotationFields = new FieldIdItem[in.readInt()]; - fieldAnnotations = new AnnotationSetItem[fieldAnnotationFields.length]; - methodAnnotationMethods = new MethodIdItem[in.readInt()]; - methodAnnotations = new AnnotationSetItem[methodAnnotationMethods.length]; + int fieldAnnotationCount = in.readInt(); + if (fieldAnnotationCount > 0) { + fieldAnnotations = new FieldAnnotation[fieldAnnotationCount]; + } else { + fieldAnnotations = null; + } - parameterAnnotationMethods = new MethodIdItem[in.readInt()]; - parameterAnnotations = new AnnotationSetRefList[parameterAnnotationMethods.length]; + int methodAnnotationCount = in.readInt(); + if (methodAnnotationCount > 0) { + methodAnnotations = new MethodAnnotation[methodAnnotationCount]; + } else { + methodAnnotations = null; + } - for (int i=0; i 0) { + parameterAnnotations = new ParameterAnnotation[parameterAnnotationCount]; + } else { + parameterAnnotations = null; + } + + if (fieldAnnotations != null) { + for (int i=0; i { int index; if (fieldAnnotations != null) { index = 0; - for (int i=0; i { out.writeInt(parameterAnnotations==null?0:parameterAnnotations.length); if (fieldAnnotations != null) { - for (int i=0; i { /** {@inheritDoc} */ public int compareTo(AnnotationDirectoryItem o) { + Preconditions.checkNotNull(o); if (!isInternable()) { if (!o.isInternable()) { + Preconditions.checkState(parent != null && o.parent != null, + "Must call setParent before comparing AnnotationDirectoryItem instances"); return parent.compareTo(o.parent); } return -1; @@ -335,91 +331,78 @@ public class AnnotationDirectoryItem extends Item { } /** - * @return The annotations associated with the class + * @return An AnnotationSetItem containing the annotations associated with this class, or null + * if there are no class annotations */ + @Nullable public AnnotationSetItem getClassAnnotations() { return classAnnotations; } /** - * Iterates over the field annotations, calling delegate.processFieldAnnotations for each - * @param delegate the delegate to call + * Get a list of the field annotations in this AnnotationDirectoryItem + * @return A list of FieldAnnotation objects, or null if there are no field annotations */ - public void iterateFieldAnnotations(FieldAnnotationIteratorDelegate delegate) { - for (int i=0; i getFieldAnnotations() { + if (fieldAnnotations == null) { + return Collections.emptyList(); } - } - - public static interface FieldAnnotationIteratorDelegate { - void processFieldAnnotations(FieldIdItem field, AnnotationSetItem fieldAnnotations); + return ReadOnlyArrayList.of(fieldAnnotations); } /** - * @return the number of field annotations in this AnnotationDirectoryItem + * Get a list of the method annotations in this AnnotationDirectoryItem + * @return A list of MethodAnnotation objects, or null if there are no method annotations + */ + @Nonnull + public List getMethodAnnotations() { + if (methodAnnotations == null) { + return Collections.emptyList(); + } + return ReadOnlyArrayList.of(methodAnnotations); + } + + /** + * Get a list of the parameter annotations in this AnnotationDirectoryItem + * @return A list of ParameterAnnotation objects, or null if there are no parameter annotations + */ + @Nonnull + public List getParameterAnnotations() { + if (parameterAnnotations == null) { + return Collections.emptyList(); + } + return ReadOnlyArrayList.of(parameterAnnotations); + } + + /** + * @return The number of field annotations in this AnnotationDirectoryItem */ public int getFieldAnnotationCount() { - return fieldAnnotationFields.length; - } - - /** - * Iterates over the method annotations, calling delegate.processMethodAnnotations for each - * @param delegate the delegate to call - */ - public void iterateMethodAnnotations(MethodAnnotationIteratorDelegate delegate) { - for (int i=0; iAnnotationDirectoryItem + * @return The number of method annotations in this AnnotationDirectoryItem */ public int getMethodAnnotationCount() { - return methodAnnotationMethods.length; - } - - /** - * Iterates over the parameter annotations, calling delegate.processParameterAnnotations for each - * @param delegate the delegate to call - */ - public void iterateParameterAnnotations(ParameterAnnotationIteratorDelegate delegate) { - for (int i=0; iAnnotationDirectoryItem + * @return The number of parameter annotations in this AnnotationDirectoryItem */ public int getParameterAnnotationCount() { - return parameterAnnotationMethods.length; + if (parameterAnnotations == null) { + return 0; + } + return parameterAnnotations.length; } /** @@ -435,19 +418,23 @@ public class AnnotationDirectoryItem extends Item { /** * Sets the ClassDefItem that this AnnotationDirectoryItem is associated with. - * This is only applicable if this AnnotationDirectoryItem contains only class annotations, and no field, method - * or parameter annotations. + * * @param classDefItem the ClassDefItem that this AnnotationDirectoryItem is associated - * with + * with. */ protected void setParent(ClassDefItem classDefItem) { + // If this AnnotationDirectoryItem is internable, then setParent may be called multiple times, because it is + // reused for multiple classes. In this case, the parent field isn't used, so it doesn't matter if we overwrite + // it. + // If, on the other hand, it is not internable, we want to make sure that only a single parent is set. parent + // should either be null, or be equal to the new parent + Preconditions.checkState(this.isInternable() || (parent == null || parent.equals(classDefItem))); this.parent = classDefItem; } @Override public int hashCode() { - //an instance is only internable if it has only class annotations, but - //no other type of annotation + // An instance is internable only if it has only class annotations, but no other type of annotation if (!isInternable()) { return super.hashCode(); } @@ -479,6 +466,19 @@ public class AnnotationDirectoryItem extends Item { public int compareTo(FieldAnnotation other) { return field.compareTo(other.field); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return compareTo((FieldAnnotation)o) == 0; + } + + @Override + public int hashCode() { + return field.hashCode() + 31 * annotationSet.hashCode(); + } } public static class MethodAnnotation implements Comparable { @@ -493,6 +493,19 @@ public class AnnotationDirectoryItem extends Item { public int compareTo(MethodAnnotation other) { return method.compareTo(other.method); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return compareTo((MethodAnnotation)o) == 0; + } + + @Override + public int hashCode() { + return method.hashCode() + 31 * annotationSet.hashCode(); + } } public static class ParameterAnnotation implements Comparable { @@ -507,5 +520,18 @@ public class AnnotationDirectoryItem extends Item { public int compareTo(ParameterAnnotation other) { return method.compareTo(other.method); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return compareTo((ParameterAnnotation)o) == 0; + } + + @Override + public int hashCode() { + return method.hashCode() + 31 * annotationSet.hashCode(); + } } } diff --git a/dexlib/src/main/java/org/jf/dexlib/Util/ReadOnlyArrayList.java b/dexlib/src/main/java/org/jf/dexlib/Util/ReadOnlyArrayList.java index fffd9d3e..2667979d 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Util/ReadOnlyArrayList.java +++ b/dexlib/src/main/java/org/jf/dexlib/Util/ReadOnlyArrayList.java @@ -45,4 +45,8 @@ public class ReadOnlyArrayList extends AbstractList implements RandomAcces public T get(int i) { return arr[i]; } + + public static ReadOnlyArrayList of(T... items) { + return new ReadOnlyArrayList(items); + } }