/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.taobao.dex; import com.taobao.dex.io.DexDataBuffer; import com.taobao.dex.util.FileUtils; import com.taobao.dx.util.Hex; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.AbstractList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.RandomAccess; import java.util.zip.Adler32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * The bytes of a dex file in memory for reading and writing. All int offsets * are unsigned. */ public final class Dex { // Provided as a convenience to avoid a memory allocation to benefit Dalvik. // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik. static final short[] EMPTY_SHORT_ARRAY = new short[0]; private static final int CHECKSUM_OFFSET = 8; private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + SizeOf.CHECKSUM; private final TableOfContents tableOfContents = new TableOfContents(); private final StringTable strings = new StringTable(); private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable(); private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable(); private final ProtoIdTable protoIds = new ProtoIdTable(); private final FieldIdTable fieldIds = new FieldIdTable(); private final MethodIdTable methodIds = new MethodIdTable(); private final ClassDefTable classDefs = new ClassDefTable(); private ByteBuffer data; private int nextSectionStart = 0; private byte[] signature = null; /** * Creates a new dex that reads from {@code data}. It is an error to modify * {@code data} after using it to create a dex buffer. */ public Dex(byte[] data) throws IOException { this(ByteBuffer.wrap(data)); } private Dex(ByteBuffer data) throws IOException { this.data = data; this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.readFrom(this); } /** * Creates a new empty dex of the specified size. */ public Dex(int byteCount) { this.data = ByteBuffer.wrap(new byte[byteCount]); this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.fileSize = byteCount; } /** * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. */ public Dex(InputStream in) throws IOException { loadFrom(in); } public Dex(InputStream in, int initSize) throws IOException { loadFrom(in, initSize); } /** * Creates a new dex buffer from the dex file {@code file}. */ public Dex(File file) throws IOException { if (file == null) { throw new IllegalArgumentException("file is null."); } if (FileUtils.hasArchiveSuffix(file.getName())) { ZipFile zipFile = null; try { zipFile = new ZipFile(file); ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); if (entry != null) { InputStream inputStream = null; try { inputStream = zipFile.getInputStream(entry); loadFrom(inputStream, (int) entry.getSize()); } finally { if (inputStream != null) { inputStream.close(); } } } else { throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); } } finally { if (zipFile != null) { try { zipFile.close(); } catch (Exception e) { // ignored. } } } } else if (file.getName().endsWith(".dex")) { InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); loadFrom(in, (int) file.length()); } catch (Exception e) { throw new DexException(e); } finally { if (in != null) { try { in.close(); } catch (Exception e) { // ignored. } } } } else { throw new DexException("unknown output extension: " + file); } } private static void checkBounds(int index, int length) { if (index < 0 || index >= length) { throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); } } private void loadFrom(InputStream in) throws IOException { loadFrom(in, 0); } private void loadFrom(InputStream in, int initSize) throws IOException { byte[] rawData = FileUtils.readStream(in, initSize); this.data = ByteBuffer.wrap(rawData); this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.readFrom(this); } public void writeTo(OutputStream out) throws IOException { byte[] rawData = data.array(); out.write(rawData); out.flush(); } public void writeTo(File dexOut) throws IOException { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(dexOut)); writeTo(out); } catch (Exception e) { throw new DexException(e); } finally { if (out != null) { try { out.close(); } catch (Exception e) { // ignored. } } } } public TableOfContents getTableOfContents() { return tableOfContents; } /** * <b>IMPORTANT</b> To open a dex section by {@code TableOfContents.Section}, * please use {@code openSection(TableOfContents.Section tocSec)} instead of * passing tocSec.off to this method. * * <b>Because dex section returned by this method never checks * tocSec's bound when reading or writing data.</b> */ public Section openSection(int position) { if (position < 0 || position >= data.capacity()) { throw new IllegalArgumentException( "position=" + position + " length=" + data.capacity() ); } ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(position); sectionData.limit(data.capacity()); return new Section("temp-section", sectionData); } public Section openSection(TableOfContents.Section tocSec) { int position = tocSec.off; if (position < 0 || position >= data.capacity()) { throw new IllegalArgumentException( "position=" + position + " length=" + data.capacity() ); } ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(position); sectionData.limit(position + tocSec.byteCount); return new Section("section", sectionData); } public Section appendSection(int maxByteCount, String name) { int limit = nextSectionStart + maxByteCount; ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(nextSectionStart); sectionData.limit(limit); Section result = new Section(name, sectionData); nextSectionStart = limit; return result; } public int getLength() { return data.capacity(); } public int getNextSectionStart() { return nextSectionStart; } /** * Returns a copy of the the bytes of this dex. */ public byte[] getBytes() { ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe byte[] result = new byte[data.capacity()]; data.position(0); data.get(result); return result; } public List<String> strings() { return strings; } public List<Integer> typeIds() { return typeIds; } public List<String> typeNames() { return typeNames; } public List<ProtoId> protoIds() { return protoIds; } public List<FieldId> fieldIds() { return fieldIds; } public List<MethodId> methodIds() { return methodIds; } public List<ClassDef> classDefs() { return classDefs; } public Iterable<ClassDef> classDefIterable() { return new ClassDefIterable(); } public ClassData readClassData(ClassDef classDef) { int offset = classDef.classDataOffset; if (offset == 0) { throw new IllegalArgumentException("offset == 0"); } return openSection(offset).readClassData(); } public Code readCode(ClassData.Method method) { int offset = method.codeOffset; if (offset == 0) { throw new IllegalArgumentException("offset == 0"); } return openSection(offset).readCode(); } /** * Returns the signature of all but the first 32 bytes of this dex. The * first 32 bytes of dex files are not specified to be included in the * signature. */ public byte[] computeSignature(boolean forceRecompute) { if (this.signature != null) { if (!forceRecompute) { return this.signature; } } MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { throw new AssertionError(); } byte[] buffer = new byte[8192]; ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe data.limit(data.capacity()); data.position(SIGNATURE_OFFSET + SizeOf.SIGNATURE); while (data.hasRemaining()) { int count = Math.min(buffer.length, data.remaining()); data.get(buffer, 0, count); digest.update(buffer, 0, count); } return (this.signature = digest.digest()); } private String bytesToHexString(byte[] bytes) { StringBuilder strBuilder = new StringBuilder(bytes.length << 1); for (byte b : bytes) { strBuilder.append(Hex.u1(b)); } return strBuilder.toString(); } /** * Returns the checksum of all but the first 12 bytes of {@code dex}. */ public int computeChecksum() throws IOException { Adler32 adler32 = new Adler32(); byte[] buffer = new byte[8192]; ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe data.limit(data.capacity()); data.position(CHECKSUM_OFFSET + SizeOf.CHECKSUM); while (data.hasRemaining()) { int count = Math.min(buffer.length, data.remaining()); data.get(buffer, 0, count); adler32.update(buffer, 0, count); } return (int) adler32.getValue(); } /** * Generates the signature and checksum of the dex file {@code out} and * writes them to the file. */ public void writeHashes() throws IOException { openSection(SIGNATURE_OFFSET).write(computeSignature(true)); openSection(CHECKSUM_OFFSET).writeInt(computeChecksum()); } /** * Look up a field id name index from a field index. Cheaper than: * {@code fieldIds().get(fieldDexIndex).getNameIndex();} */ public int nameIndexFromFieldIndex(int fieldIndex) { checkBounds(fieldIndex, tableOfContents.fieldIds.size); int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); position += SizeOf.USHORT; // declaringClassIndex position += SizeOf.USHORT; // typeIndex return data.getInt(position); // nameIndex } public int findStringIndex(String s) { return Collections.binarySearch(strings, s); } public int findTypeIndex(String descriptor) { return Collections.binarySearch(typeNames, descriptor); } public int findFieldIndex(FieldId fieldId) { return Collections.binarySearch(fieldIds, fieldId); } public int findMethodIndex(MethodId methodId) { return Collections.binarySearch(methodIds, methodId); } public int findClassDefIndexFromTypeIndex(int typeIndex) { checkBounds(typeIndex, tableOfContents.typeIds.size); if (!tableOfContents.classDefs.exists()) { return -1; } for (int i = 0; i < tableOfContents.classDefs.size; i++) { if (typeIndexFromClassDefIndex(i) == typeIndex) { return i; } } return -1; } /** * Look up a field id type index from a field index. Cheaper than: * {@code fieldIds().get(fieldDexIndex).getTypeIndex();} */ public int typeIndexFromFieldIndex(int fieldIndex) { checkBounds(fieldIndex, tableOfContents.fieldIds.size); int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); position += SizeOf.USHORT; // declaringClassIndex return data.getShort(position) & 0xFFFF; // typeIndex } /** * Look up a method id declaring class index from a method index. Cheaper than: * {@code methodIds().get(methodIndex).getDeclaringClassIndex();} */ public int declaringClassIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); return data.getShort(position) & 0xFFFF; // declaringClassIndex } /** * Look up a method id name index from a method index. Cheaper than: * {@code methodIds().get(methodIndex).getNameIndex();} */ public int nameIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex position += SizeOf.USHORT; // protoIndex return data.getInt(position); // nameIndex } /** * Look up a parameter type ids from a method index. Cheaper than: * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} */ public short[] parameterTypeIndicesFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex int protoIndex = data.getShort(position) & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex position += SizeOf.UINT; // returnTypeIndex int parametersOffset = data.getInt(position); if (parametersOffset == 0) { return EMPTY_SHORT_ARRAY; } position = parametersOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected parameter type list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } /** * Look up a parameter type ids from a methodId. Cheaper than: * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} */ public short[] parameterTypeIndicesFromMethodId(MethodId methodId) { int protoIndex = methodId.protoIndex & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); int position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex position += SizeOf.UINT; // returnTypeIndex int parametersOffset = data.getInt(position); if (parametersOffset == 0) { return EMPTY_SHORT_ARRAY; } position = parametersOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected parameter type list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } /** * Look up a method id return type index from a method index. Cheaper than: * {@code protoIds().get(methodIds().get(methodDexIndex).getProtoIndex()).getReturnTypeIndex();} */ public int returnTypeIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex int protoIndex = data.getShort(position) & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex return data.getInt(position); // returnTypeIndex } /** * Look up a descriptor index from a type index. Cheaper than: * {@code openSection(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();} */ public int descriptorIndexFromTypeIndex(int typeIndex) { checkBounds(typeIndex, tableOfContents.typeIds.size); int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex); return data.getInt(position); } /** * Look up a type index index from a class def index. */ public int typeIndexFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); return data.getInt(position); } /** * Look up an annotation directory offset from a class def index. */ public int annotationDirectoryOffsetFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); position += SizeOf.UINT; // type position += SizeOf.UINT; // accessFlags position += SizeOf.UINT; // superType position += SizeOf.UINT; // interfacesOffset position += SizeOf.UINT; // sourceFileIndex return data.getInt(position); } /** * Look up interface types indices from a return type index from a method index. Cheaper than: * {@code ...getClassDef(classDefIndex).getInterfaces();} */ public short[] interfaceTypeIndicesFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); position += SizeOf.UINT; // type position += SizeOf.UINT; // accessFlags position += SizeOf.UINT; // superType int interfacesOffset = data.getInt(position); if (interfacesOffset == 0) { return EMPTY_SHORT_ARRAY; } position = interfacesOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected interfaces list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } public short[] interfaceTypeIndicesFromClassDef(ClassDef classDef) { int position = classDef.off; position += SizeOf.UINT; // type position += SizeOf.UINT; // accessFlags position += SizeOf.UINT; // superType int interfacesOffset = data.getInt(position); if (interfacesOffset == 0) { return EMPTY_SHORT_ARRAY; } position = interfacesOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected interfaces list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } public final class Section extends DexDataBuffer { private final String name; private Section(String name, ByteBuffer data) { super(data); this.name = name; } /** * @inheritDoc */ @Override public StringData readStringData() { ensureFourBytesAligned(tableOfContents.stringDatas, false); return super.readStringData(); } /** * @inheritDoc */ @Override public TypeList readTypeList() { ensureFourBytesAligned(tableOfContents.typeLists, false); return super.readTypeList(); } /** * @inheritDoc */ @Override public FieldId readFieldId() { ensureFourBytesAligned(tableOfContents.fieldIds, false); return super.readFieldId(); } /** * @inheritDoc */ @Override public MethodId readMethodId() { ensureFourBytesAligned(tableOfContents.methodIds, false); return super.readMethodId(); } /** * @inheritDoc */ @Override public ProtoId readProtoId() { ensureFourBytesAligned(tableOfContents.protoIds, false); return super.readProtoId(); } /** * @inheritDoc */ @Override public ClassDef readClassDef() { ensureFourBytesAligned(tableOfContents.classDefs, false); return super.readClassDef(); } /** * @inheritDoc */ @Override public Code readCode() { ensureFourBytesAligned(tableOfContents.codes, false); return super.readCode(); } /** * @inheritDoc */ @Override public DebugInfoItem readDebugInfoItem() { ensureFourBytesAligned(tableOfContents.debugInfos, false); return super.readDebugInfoItem(); } /** * @inheritDoc */ @Override public ClassData readClassData() { ensureFourBytesAligned(tableOfContents.classDatas, false); return super.readClassData(); } /** * @inheritDoc */ @Override public Annotation readAnnotation() { ensureFourBytesAligned(tableOfContents.annotations, false); return super.readAnnotation(); } /** * @inheritDoc */ @Override public AnnotationSet readAnnotationSet() { ensureFourBytesAligned(tableOfContents.annotationSets, false); return super.readAnnotationSet(); } /** * @inheritDoc */ @Override public AnnotationSetRefList readAnnotationSetRefList() { ensureFourBytesAligned(tableOfContents.annotationSetRefLists, false); return super.readAnnotationSetRefList(); } /** * @inheritDoc */ @Override public AnnotationsDirectory readAnnotationsDirectory() { ensureFourBytesAligned(tableOfContents.annotationsDirectories, false); return super.readAnnotationsDirectory(); } /** * @inheritDoc */ @Override public EncodedValue readEncodedArray() { ensureFourBytesAligned(tableOfContents.encodedArrays, false); return super.readEncodedArray(); } private void ensureFourBytesAligned(TableOfContents.Section tocSec, boolean isFillWithZero) { if (tocSec.isElementFourByteAligned) { if (isFillWithZero) { alignToFourBytesWithZeroFill(); } else { alignToFourBytes(); } } } /** * @inheritDoc */ @Override public int writeStringData(StringData stringData) { ensureFourBytesAligned(tableOfContents.stringDatas, true); return super.writeStringData(stringData); } /** * @inheritDoc */ @Override public int writeTypeList(TypeList typeList) { ensureFourBytesAligned(tableOfContents.typeLists, true); return super.writeTypeList(typeList); } /** * @inheritDoc */ @Override public int writeFieldId(FieldId fieldId) { ensureFourBytesAligned(tableOfContents.fieldIds, true); return super.writeFieldId(fieldId); } /** * @inheritDoc */ @Override public int writeMethodId(MethodId methodId) { ensureFourBytesAligned(tableOfContents.methodIds, true); return super.writeMethodId(methodId); } /** * @inheritDoc */ @Override public int writeProtoId(ProtoId protoId) { ensureFourBytesAligned(tableOfContents.protoIds, true); return super.writeProtoId(protoId); } /** * @inheritDoc */ @Override public int writeClassDef(ClassDef classDef) { ensureFourBytesAligned(tableOfContents.classDefs, true); return super.writeClassDef(classDef); } /** * @inheritDoc */ @Override public int writeCode(Code code) { ensureFourBytesAligned(tableOfContents.codes, true); return super.writeCode(code); } /** * @inheritDoc */ @Override public int writeDebugInfoItem(DebugInfoItem debugInfoItem) { ensureFourBytesAligned(tableOfContents.debugInfos, true); return super.writeDebugInfoItem(debugInfoItem); } /** * @inheritDoc */ @Override public int writeClassData(ClassData classData) { ensureFourBytesAligned(tableOfContents.classDatas, true); return super.writeClassData(classData); } /** * @inheritDoc */ @Override public int writeAnnotation(Annotation annotation) { ensureFourBytesAligned(tableOfContents.annotations, true); return super.writeAnnotation(annotation); } /** * @inheritDoc */ @Override public int writeAnnotationSet(AnnotationSet annotationSet) { ensureFourBytesAligned(tableOfContents.annotationSets, true); return super.writeAnnotationSet(annotationSet); } /** * @inheritDoc */ @Override public int writeAnnotationSetRefList(AnnotationSetRefList annotationSetRefList) { ensureFourBytesAligned(tableOfContents.annotationSetRefLists, true); return super.writeAnnotationSetRefList(annotationSetRefList); } /** * @inheritDoc */ @Override public int writeAnnotationsDirectory(AnnotationsDirectory annotationsDirectory) { ensureFourBytesAligned(tableOfContents.annotationsDirectories, true); return super.writeAnnotationsDirectory(annotationsDirectory); } /** * @inheritDoc */ @Override public int writeEncodedArray(EncodedValue encodedValue) { ensureFourBytesAligned(tableOfContents.encodedArrays, true); return super.writeEncodedArray(encodedValue); } } private final class StringTable extends AbstractList<String> implements RandomAccess { @Override public String get(int index) { checkBounds(index, tableOfContents.stringIds.size); int stringOff = openSection(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)).readInt(); return openSection(stringOff).readStringData().value; } @Override public int size() { return tableOfContents.stringIds.size; } } private final class TypeIndexToDescriptorIndexTable extends AbstractList<Integer> implements RandomAccess { @Override public Integer get(int index) { return descriptorIndexFromTypeIndex(index); } @Override public int size() { return tableOfContents.typeIds.size; } } private final class TypeIndexToDescriptorTable extends AbstractList<String> implements RandomAccess { @Override public String get(int index) { return strings.get(descriptorIndexFromTypeIndex(index)); } @Override public int size() { return tableOfContents.typeIds.size; } } private final class ProtoIdTable extends AbstractList<ProtoId> implements RandomAccess { @Override public ProtoId get(int index) { checkBounds(index, tableOfContents.protoIds.size); return openSection(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) .readProtoId(); } @Override public int size() { return tableOfContents.protoIds.size; } } private final class FieldIdTable extends AbstractList<FieldId> implements RandomAccess { @Override public FieldId get(int index) { checkBounds(index, tableOfContents.fieldIds.size); return openSection(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) .readFieldId(); } @Override public int size() { return tableOfContents.fieldIds.size; } } private final class MethodIdTable extends AbstractList<MethodId> implements RandomAccess { @Override public MethodId get(int index) { checkBounds(index, tableOfContents.methodIds.size); return openSection(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) .readMethodId(); } @Override public int size() { return tableOfContents.methodIds.size; } } private final class ClassDefTable extends AbstractList<ClassDef> implements RandomAccess { @Override public ClassDef get(int index) { checkBounds(index, tableOfContents.classDefs.size); return openSection(tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * index)) .readClassDef(); } @Override public int size() { return tableOfContents.classDefs.size; } } private final class ClassDefIterator implements Iterator<ClassDef> { private final Section in = openSection(tableOfContents.classDefs); private int count = 0; @Override public boolean hasNext() { return count < tableOfContents.classDefs.size; } @Override public ClassDef next() { if (!hasNext()) { throw new NoSuchElementException(); } count++; return in.readClassDef(); } @Override public void remove() { throw new UnsupportedOperationException(); } } private final class ClassDefIterable implements Iterable<ClassDef> { public Iterator<ClassDef> iterator() { return !tableOfContents.classDefs.exists() ? Collections.<ClassDef>emptySet().iterator() : new ClassDefIterator(); } } }