/*
* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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.dexlib2.dexbacked.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.jf.dexlib2.dexbacked.DexBackedAnnotation;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
public abstract class AnnotationsDirectory {
public static final AnnotationsDirectory EMPTY = new AnnotationsDirectory() {
@Override public int getFieldAnnotationCount() { return 0; }
@Nonnull @Override public Set<? extends DexBackedAnnotation> getClassAnnotations() { return ImmutableSet.of(); }
@Nonnull @Override public AnnotationIterator getFieldAnnotationIterator() { return AnnotationIterator.EMPTY; }
@Nonnull @Override public AnnotationIterator getMethodAnnotationIterator() { return AnnotationIterator.EMPTY; }
@Nonnull @Override public AnnotationIterator getParameterAnnotationIterator() {return AnnotationIterator.EMPTY;}
};
public abstract int getFieldAnnotationCount();
@Nonnull public abstract Set<? extends DexBackedAnnotation> getClassAnnotations();
@Nonnull public abstract AnnotationIterator getFieldAnnotationIterator();
@Nonnull public abstract AnnotationIterator getMethodAnnotationIterator();
@Nonnull public abstract AnnotationIterator getParameterAnnotationIterator();
@Nonnull
public static AnnotationsDirectory newOrEmpty(@Nonnull DexBackedDexFile dexFile,
int directoryAnnotationsOffset) {
if (directoryAnnotationsOffset == 0) {
return EMPTY;
}
return new AnnotationsDirectoryImpl(dexFile, directoryAnnotationsOffset);
}
/**
* This provides a forward-only, skipable iteration over the field_annotation, method_annotation or
* parameter_annotation lists in an annotations_directory_item.
*
* These lists associate a key, either a field or method index, with an offset to where the annotation data for
* that field/method/parameter is stored.
*/
public interface AnnotationIterator {
public static final AnnotationIterator EMPTY = new AnnotationIterator() {
@Override public int seekTo(int key) { return 0; }
@Override public void reset() {}
};
/**
* Seeks the iterator forward, to the first item whose key is >= the requested key. If the requested key value
* is less than that of the item that the iterator currently points to, it will not be moved forward.
*
* If an item with the requested key is found, the associated annotation offset is returned. Otherwise, 0 is
* returned.
*
* @param key The method/field index to search for
* @return The annotation offset associated with the requested key, or 0 if not found.
*/
public int seekTo(int key);
/**
* Resets the iterator to the beginning of its list.
*/
public void reset();
}
@Nonnull
public static Set<? extends DexBackedAnnotation> getAnnotations(@Nonnull final DexBackedDexFile dexFile,
final int annotationSetOffset) {
if (annotationSetOffset != 0) {
final int size = dexFile.readSmallUint(annotationSetOffset);
return new FixedSizeSet<DexBackedAnnotation>() {
@Nonnull
@Override
public DexBackedAnnotation readItem(int index) {
int annotationOffset = dexFile.readSmallUint(annotationSetOffset + 4 + (4*index));
return new DexBackedAnnotation(dexFile, annotationOffset);
}
@Override public int size() { return size; }
};
}
return ImmutableSet.of();
}
@Nonnull
public static List<Set<? extends DexBackedAnnotation>> getParameterAnnotations(
@Nonnull final DexBackedDexFile dexFile, final int annotationSetListOffset) {
if (annotationSetListOffset > 0) {
final int size = dexFile.readSmallUint(annotationSetListOffset);
return new FixedSizeList<Set<? extends DexBackedAnnotation>>() {
@Nonnull
@Override
public Set<? extends DexBackedAnnotation> readItem(int index) {
int annotationSetOffset = dexFile.readSmallUint(annotationSetListOffset + 4 + index * 4);
return getAnnotations(dexFile, annotationSetOffset);
}
@Override public int size() { return size; }
};
}
return ImmutableList.of();
}
private static class AnnotationsDirectoryImpl extends AnnotationsDirectory {
@Nonnull public final DexBackedDexFile dexFile;
private final int directoryOffset;
private static final int FIELD_COUNT_OFFSET = 4;
private static final int METHOD_COUNT_OFFSET = 8;
private static final int PARAMETER_COUNT_OFFSET = 12;
private static final int ANNOTATIONS_START_OFFSET = 16;
/** The size of a field_annotation structure */
private static final int FIELD_ANNOTATION_SIZE = 8;
/** The size of a method_annotation structure */
private static final int METHOD_ANNOTATION_SIZE = 8;
public AnnotationsDirectoryImpl(@Nonnull DexBackedDexFile dexFile,
int directoryOffset) {
this.dexFile = dexFile;
this.directoryOffset = directoryOffset;
}
public int getFieldAnnotationCount() {
return dexFile.readSmallUint(directoryOffset + FIELD_COUNT_OFFSET);
}
public int getMethodAnnotationCount() {
return dexFile.readSmallUint(directoryOffset + METHOD_COUNT_OFFSET);
}
public int getParameterAnnotationCount() {
return dexFile.readSmallUint(directoryOffset + PARAMETER_COUNT_OFFSET);
}
@Nonnull
public Set<? extends DexBackedAnnotation> getClassAnnotations() {
return getAnnotations(dexFile, dexFile.readSmallUint(directoryOffset));
}
@Nonnull
public AnnotationIterator getFieldAnnotationIterator() {
int fieldAnnotationCount = getFieldAnnotationCount();
if (fieldAnnotationCount == 0) {
return AnnotationIterator.EMPTY;
}
return new AnnotationIteratorImpl(directoryOffset + ANNOTATIONS_START_OFFSET, fieldAnnotationCount);
}
@Nonnull
public AnnotationIterator getMethodAnnotationIterator() {
int methodCount = getMethodAnnotationCount();
if (methodCount == 0) {
return AnnotationIterator.EMPTY;
}
int fieldCount = getFieldAnnotationCount();
int methodAnnotationsOffset = directoryOffset + ANNOTATIONS_START_OFFSET +
fieldCount * FIELD_ANNOTATION_SIZE;
return new AnnotationIteratorImpl(methodAnnotationsOffset, methodCount);
}
@Nonnull
public AnnotationIterator getParameterAnnotationIterator() {
int parameterAnnotationCount = getParameterAnnotationCount();
if (parameterAnnotationCount == 0) {
return AnnotationIterator.EMPTY;
}
int fieldCount = getFieldAnnotationCount();
int methodCount = getMethodAnnotationCount();
int parameterAnnotationsOffset = directoryOffset + ANNOTATIONS_START_OFFSET +
fieldCount * FIELD_ANNOTATION_SIZE +
methodCount * METHOD_ANNOTATION_SIZE;
return new AnnotationIteratorImpl(parameterAnnotationsOffset, parameterAnnotationCount);
}
private class AnnotationIteratorImpl implements AnnotationIterator {
private final int startOffset;
private final int size;
private int currentIndex;
private int currentItemIndex;
public AnnotationIteratorImpl(int startOffset, int size) {
this.startOffset = startOffset;
this.size = size;
this.currentItemIndex = dexFile.readSmallUint(startOffset);
this.currentIndex = 0;
}
public int seekTo(int itemIndex) {
while (currentItemIndex < itemIndex && (currentIndex+1) < size) {
currentIndex++;
currentItemIndex = dexFile.readSmallUint(startOffset + (currentIndex*8));
}
if (currentItemIndex == itemIndex) {
return dexFile.readSmallUint(startOffset + (currentIndex*8)+4);
}
return 0;
}
public void reset() {
this.currentItemIndex = dexFile.readSmallUint(startOffset);
this.currentIndex = 0;
}
}
}
}