/* * [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 java.util.Arrays; import java.util.Collections; import java.util.List; import org.jf.dexlib.Util.AnnotatedOutput; import org.jf.dexlib.Util.ExceptionWithContext; import org.jf.dexlib.Util.Input; import org.jf.dexlib.Util.ReadOnlyArrayList; public class AnnotationDirectoryItem extends Item<AnnotationDirectoryItem> { private AnnotationSetItem classAnnotations; private FieldAnnotation[] fieldAnnotations; private MethodAnnotation[] methodAnnotations; 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, AnnotationSetItem classAnnotations, List<FieldAnnotation> fieldAnnotations, List<MethodAnnotation> methodAnnotations, 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) { 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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; } } }