| /* | |
| * 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(); | |
| } | |
| } | |
| } |