blob: 3882b25d37efb7349453b1a9ed00ba202e88a547 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2010 Ben Gruver (JesusFreke)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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.*;
public class AnnotationDirectoryItem extends Item<AnnotationDirectoryItem> {
@Nullable
private AnnotationSetItem classAnnotations;
@Nullable
private FieldAnnotation[] fieldAnnotations;
@Nullable
private MethodAnnotation[] methodAnnotations;
@Nullable
private ParameterAnnotation[] parameterAnnotations;
/**
* Creates a new uninitialized <code>AnnotationDirectoryItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
*/
protected AnnotationDirectoryItem(DexFile dexFile) {
super(dexFile);
}
/**
* Creates a new <code>AnnotationDirectoryItem</code> with the given values
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param classAnnotations The annotations associated with the overall class
* @param fieldAnnotations A list of <code>FieldAnnotation</code> objects that contain the field annotations for
* this class
* @param methodAnnotations A list of <code>MethodAnnotation</code> objects that contain the method annotations for
* this class
* @param parameterAnnotations A list of <code>ParameterAnnotation</code> objects that contain the parameter
* annotations for the methods in this class
*/
private AnnotationDirectoryItem(DexFile dexFile, @Nullable AnnotationSetItem classAnnotations,
@Nullable List<FieldAnnotation> fieldAnnotations,
@Nullable List<MethodAnnotation> methodAnnotations,
@Nullable List<ParameterAnnotation> parameterAnnotations) {
super(dexFile);
this.classAnnotations = classAnnotations;
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);
}
}
/**
* Returns an <code>AnnotationDirectoryItem</code> for the given values, and that has been interned into the given
* <code>DexFile</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param classAnnotations The annotations associated with the class
* @param fieldAnnotations A list of <code>FieldAnnotation</code> objects containing the field annotations
* @param methodAnnotations A list of <code>MethodAnnotation</code> objects containing the method annotations
* @param parameterAnnotations A list of <code>ParameterAnnotation</code> objects containin the parameter
* annotations
* @return an <code>AnnotationItem</code> for the given values, and that has been interned into the given
* <code>DexFile</code>
*/
public static AnnotationDirectoryItem internAnnotationDirectoryItem(DexFile dexFile,
AnnotationSetItem classAnnotations,
List<FieldAnnotation> fieldAnnotations,
List<MethodAnnotation> methodAnnotations,
List<ParameterAnnotation> parameterAnnotations) {
AnnotationDirectoryItem annotationDirectoryItem = new AnnotationDirectoryItem(dexFile, classAnnotations,
fieldAnnotations, methodAnnotations, parameterAnnotations);
return dexFile.AnnotationDirectoriesSection.intern(annotationDirectoryItem);
}
/** {@inheritDoc} */
protected void readItem(Input in, ReadContext readContext) {
classAnnotations = (AnnotationSetItem)readContext.getOptionalOffsettedItemByOffset(
ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt());
int fieldAnnotationCount = in.readInt();
if (fieldAnnotationCount > 0) {
fieldAnnotations = new FieldAnnotation[fieldAnnotationCount];
} else {
fieldAnnotations = null;
}
int methodAnnotationCount = in.readInt();
if (methodAnnotationCount > 0) {
methodAnnotations = new MethodAnnotation[methodAnnotationCount];
} else {
methodAnnotations = null;
}
int parameterAnnotationCount = in.readInt();
if (parameterAnnotationCount > 0) {
parameterAnnotations = new ParameterAnnotation[parameterAnnotationCount];
} else {
parameterAnnotations = null;
}
if (fieldAnnotations != null) {
for (int i=0; i<fieldAnnotations.length; i++) {
try {
FieldIdItem fieldIdItem = dexFile.FieldIdsSection.getItemByIndex(in.readInt());
AnnotationSetItem fieldAnnotationSet = (AnnotationSetItem)readContext.getOffsettedItemByOffset(
ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt());
fieldAnnotations[i] = new FieldAnnotation(fieldIdItem, fieldAnnotationSet);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
"Error occured while reading FieldAnnotation at index " + i);
}
}
}
if (methodAnnotations != null) {
for (int i=0; i<methodAnnotations.length; i++) {
try {
MethodIdItem methodIdItem = dexFile.MethodIdsSection.getItemByIndex(in.readInt());
AnnotationSetItem methodAnnotationSet = (AnnotationSetItem)readContext.getOffsettedItemByOffset(
ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt());
methodAnnotations[i] = new MethodAnnotation(methodIdItem, methodAnnotationSet);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
"Error occured while reading MethodAnnotation at index " + i);
}
}
}
if (parameterAnnotations != null) {
for (int i=0; i<parameterAnnotations.length; i++) {
try {
MethodIdItem methodIdItem = dexFile.MethodIdsSection.getItemByIndex(in.readInt());
AnnotationSetRefList paramaterAnnotationSet = (AnnotationSetRefList)readContext.getOffsettedItemByOffset(
ItemType.TYPE_ANNOTATION_SET_REF_LIST, in.readInt());
parameterAnnotations[i] = new ParameterAnnotation(methodIdItem, paramaterAnnotationSet);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,
"Error occured while reading ParameterAnnotation at index " + i);
}
}
}
}
/** {@inheritDoc} */
protected int placeItem(int offset) {
return offset + 16 + (
(fieldAnnotations==null?0:fieldAnnotations.length) +
(methodAnnotations==null?0:methodAnnotations.length) +
(parameterAnnotations==null?0:parameterAnnotations.length)) * 8;
}
/** {@inheritDoc} */
protected void writeItem(AnnotatedOutput out) {
if (out.annotates()) {
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()));
} else {
out.annotate(4, "class_annotations_off:");
}
int length = fieldAnnotations==null?0:fieldAnnotations.length;
out.annotate(4, "annotated_fields_size: 0x" + Integer.toHexString(length) + " (" +
length + ")");
length = methodAnnotations==null?0:methodAnnotations.length;
out.annotate(4, "annotated_methods_size: 0x" + Integer.toHexString(length) + " (" +
length + ")");
length = parameterAnnotations==null?0:parameterAnnotations.length;
out.annotate(4, "annotated_parameters_size: 0x" + Integer.toHexString(length) + " (" +
length + ")");
int index;
if (fieldAnnotations != null) {
index = 0;
for (FieldAnnotation fieldAnnotation: fieldAnnotations) {
out.annotate(0, "[" + index++ + "] field_annotation");
out.indent();
out.annotate(4, "field: " + fieldAnnotation.field.getFieldName().getStringValue() + ":" +
fieldAnnotation.field.getFieldType().getTypeDescriptor());
out.annotate(4, "annotations_off: 0x" +
Integer.toHexString(fieldAnnotation.annotationSet.getOffset()));
out.deindent();
}
}
if (methodAnnotations != null) {
index = 0;
for (MethodAnnotation methodAnnotation: methodAnnotations) {
out.annotate(0, "[" + index++ + "] method_annotation");
out.indent();
out.annotate(4, "method: " + methodAnnotation.method.getMethodString());
out.annotate(4, "annotations_off: 0x" +
Integer.toHexString(methodAnnotation.annotationSet.getOffset()));
out.deindent();
}
}
if (parameterAnnotations != null) {
index = 0;
for (ParameterAnnotation parameterAnnotation: parameterAnnotations) {
out.annotate(0, "[" + index++ + "] parameter_annotation");
out.indent();
out.annotate(4, "method: " + parameterAnnotation.method.getMethodString());
out.annotate(4, "annotations_off: 0x" +
Integer.toHexString(parameterAnnotation.annotationSet.getOffset()));
}
}
}
out.writeInt(classAnnotations==null?0:classAnnotations.getOffset());
out.writeInt(fieldAnnotations==null?0:fieldAnnotations.length);
out.writeInt(methodAnnotations==null?0:methodAnnotations.length);
out.writeInt(parameterAnnotations==null?0:parameterAnnotations.length);
if (fieldAnnotations != null) {
for (FieldAnnotation fieldAnnotation: fieldAnnotations) {
out.writeInt(fieldAnnotation.field.getIndex());
out.writeInt(fieldAnnotation.annotationSet.getOffset());
}
}
if (methodAnnotations != null) {
for (MethodAnnotation methodAnnotation: methodAnnotations) {
out.writeInt(methodAnnotation.method.getIndex());
out.writeInt(methodAnnotation.annotationSet.getOffset());
}
}
if (parameterAnnotations != null) {
for (ParameterAnnotation parameterAnnotation: parameterAnnotations) {
out.writeInt(parameterAnnotation.method.getIndex());
out.writeInt(parameterAnnotation.annotationSet.getOffset());
}
}
}
/** {@inheritDoc} */
public ItemType getItemType() {
return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM;
}
/** {@inheritDoc} */
public String getConciseIdentity() {
TypeIdItem parentType = getParentType();
if (parentType == null) {
return "annotation_directory_item @0x" + Integer.toHexString(getOffset());
}
return "annotation_directory_item @0x" + Integer.toHexString(getOffset()) +
" (" + parentType.getTypeDescriptor() + ")";
}
/** {@inheritDoc} */
public int compareTo(AnnotationDirectoryItem o) {
Preconditions.checkNotNull(o);
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 (classAnnotations != null) {
if (o.classAnnotations != null) {
return classAnnotations.compareTo(o.classAnnotations);
}
return 1;
}
return -1;
}
/**
* 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;
}
/**
* @return An <code>AnnotationSetItem</code> containing the annotations associated with this class, or null
* if there are no class annotations
*/
@Nullable
public AnnotationSetItem getClassAnnotations() {
return classAnnotations;
}
/**
* Get a list of the field annotations in this <code>AnnotationDirectoryItem</code>
* @return A list of FieldAnnotation objects, or null if there are no field annotations
*/
@Nonnull
public List<FieldAnnotation> getFieldAnnotations() {
if (fieldAnnotations == null) {
return Collections.emptyList();
}
return ReadOnlyArrayList.of(fieldAnnotations);
}
/**
* Get a list of the method annotations in this <code>AnnotationDirectoryItem</code>
* @return A list of MethodAnnotation objects, or null if there are no method annotations
*/
@Nonnull
public List<MethodAnnotation> getMethodAnnotations() {
if (methodAnnotations == null) {
return Collections.emptyList();
}
return ReadOnlyArrayList.of(methodAnnotations);
}
/**
* Get a list of the parameter annotations in this <code>AnnotationDirectoryItem</code>
* @return A list of ParameterAnnotation objects, or null if there are no parameter annotations
*/
@Nonnull
public List<ParameterAnnotation> getParameterAnnotations() {
if (parameterAnnotations == null) {
return Collections.emptyList();
}
return ReadOnlyArrayList.of(parameterAnnotations);
}
/**
* Gets the field annotations for the given field, or null if no annotations are defined for that field
* @param fieldIdItem The field to get the annotations for
* @return An <code>AnnotationSetItem</code> containing the field annotations, or null if none are found
*/
@Nullable
public AnnotationSetItem getFieldAnnotations(FieldIdItem fieldIdItem) {
if (fieldAnnotations == null) {
return null;
}
int index = Arrays.binarySearch(fieldAnnotations, fieldIdItem);
if (index < 0) {
return null;
}
return fieldAnnotations[index].annotationSet;
}
/**
* Gets the method annotations for the given method, or null if no annotations are defined for that method
* @param methodIdItem The method to get the annotations for
* @return An <code>AnnotationSetItem</code> containing the method annotations, or null if none are found
*/
@Nullable
public AnnotationSetItem getMethodAnnotations(MethodIdItem methodIdItem) {
if (methodAnnotations == null) {
return null;
}
int index = Arrays.binarySearch(methodAnnotations, methodIdItem);
if (index < 0) {
return null;
}
return methodAnnotations[index].annotationSet;
}
/**
* Gets the parameter annotations for the given method, or null if no parameter annotations are defined for that
* method
* @param methodIdItem The method to get the parameter annotations for
* @return An <code>AnnotationSetRefList</code> containing the parameter annotations, or null if none are found
*/
@Nullable
public AnnotationSetRefList getParameterAnnotations(MethodIdItem methodIdItem) {
if (parameterAnnotations == null) {
return null;
}
int index = Arrays.binarySearch(parameterAnnotations, methodIdItem);
if (index < 0) {
return null;
}
return parameterAnnotations[index].annotationSet;
}
/**
*
*/
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() {
if (fieldAnnotations == null) {
return 0;
}
return fieldAnnotations.length;
}
/**
* @return The number of method annotations in this <code>AnnotationDirectoryItem</code>
*/
public int getMethodAnnotationCount() {
if (methodAnnotations == null) {
return 0;
}
return methodAnnotations.length;
}
/**
* @return The number of parameter annotations in this <code>AnnotationDirectoryItem</code>
*/
public int getParameterAnnotationCount() {
if (parameterAnnotations == null) {
return 0;
}
return parameterAnnotations.length;
}
@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();
}
if (classAnnotations != null) {
return classAnnotations.hashCode();
}
return 0;
}
@Override
public boolean equals(Object o) {
if (this==o) {
return true;
}
if (o==null || !this.getClass().equals(o.getClass())) {
return false;
}
AnnotationDirectoryItem other = (AnnotationDirectoryItem)o;
return (this.compareTo(other) == 0);
}
public static class FieldAnnotation implements Comparable<Convertible<FieldIdItem>>, Convertible<FieldIdItem> {
public final FieldIdItem field;
public final AnnotationSetItem annotationSet;
public FieldAnnotation(FieldIdItem field, AnnotationSetItem annotationSet) {
this.field = field;
this.annotationSet = annotationSet;
}
public int compareTo(Convertible<FieldIdItem> other) {
return field.compareTo(other.convert());
}
@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 FieldIdItem convert() {
return field;
}
}
public static class MethodAnnotation implements Comparable<Convertible<MethodIdItem>>, Convertible<MethodIdItem> {
public final MethodIdItem method;
public final AnnotationSetItem annotationSet;
public MethodAnnotation(MethodIdItem method, AnnotationSetItem annotationSet) {
this.method = method;
this.annotationSet = annotationSet;
}
public int compareTo(Convertible<MethodIdItem> other) {
return method.compareTo(other.convert());
}
@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 MethodIdItem convert() {
return method;
}
}
public static class ParameterAnnotation implements Comparable<Convertible<MethodIdItem>>,
Convertible<MethodIdItem> {
public final MethodIdItem method;
public final AnnotationSetRefList annotationSet;
public ParameterAnnotation(MethodIdItem method, AnnotationSetRefList annotationSet) {
this.method = method;
this.annotationSet = annotationSet;
}
public int compareTo(Convertible<MethodIdItem> other) {
return method.compareTo(other.convert());
}
@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();
}
public MethodIdItem convert() {
return method;
}
}
}