Change how the parent is determined for AnnotationDirectoryItem and ClassDataItem
diff --git a/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java b/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java
index 3942ce5..3882b25 100644
--- a/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java
+++ b/dexlib/src/main/java/org/jf/dexlib/AnnotationDirectoryItem.java
@@ -49,16 +49,6 @@
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
- * which knows it should handle a null value as a special case
- */
- @Nullable
- private ClassDefItem parent = null;
-
- /**
* Creates a new uninitialized <code>AnnotationDirectoryItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
*/
@@ -211,8 +201,9 @@
/** {@inheritDoc} */
protected void writeItem(AnnotatedOutput out) {
if (out.annotates()) {
- if (!isInternable() && parent != null) {
- out.annotate(0, parent.getClassType().getTypeDescriptor());
+ TypeIdItem parentType = getParentType();
+ if (parentType != null) {
+ out.annotate(0, parentType.getTypeDescriptor());
}
if (classAnnotations != null) {
out.annotate(4, "class_annotations_off: 0x" + Integer.toHexString(classAnnotations.getOffset()));
@@ -303,29 +294,60 @@
/** {@inheritDoc} */
public String getConciseIdentity() {
- if (parent == null) {
+ TypeIdItem parentType = getParentType();
+ if (parentType == null) {
return "annotation_directory_item @0x" + Integer.toHexString(getOffset());
}
- return "annotation_directory_item @0x" + Integer.toHexString(getOffset()) + " (" + parent.getClassType() + ")";
+ return "annotation_directory_item @0x" + Integer.toHexString(getOffset()) +
+ " (" + parentType.getTypeDescriptor() + ")";
}
/** {@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);
+
+ TypeIdItem parentType = getParentType();
+ TypeIdItem otherParentType = o.getParentType();
+ if (parentType != null) {
+ if (otherParentType != null) {
+ return parentType.compareTo(otherParentType);
}
+ return 1;
+ }
+ if (otherParentType != null) {
return -1;
}
- if (!o.isInternable()) {
+ if (classAnnotations != null) {
+ if (o.classAnnotations != null) {
+ return classAnnotations.compareTo(o.classAnnotations);
+ }
return 1;
}
+ return -1;
+ }
- return classAnnotations.compareTo(o.classAnnotations);
+ /**
+ * Returns the parent type for an AnnotationDirectoryItem that is guaranteed to have a single parent, or null
+ * for one that may be referenced by multiple classes.
+ *
+ * Specifically, the AnnotationDirectoryItem may be referenced by multiple classes if it has only class annotations,
+ * but not field/method/parameter annotations.
+ *
+ * @return The parent type for this AnnotationDirectoryItem, or null if it may have multiple parents
+ */
+ @Nullable
+ public TypeIdItem getParentType() {
+ if (fieldAnnotations != null && fieldAnnotations.length > 0) {
+ return fieldAnnotations[0].field.getContainingClass();
+ }
+ if (methodAnnotations != null && methodAnnotations.length > 0) {
+ return methodAnnotations[0].method.getContainingClass();
+ }
+ if (parameterAnnotations != null && parameterAnnotations.length > 0) {
+ return parameterAnnotations[0].method.getContainingClass();
+ }
+ return null;
}
/**
@@ -426,6 +448,17 @@
}
/**
+ *
+ */
+ public int getClassAnnotationCount() {
+ if (classAnnotations == null) {
+ return 0;
+ }
+ AnnotationItem[] annotations = classAnnotations.getAnnotations();
+ return annotations.length;
+ }
+
+ /**
* @return The number of field annotations in this <code>AnnotationDirectoryItem</code>
*/
public int getFieldAnnotationCount() {
@@ -455,40 +488,17 @@
return parameterAnnotations.length;
}
- /**
- * @return true if this <code>AnnotationDirectoryItem</code> is internable. It is only internable if it has
- * only class annotations, but no field, method or parameter annotations
- */
- private boolean isInternable() {
- return classAnnotations != null &&
- (fieldAnnotations == null || fieldAnnotations.length == 0) &&
- (methodAnnotations == null || methodAnnotations.length == 0) &&
- (parameterAnnotations == null || parameterAnnotations.length == 0);
- }
-
- /**
- * Sets the <code>ClassDefItem</code> that this <code>AnnotationDirectoryItem</code> is associated with.
- *
- * @param classDefItem the <code>ClassDefItem</code> that this <code>AnnotationDirectoryItem</code> is associated
- * 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 internable only if it has only class annotations, but no other type of annotation
- if (!isInternable()) {
- return super.hashCode();
+ // If the item has a single parent, we can use the re-use the identity (hash) of that parent
+ TypeIdItem parentType = getParentType();
+ if (parentType != null) {
+ return parentType.hashCode();
}
- return classAnnotations.hashCode();
+ if (classAnnotations != null) {
+ return classAnnotations.hashCode();
+ }
+ return 0;
}
@Override
diff --git a/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java b/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java
index 3371071..9fe605e 100644
--- a/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java
+++ b/dexlib/src/main/java/org/jf/dexlib/ClassDataItem.java
@@ -45,9 +45,6 @@
@Nullable
private EncodedMethod[] virtualMethods = null;
- @Nullable
- private ClassDefItem parent = null;
-
/**
* Creates a new uninitialized <code>ClassDataItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
@@ -384,14 +381,17 @@
/** {@inheritDoc} */
public String getConciseIdentity() {
- if (parent == null) {
+ TypeIdItem parentType = getParentType();
+ if (parentType == null) {
return "class_data_item @0x" + Integer.toHexString(getOffset());
}
- return "class_data_item @0x" + Integer.toHexString(getOffset()) + " (" + parent.getClassType() +")";
+ return "class_data_item @0x" + Integer.toHexString(getOffset()) + " (" + parentType.getTypeDescriptor() +")";
}
/** {@inheritDoc} */
public int compareTo(ClassDataItem other) {
+ Preconditions.checkNotNull(other);
+
// An empty CodeDataItem may be shared by multiple ClassDefItems, so we can't use parent in this case
if (isEmpty()) {
if (other.isEmpty()) {
@@ -403,25 +403,54 @@
return 1;
}
- if (parent == null) {
- if (other.parent == null) {
+ TypeIdItem parentType = getParentType();
+ TypeIdItem otherParentType= other.getParentType();
+ if (parentType == null) {
+ if (otherParentType == null) {
return 0;
}
return -1;
}
- if (other.parent == null) {
+ if (otherParentType == null) {
return 1;
}
- return parent.compareTo(other.parent);
+ return parentType.compareTo(otherParentType);
+ }
+
+ @Override
+ public int hashCode() {
+ // If the item has a single parent, we can use the re-use the identity (hash) of that parent
+ TypeIdItem parentType = getParentType();
+ if (parentType != null) {
+ return parentType.hashCode();
+ }
+ return 0;
}
/**
- * Sets the <code>ClassDefItem</code> that this <code>ClassDataItem</code> is associated with
- * @param classDefItem the <code>ClassDefItem</code> that this <code>ClassDataItem</code> is associated with
+ * Returns the parent type for a non-empty ClassDataItem, or null for an empty one (which could be referenced by
+ * multiple ClassDefItem parents)
+ *
+ * Specifically, the AnnotationDirectoryItem may be referenced by multiple classes if it has only class annotations,
+ * but not field/method/parameter annotations.
+ *
+ * @return The parent type for this AnnotationDirectoryItem, or null if it may have multiple parents
*/
- protected void setParent(ClassDefItem classDefItem) {
- Preconditions.checkState(parent == null || parent.compareTo(classDefItem) == 0 || isEmpty());
- parent = classDefItem;
+ @Nullable
+ public TypeIdItem getParentType() {
+ if (staticFields != null && staticFields.length > 0) {
+ return staticFields[0].field.getContainingClass();
+ }
+ if (instanceFields != null && instanceFields.length > 0) {
+ return instanceFields[0].field.getContainingClass();
+ }
+ if (directMethods != null && directMethods.length > 0) {
+ return directMethods[0].method.getContainingClass();
+ }
+ if (virtualMethods != null && virtualMethods.length > 0) {
+ return virtualMethods[0].method.getContainingClass();
+ }
+ return null;
}
/**
diff --git a/dexlib/src/main/java/org/jf/dexlib/ClassDefItem.java b/dexlib/src/main/java/org/jf/dexlib/ClassDefItem.java
index 3737adb..9664b99 100644
--- a/dexlib/src/main/java/org/jf/dexlib/ClassDefItem.java
+++ b/dexlib/src/main/java/org/jf/dexlib/ClassDefItem.java
@@ -28,7 +28,6 @@
package org.jf.dexlib;
-import com.google.common.base.Preconditions;
import org.jf.dexlib.EncodedValue.ArrayEncodedSubValue;
import org.jf.dexlib.EncodedValue.EncodedValue;
import org.jf.dexlib.Util.AccessFlags;
@@ -88,13 +87,6 @@
this.annotations = annotations;
this.classData = classData;
this.staticFieldInitializers = staticFieldInitializers;
-
- if (classData != null) {
- classData.setParent(this);
- }
- if (annotations != null) {
- annotations.setParent(this);
- }
}
/**
@@ -145,13 +137,6 @@
classData = (ClassDataItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_CLASS_DATA_ITEM, in.readInt());
staticFieldInitializers = (EncodedArrayItem)readContext.getOptionalOffsettedItemByOffset(
ItemType.TYPE_ENCODED_ARRAY_ITEM, in.readInt());
-
- if (classData != null) {
- classData.setParent(this);
- }
- if (annotations != null) {
- annotations.setParent(this);
- }
}
/** {@inheritDoc} */
@@ -344,7 +329,7 @@
*/
private static EncodedArrayItem makeStaticFieldInitializersItem(DexFile dexFile,
@Nonnull List<StaticFieldInitializer> staticFieldInitializers) {
- if (staticFieldInitializers == null || staticFieldInitializers.size() == 0) {
+ if (staticFieldInitializers.size() == 0) {
return null;
}