| /* |
| * 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.android.dex; |
| |
| import com.android.dex.Code.CatchHandler; |
| import com.android.dex.Code.Try; |
| import com.android.dex.MethodHandle.MethodHandleType; |
| import com.android.dex.util.ByteInput; |
| import com.android.dex.util.ByteOutput; |
| import com.android.dex.util.FileUtils; |
| import java.io.ByteArrayOutputStream; |
| 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.io.UTFDataFormatException; |
| 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 { |
| private static final int CHECKSUM_OFFSET = 8; |
| private static final int CHECKSUM_SIZE = 4; |
| private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE; |
| private static final int SIGNATURE_SIZE = 20; |
| // 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 ByteBuffer data; |
| private final TableOfContents tableOfContents = new TableOfContents(); |
| private int nextSectionStart = 0; |
| 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(); |
| |
| /** |
| * 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) throws IOException { |
| this.data = ByteBuffer.wrap(new byte[byteCount]); |
| this.data.order(ByteOrder.LITTLE_ENDIAN); |
| } |
| |
| /** |
| * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. |
| */ |
| public Dex(InputStream in) throws IOException { |
| try { |
| loadFrom(in); |
| } finally { |
| in.close(); |
| } |
| } |
| |
| /** |
| * Creates a new dex buffer from the dex file {@code file}. |
| */ |
| public Dex(File file) throws IOException { |
| if (FileUtils.hasArchiveSuffix(file.getName())) { |
| ZipFile zipFile = new ZipFile(file); |
| ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); |
| if (entry != null) { |
| try (InputStream inputStream = zipFile.getInputStream(entry)) { |
| loadFrom(inputStream); |
| } |
| zipFile.close(); |
| } else { |
| throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); |
| } |
| } else if (file.getName().endsWith(".dex")) { |
| try (InputStream inputStream = new FileInputStream(file)) { |
| loadFrom(inputStream); |
| } |
| } else { |
| throw new DexException("unknown output extension: " + file); |
| } |
| } |
| |
| /** |
| * It is the caller's responsibility to close {@code in}. |
| */ |
| private void loadFrom(InputStream in) throws IOException { |
| ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); |
| byte[] buffer = new byte[8192]; |
| |
| int count; |
| while ((count = in.read(buffer)) != -1) { |
| bytesOut.write(buffer, 0, count); |
| } |
| |
| this.data = ByteBuffer.wrap(bytesOut.toByteArray()); |
| this.data.order(ByteOrder.LITTLE_ENDIAN); |
| this.tableOfContents.readFrom(this); |
| } |
| |
| private static void checkBounds(int index, int length) { |
| if (index < 0 || index >= length) { |
| throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); |
| } |
| } |
| |
| public void writeTo(OutputStream out) throws IOException { |
| byte[] buffer = new byte[8192]; |
| ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe |
| data.clear(); |
| while (data.hasRemaining()) { |
| int count = Math.min(buffer.length, data.remaining()); |
| data.get(buffer, 0, count); |
| out.write(buffer, 0, count); |
| } |
| } |
| |
| public void writeTo(File dexOut) throws IOException { |
| try (OutputStream out = new FileOutputStream(dexOut)) { |
| writeTo(out); |
| } |
| } |
| |
| public TableOfContents getTableOfContents() { |
| return tableOfContents; |
| } |
| |
| public Section open(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("section", sectionData); |
| } |
| |
| public Section appendSection(int maxByteCount, String name) { |
| if ((maxByteCount & 3) != 0) { |
| throw new IllegalStateException("Not four byte aligned!"); |
| } |
| 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 Iterable<ClassDef> classDefs() { |
| return new ClassDefIterable(); |
| } |
| |
| public TypeList readTypeList(int offset) { |
| if (offset == 0) { |
| return TypeList.EMPTY; |
| } |
| return open(offset).readTypeList(); |
| } |
| |
| public ClassData readClassData(ClassDef classDef) { |
| int offset = classDef.getClassDataOffset(); |
| if (offset == 0) { |
| throw new IllegalArgumentException("offset == 0"); |
| } |
| return open(offset).readClassData(); |
| } |
| |
| public Code readCode(ClassData.Method method) { |
| int offset = method.getCodeOffset(); |
| if (offset == 0) { |
| throw new IllegalArgumentException("offset == 0"); |
| } |
| return open(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() throws IOException { |
| 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 + SIGNATURE_SIZE); |
| while (data.hasRemaining()) { |
| int count = Math.min(buffer.length, data.remaining()); |
| data.get(buffer, 0, count); |
| digest.update(buffer, 0, count); |
| } |
| return digest.digest(); |
| } |
| |
| /** |
| * 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 + CHECKSUM_SIZE); |
| 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 { |
| open(SIGNATURE_OFFSET).write(computeSignature()); |
| open(CHECKSUM_OFFSET).writeInt(computeChecksum()); |
| } |
| |
| /** |
| * Look up a descriptor index from a type index. Cheaper than: |
| * {@code open(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); |
| } |
| |
| |
| public final class Section implements ByteInput, ByteOutput { |
| private final String name; |
| private final ByteBuffer data; |
| private final int initialPosition; |
| |
| private Section(String name, ByteBuffer data) { |
| this.name = name; |
| this.data = data; |
| this.initialPosition = data.position(); |
| } |
| |
| public int getPosition() { |
| return data.position(); |
| } |
| |
| public int readInt() { |
| return data.getInt(); |
| } |
| |
| public short readShort() { |
| return data.getShort(); |
| } |
| |
| public int readUnsignedShort() { |
| return readShort() & 0xffff; |
| } |
| |
| @Override |
| public byte readByte() { |
| return data.get(); |
| } |
| |
| public byte[] readByteArray(int length) { |
| byte[] result = new byte[length]; |
| data.get(result); |
| return result; |
| } |
| |
| public short[] readShortArray(int length) { |
| if (length == 0) { |
| return EMPTY_SHORT_ARRAY; |
| } |
| short[] result = new short[length]; |
| for (int i = 0; i < length; i++) { |
| result[i] = readShort(); |
| } |
| return result; |
| } |
| |
| public int readUleb128() { |
| return Leb128.readUnsignedLeb128(this); |
| } |
| |
| public int readUleb128p1() { |
| return Leb128.readUnsignedLeb128(this) - 1; |
| } |
| |
| public int readSleb128() { |
| return Leb128.readSignedLeb128(this); |
| } |
| |
| public void writeUleb128p1(int i) { |
| writeUleb128(i + 1); |
| } |
| |
| public TypeList readTypeList() { |
| int size = readInt(); |
| short[] types = readShortArray(size); |
| alignToFourBytes(); |
| return new TypeList(Dex.this, types); |
| } |
| |
| public String readString() { |
| int offset = readInt(); |
| int savedPosition = data.position(); |
| int savedLimit = data.limit(); |
| data.position(offset); |
| data.limit(data.capacity()); |
| try { |
| int expectedLength = readUleb128(); |
| String result = Mutf8.decode(this, new char[expectedLength]); |
| if (result.length() != expectedLength) { |
| throw new DexException("Declared length " + expectedLength |
| + " doesn't match decoded length of " + result.length()); |
| } |
| return result; |
| } catch (UTFDataFormatException e) { |
| throw new DexException(e); |
| } finally { |
| data.position(savedPosition); |
| data.limit(savedLimit); |
| } |
| } |
| |
| public FieldId readFieldId() { |
| int declaringClassIndex = readUnsignedShort(); |
| int typeIndex = readUnsignedShort(); |
| int nameIndex = readInt(); |
| return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex); |
| } |
| |
| public MethodId readMethodId() { |
| int declaringClassIndex = readUnsignedShort(); |
| int protoIndex = readUnsignedShort(); |
| int nameIndex = readInt(); |
| return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex); |
| } |
| |
| public ProtoId readProtoId() { |
| int shortyIndex = readInt(); |
| int returnTypeIndex = readInt(); |
| int parametersOffset = readInt(); |
| return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset); |
| } |
| |
| public CallSiteId readCallSiteId() { |
| int offset = readInt(); |
| return new CallSiteId(Dex.this, offset); |
| } |
| |
| public MethodHandle readMethodHandle() { |
| MethodHandleType methodHandleType = MethodHandleType.fromValue(readUnsignedShort()); |
| int unused1 = readUnsignedShort(); |
| int fieldOrMethodId = readUnsignedShort(); |
| int unused2 = readUnsignedShort(); |
| return new MethodHandle(Dex.this, methodHandleType, unused1, fieldOrMethodId, unused2); |
| } |
| |
| public ClassDef readClassDef() { |
| int offset = getPosition(); |
| int type = readInt(); |
| int accessFlags = readInt(); |
| int supertype = readInt(); |
| int interfacesOffset = readInt(); |
| int sourceFileIndex = readInt(); |
| int annotationsOffset = readInt(); |
| int classDataOffset = readInt(); |
| int staticValuesOffset = readInt(); |
| return new ClassDef(Dex.this, offset, type, accessFlags, supertype, |
| interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, |
| staticValuesOffset); |
| } |
| |
| private Code readCode() { |
| int registersSize = readUnsignedShort(); |
| int insSize = readUnsignedShort(); |
| int outsSize = readUnsignedShort(); |
| int triesSize = readUnsignedShort(); |
| int debugInfoOffset = readInt(); |
| int instructionsSize = readInt(); |
| short[] instructions = readShortArray(instructionsSize); |
| Try[] tries; |
| CatchHandler[] catchHandlers; |
| if (triesSize > 0) { |
| if (instructions.length % 2 == 1) { |
| readShort(); // padding |
| } |
| |
| /* |
| * We can't read the tries until we've read the catch handlers. |
| * Unfortunately they're in the opposite order in the dex file |
| * so we need to read them out-of-order. |
| */ |
| Section triesSection = open(data.position()); |
| skip(triesSize * SizeOf.TRY_ITEM); |
| catchHandlers = readCatchHandlers(); |
| tries = triesSection.readTries(triesSize, catchHandlers); |
| } else { |
| tries = new Try[0]; |
| catchHandlers = new CatchHandler[0]; |
| } |
| return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions, |
| tries, catchHandlers); |
| } |
| |
| private CatchHandler[] readCatchHandlers() { |
| int baseOffset = data.position(); |
| int catchHandlersSize = readUleb128(); |
| CatchHandler[] result = new CatchHandler[catchHandlersSize]; |
| for (int i = 0; i < catchHandlersSize; i++) { |
| int offset = data.position() - baseOffset; |
| result[i] = readCatchHandler(offset); |
| } |
| return result; |
| } |
| |
| private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) { |
| Try[] result = new Try[triesSize]; |
| for (int i = 0; i < triesSize; i++) { |
| int startAddress = readInt(); |
| int instructionCount = readUnsignedShort(); |
| int handlerOffset = readUnsignedShort(); |
| int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); |
| result[i] = new Try(startAddress, instructionCount, catchHandlerIndex); |
| } |
| return result; |
| } |
| |
| private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) { |
| for (int i = 0; i < catchHandlers.length; i++) { |
| CatchHandler catchHandler = catchHandlers[i]; |
| if (catchHandler.getOffset() == offset) { |
| return i; |
| } |
| } |
| throw new IllegalArgumentException(); |
| } |
| |
| private CatchHandler readCatchHandler(int offset) { |
| int size = readSleb128(); |
| int handlersCount = Math.abs(size); |
| int[] typeIndexes = new int[handlersCount]; |
| int[] addresses = new int[handlersCount]; |
| for (int i = 0; i < handlersCount; i++) { |
| typeIndexes[i] = readUleb128(); |
| addresses[i] = readUleb128(); |
| } |
| int catchAllAddress = size <= 0 ? readUleb128() : -1; |
| return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset); |
| } |
| |
| private ClassData readClassData() { |
| int staticFieldsSize = readUleb128(); |
| int instanceFieldsSize = readUleb128(); |
| int directMethodsSize = readUleb128(); |
| int virtualMethodsSize = readUleb128(); |
| ClassData.Field[] staticFields = readFields(staticFieldsSize); |
| ClassData.Field[] instanceFields = readFields(instanceFieldsSize); |
| ClassData.Method[] directMethods = readMethods(directMethodsSize); |
| ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); |
| return new ClassData(staticFields, instanceFields, directMethods, virtualMethods); |
| } |
| |
| private ClassData.Field[] readFields(int count) { |
| ClassData.Field[] result = new ClassData.Field[count]; |
| int fieldIndex = 0; |
| for (int i = 0; i < count; i++) { |
| fieldIndex += readUleb128(); // field index diff |
| int accessFlags = readUleb128(); |
| result[i] = new ClassData.Field(fieldIndex, accessFlags); |
| } |
| return result; |
| } |
| |
| private ClassData.Method[] readMethods(int count) { |
| ClassData.Method[] result = new ClassData.Method[count]; |
| int methodIndex = 0; |
| for (int i = 0; i < count; i++) { |
| methodIndex += readUleb128(); // method index diff |
| int accessFlags = readUleb128(); |
| int codeOff = readUleb128(); |
| result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns a byte array containing the bytes from {@code start} to this |
| * section's current position. |
| */ |
| private byte[] getBytesFrom(int start) { |
| int end = data.position(); |
| byte[] result = new byte[end - start]; |
| data.position(start); |
| data.get(result); |
| return result; |
| } |
| |
| public Annotation readAnnotation() { |
| byte visibility = readByte(); |
| int start = data.position(); |
| new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue(); |
| return new Annotation(Dex.this, visibility, new EncodedValue(getBytesFrom(start))); |
| } |
| |
| public EncodedValue readEncodedArray() { |
| int start = data.position(); |
| new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); |
| return new EncodedValue(getBytesFrom(start)); |
| } |
| |
| public void skip(int count) { |
| if (count < 0) { |
| throw new IllegalArgumentException(); |
| } |
| data.position(data.position() + count); |
| } |
| |
| /** |
| * Skips bytes until the position is aligned to a multiple of 4. |
| */ |
| public void alignToFourBytes() { |
| data.position((data.position() + 3) & ~3); |
| } |
| |
| /** |
| * Writes 0x00 until the position is aligned to a multiple of 4. |
| */ |
| public void alignToFourBytesWithZeroFill() { |
| while ((data.position() & 3) != 0) { |
| data.put((byte) 0); |
| } |
| } |
| |
| public void assertFourByteAligned() { |
| if ((data.position() & 3) != 0) { |
| throw new IllegalStateException("Not four byte aligned!"); |
| } |
| } |
| |
| public void write(byte[] bytes) { |
| this.data.put(bytes); |
| } |
| |
| @Override |
| public void writeByte(int b) { |
| data.put((byte) b); |
| } |
| |
| public void writeShort(short i) { |
| data.putShort(i); |
| } |
| |
| public void writeUnsignedShort(int i) { |
| short s = (short) i; |
| if (i != (s & 0xffff)) { |
| throw new IllegalArgumentException("Expected an unsigned short: " + i); |
| } |
| writeShort(s); |
| } |
| |
| public void write(short[] shorts) { |
| for (short s : shorts) { |
| writeShort(s); |
| } |
| } |
| |
| public void writeInt(int i) { |
| data.putInt(i); |
| } |
| |
| public void writeUleb128(int i) { |
| try { |
| Leb128.writeUnsignedLeb128(this, i); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new DexException("Section limit " + data.limit() + " exceeded by " + name); |
| } |
| } |
| |
| public void writeSleb128(int i) { |
| try { |
| Leb128.writeSignedLeb128(this, i); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new DexException("Section limit " + data.limit() + " exceeded by " + name); |
| } |
| } |
| |
| public void writeStringData(String value) { |
| try { |
| int length = value.length(); |
| writeUleb128(length); |
| write(Mutf8.encode(value)); |
| writeByte(0); |
| } catch (UTFDataFormatException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| public void writeTypeList(TypeList typeList) { |
| short[] types = typeList.getTypes(); |
| writeInt(types.length); |
| for (short type : types) { |
| writeShort(type); |
| } |
| alignToFourBytesWithZeroFill(); |
| } |
| |
| /** |
| * Returns the number of bytes used by this section. |
| */ |
| public int used() { |
| return data.position() - initialPosition; |
| } |
| } |
| |
| private final class StringTable extends AbstractList<String> implements RandomAccess { |
| @Override |
| public String get(int index) { |
| checkBounds(index, tableOfContents.stringIds.size); |
| return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)) |
| .readString(); |
| } |
| @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 open(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 open(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 open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) |
| .readMethodId(); |
| } |
| @Override |
| public int size() { |
| return tableOfContents.methodIds.size; |
| } |
| } |
| |
| private final class ClassDefIterator implements Iterator<ClassDef> { |
| private final Dex.Section in = open(tableOfContents.classDefs.off); |
| 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> { |
| @Override |
| public Iterator<ClassDef> iterator() { |
| return !tableOfContents.classDefs.exists() |
| ? Collections.<ClassDef>emptySet().iterator() |
| : new ClassDefIterator(); |
| } |
| } |
| } |