1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dex; 18 19import com.android.dex.Code.CatchHandler; 20import com.android.dex.Code.Try; 21import com.android.dex.MethodHandle.MethodHandleType; 22import com.android.dex.util.ByteInput; 23import com.android.dex.util.ByteOutput; 24import com.android.dex.util.FileUtils; 25 26import java.io.ByteArrayOutputStream; 27import java.io.File; 28import java.io.FileInputStream; 29import java.io.FileOutputStream; 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.OutputStream; 33import java.io.UTFDataFormatException; 34import java.nio.ByteBuffer; 35import java.nio.ByteOrder; 36import java.security.MessageDigest; 37import java.security.NoSuchAlgorithmException; 38import java.util.AbstractList; 39import java.util.Collections; 40import java.util.Iterator; 41import java.util.List; 42import java.util.NoSuchElementException; 43import java.util.RandomAccess; 44import java.util.zip.Adler32; 45import java.util.zip.ZipEntry; 46import java.util.zip.ZipFile; 47 48/** 49 * The bytes of a dex file in memory for reading and writing. All int offsets 50 * are unsigned. 51 */ 52public final class Dex { 53 private static final int CHECKSUM_OFFSET = 8; 54 private static final int CHECKSUM_SIZE = 4; 55 private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE; 56 private static final int SIGNATURE_SIZE = 20; 57 // Provided as a convenience to avoid a memory allocation to benefit Dalvik. 58 // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik. 59 static final short[] EMPTY_SHORT_ARRAY = new short[0]; 60 61 private ByteBuffer data; 62 private final TableOfContents tableOfContents = new TableOfContents(); 63 private int nextSectionStart = 0; 64 private final StringTable strings = new StringTable(); 65 private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable(); 66 private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable(); 67 private final ProtoIdTable protoIds = new ProtoIdTable(); 68 private final FieldIdTable fieldIds = new FieldIdTable(); 69 private final MethodIdTable methodIds = new MethodIdTable(); 70 71 /** 72 * Creates a new dex that reads from {@code data}. It is an error to modify 73 * {@code data} after using it to create a dex buffer. 74 */ 75 public Dex(byte[] data) throws IOException { 76 this(ByteBuffer.wrap(data)); 77 } 78 79 private Dex(ByteBuffer data) throws IOException { 80 this.data = data; 81 this.data.order(ByteOrder.LITTLE_ENDIAN); 82 this.tableOfContents.readFrom(this); 83 } 84 85 /** 86 * Creates a new empty dex of the specified size. 87 */ 88 public Dex(int byteCount) throws IOException { 89 this.data = ByteBuffer.wrap(new byte[byteCount]); 90 this.data.order(ByteOrder.LITTLE_ENDIAN); 91 } 92 93 /** 94 * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. 95 */ 96 public Dex(InputStream in) throws IOException { 97 try { 98 loadFrom(in); 99 } finally { 100 in.close(); 101 } 102 } 103 104 /** 105 * Creates a new dex buffer from the dex file {@code file}. 106 */ 107 public Dex(File file) throws IOException { 108 if (FileUtils.hasArchiveSuffix(file.getName())) { 109 ZipFile zipFile = new ZipFile(file); 110 ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); 111 if (entry != null) { 112 try (InputStream inputStream = zipFile.getInputStream(entry)) { 113 loadFrom(inputStream); 114 } 115 zipFile.close(); 116 } else { 117 throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); 118 } 119 } else if (file.getName().endsWith(".dex")) { 120 try (InputStream inputStream = new FileInputStream(file)) { 121 loadFrom(inputStream); 122 } 123 } else { 124 throw new DexException("unknown output extension: " + file); 125 } 126 } 127 128 /** 129 * It is the caller's responsibility to close {@code in}. 130 */ 131 private void loadFrom(InputStream in) throws IOException { 132 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 133 byte[] buffer = new byte[8192]; 134 135 int count; 136 while ((count = in.read(buffer)) != -1) { 137 bytesOut.write(buffer, 0, count); 138 } 139 140 this.data = ByteBuffer.wrap(bytesOut.toByteArray()); 141 this.data.order(ByteOrder.LITTLE_ENDIAN); 142 this.tableOfContents.readFrom(this); 143 } 144 145 private static void checkBounds(int index, int length) { 146 if (index < 0 || index >= length) { 147 throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); 148 } 149 } 150 151 public void writeTo(OutputStream out) throws IOException { 152 byte[] buffer = new byte[8192]; 153 ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe 154 data.clear(); 155 while (data.hasRemaining()) { 156 int count = Math.min(buffer.length, data.remaining()); 157 data.get(buffer, 0, count); 158 out.write(buffer, 0, count); 159 } 160 } 161 162 public void writeTo(File dexOut) throws IOException { 163 try (OutputStream out = new FileOutputStream(dexOut)) { 164 writeTo(out); 165 } 166 } 167 168 public TableOfContents getTableOfContents() { 169 return tableOfContents; 170 } 171 172 public Section open(int position) { 173 if (position < 0 || position >= data.capacity()) { 174 throw new IllegalArgumentException("position=" + position 175 + " length=" + data.capacity()); 176 } 177 ByteBuffer sectionData = data.duplicate(); 178 sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? 179 sectionData.position(position); 180 sectionData.limit(data.capacity()); 181 return new Section("section", sectionData); 182 } 183 184 public Section appendSection(int maxByteCount, String name) { 185 if ((maxByteCount & 3) != 0) { 186 throw new IllegalStateException("Not four byte aligned!"); 187 } 188 int limit = nextSectionStart + maxByteCount; 189 ByteBuffer sectionData = data.duplicate(); 190 sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? 191 sectionData.position(nextSectionStart); 192 sectionData.limit(limit); 193 Section result = new Section(name, sectionData); 194 nextSectionStart = limit; 195 return result; 196 } 197 198 public int getLength() { 199 return data.capacity(); 200 } 201 202 public int getNextSectionStart() { 203 return nextSectionStart; 204 } 205 206 /** 207 * Returns a copy of the the bytes of this dex. 208 */ 209 public byte[] getBytes() { 210 ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe 211 byte[] result = new byte[data.capacity()]; 212 data.position(0); 213 data.get(result); 214 return result; 215 } 216 217 public List<String> strings() { 218 return strings; 219 } 220 221 public List<Integer> typeIds() { 222 return typeIds; 223 } 224 225 public List<String> typeNames() { 226 return typeNames; 227 } 228 229 public List<ProtoId> protoIds() { 230 return protoIds; 231 } 232 233 public List<FieldId> fieldIds() { 234 return fieldIds; 235 } 236 237 public List<MethodId> methodIds() { 238 return methodIds; 239 } 240 241 public Iterable<ClassDef> classDefs() { 242 return new ClassDefIterable(); 243 } 244 245 public TypeList readTypeList(int offset) { 246 if (offset == 0) { 247 return TypeList.EMPTY; 248 } 249 return open(offset).readTypeList(); 250 } 251 252 public ClassData readClassData(ClassDef classDef) { 253 int offset = classDef.getClassDataOffset(); 254 if (offset == 0) { 255 throw new IllegalArgumentException("offset == 0"); 256 } 257 return open(offset).readClassData(); 258 } 259 260 public Code readCode(ClassData.Method method) { 261 int offset = method.getCodeOffset(); 262 if (offset == 0) { 263 throw new IllegalArgumentException("offset == 0"); 264 } 265 return open(offset).readCode(); 266 } 267 268 /** 269 * Returns the signature of all but the first 32 bytes of this dex. The 270 * first 32 bytes of dex files are not specified to be included in the 271 * signature. 272 */ 273 public byte[] computeSignature() throws IOException { 274 MessageDigest digest; 275 try { 276 digest = MessageDigest.getInstance("SHA-1"); 277 } catch (NoSuchAlgorithmException e) { 278 throw new AssertionError(); 279 } 280 byte[] buffer = new byte[8192]; 281 ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe 282 data.limit(data.capacity()); 283 data.position(SIGNATURE_OFFSET + SIGNATURE_SIZE); 284 while (data.hasRemaining()) { 285 int count = Math.min(buffer.length, data.remaining()); 286 data.get(buffer, 0, count); 287 digest.update(buffer, 0, count); 288 } 289 return digest.digest(); 290 } 291 292 /** 293 * Returns the checksum of all but the first 12 bytes of {@code dex}. 294 */ 295 public int computeChecksum() throws IOException { 296 Adler32 adler32 = new Adler32(); 297 byte[] buffer = new byte[8192]; 298 ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe 299 data.limit(data.capacity()); 300 data.position(CHECKSUM_OFFSET + CHECKSUM_SIZE); 301 while (data.hasRemaining()) { 302 int count = Math.min(buffer.length, data.remaining()); 303 data.get(buffer, 0, count); 304 adler32.update(buffer, 0, count); 305 } 306 return (int) adler32.getValue(); 307 } 308 309 /** 310 * Generates the signature and checksum of the dex file {@code out} and 311 * writes them to the file. 312 */ 313 public void writeHashes() throws IOException { 314 open(SIGNATURE_OFFSET).write(computeSignature()); 315 open(CHECKSUM_OFFSET).writeInt(computeChecksum()); 316 } 317 318 /** 319 * Look up a descriptor index from a type index. Cheaper than: 320 * {@code open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();} 321 */ 322 public int descriptorIndexFromTypeIndex(int typeIndex) { 323 checkBounds(typeIndex, tableOfContents.typeIds.size); 324 int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex); 325 return data.getInt(position); 326 } 327 328 329 public final class Section implements ByteInput, ByteOutput { 330 private final String name; 331 private final ByteBuffer data; 332 private final int initialPosition; 333 334 private Section(String name, ByteBuffer data) { 335 this.name = name; 336 this.data = data; 337 this.initialPosition = data.position(); 338 } 339 340 public int getPosition() { 341 return data.position(); 342 } 343 344 public int readInt() { 345 return data.getInt(); 346 } 347 348 public short readShort() { 349 return data.getShort(); 350 } 351 352 public int readUnsignedShort() { 353 return readShort() & 0xffff; 354 } 355 356 @Override 357 public byte readByte() { 358 return data.get(); 359 } 360 361 public byte[] readByteArray(int length) { 362 byte[] result = new byte[length]; 363 data.get(result); 364 return result; 365 } 366 367 public short[] readShortArray(int length) { 368 if (length == 0) { 369 return EMPTY_SHORT_ARRAY; 370 } 371 short[] result = new short[length]; 372 for (int i = 0; i < length; i++) { 373 result[i] = readShort(); 374 } 375 return result; 376 } 377 378 public int readUleb128() { 379 return Leb128.readUnsignedLeb128(this); 380 } 381 382 public int readUleb128p1() { 383 return Leb128.readUnsignedLeb128(this) - 1; 384 } 385 386 public int readSleb128() { 387 return Leb128.readSignedLeb128(this); 388 } 389 390 public void writeUleb128p1(int i) { 391 writeUleb128(i + 1); 392 } 393 394 public TypeList readTypeList() { 395 int size = readInt(); 396 short[] types = readShortArray(size); 397 alignToFourBytes(); 398 return new TypeList(Dex.this, types); 399 } 400 401 public String readString() { 402 int offset = readInt(); 403 int savedPosition = data.position(); 404 int savedLimit = data.limit(); 405 data.position(offset); 406 data.limit(data.capacity()); 407 try { 408 int expectedLength = readUleb128(); 409 String result = Mutf8.decode(this, new char[expectedLength]); 410 if (result.length() != expectedLength) { 411 throw new DexException("Declared length " + expectedLength 412 + " doesn't match decoded length of " + result.length()); 413 } 414 return result; 415 } catch (UTFDataFormatException e) { 416 throw new DexException(e); 417 } finally { 418 data.position(savedPosition); 419 data.limit(savedLimit); 420 } 421 } 422 423 public FieldId readFieldId() { 424 int declaringClassIndex = readUnsignedShort(); 425 int typeIndex = readUnsignedShort(); 426 int nameIndex = readInt(); 427 return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex); 428 } 429 430 public MethodId readMethodId() { 431 int declaringClassIndex = readUnsignedShort(); 432 int protoIndex = readUnsignedShort(); 433 int nameIndex = readInt(); 434 return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex); 435 } 436 437 public ProtoId readProtoId() { 438 int shortyIndex = readInt(); 439 int returnTypeIndex = readInt(); 440 int parametersOffset = readInt(); 441 return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset); 442 } 443 444 public CallSiteId readCallSiteId() { 445 int offset = readInt(); 446 return new CallSiteId(Dex.this, offset); 447 } 448 449 public MethodHandle readMethodHandle() { 450 MethodHandleType methodHandleType = MethodHandleType.fromValue(readUnsignedShort()); 451 int unused1 = readUnsignedShort(); 452 int fieldOrMethodId = readUnsignedShort(); 453 int unused2 = readUnsignedShort(); 454 return new MethodHandle(Dex.this, methodHandleType, unused1, fieldOrMethodId, unused2); 455 } 456 457 public ClassDef readClassDef() { 458 int offset = getPosition(); 459 int type = readInt(); 460 int accessFlags = readInt(); 461 int supertype = readInt(); 462 int interfacesOffset = readInt(); 463 int sourceFileIndex = readInt(); 464 int annotationsOffset = readInt(); 465 int classDataOffset = readInt(); 466 int staticValuesOffset = readInt(); 467 return new ClassDef(Dex.this, offset, type, accessFlags, supertype, 468 interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, 469 staticValuesOffset); 470 } 471 472 private Code readCode() { 473 int registersSize = readUnsignedShort(); 474 int insSize = readUnsignedShort(); 475 int outsSize = readUnsignedShort(); 476 int triesSize = readUnsignedShort(); 477 int debugInfoOffset = readInt(); 478 int instructionsSize = readInt(); 479 short[] instructions = readShortArray(instructionsSize); 480 Try[] tries; 481 CatchHandler[] catchHandlers; 482 if (triesSize > 0) { 483 if (instructions.length % 2 == 1) { 484 readShort(); // padding 485 } 486 487 /* 488 * We can't read the tries until we've read the catch handlers. 489 * Unfortunately they're in the opposite order in the dex file 490 * so we need to read them out-of-order. 491 */ 492 Section triesSection = open(data.position()); 493 skip(triesSize * SizeOf.TRY_ITEM); 494 catchHandlers = readCatchHandlers(); 495 tries = triesSection.readTries(triesSize, catchHandlers); 496 } else { 497 tries = new Try[0]; 498 catchHandlers = new CatchHandler[0]; 499 } 500 return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions, 501 tries, catchHandlers); 502 } 503 504 private CatchHandler[] readCatchHandlers() { 505 int baseOffset = data.position(); 506 int catchHandlersSize = readUleb128(); 507 CatchHandler[] result = new CatchHandler[catchHandlersSize]; 508 for (int i = 0; i < catchHandlersSize; i++) { 509 int offset = data.position() - baseOffset; 510 result[i] = readCatchHandler(offset); 511 } 512 return result; 513 } 514 515 private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) { 516 Try[] result = new Try[triesSize]; 517 for (int i = 0; i < triesSize; i++) { 518 int startAddress = readInt(); 519 int instructionCount = readUnsignedShort(); 520 int handlerOffset = readUnsignedShort(); 521 int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); 522 result[i] = new Try(startAddress, instructionCount, catchHandlerIndex); 523 } 524 return result; 525 } 526 527 private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) { 528 for (int i = 0; i < catchHandlers.length; i++) { 529 CatchHandler catchHandler = catchHandlers[i]; 530 if (catchHandler.getOffset() == offset) { 531 return i; 532 } 533 } 534 throw new IllegalArgumentException(); 535 } 536 537 private CatchHandler readCatchHandler(int offset) { 538 int size = readSleb128(); 539 int handlersCount = Math.abs(size); 540 int[] typeIndexes = new int[handlersCount]; 541 int[] addresses = new int[handlersCount]; 542 for (int i = 0; i < handlersCount; i++) { 543 typeIndexes[i] = readUleb128(); 544 addresses[i] = readUleb128(); 545 } 546 int catchAllAddress = size <= 0 ? readUleb128() : -1; 547 return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset); 548 } 549 550 private ClassData readClassData() { 551 int staticFieldsSize = readUleb128(); 552 int instanceFieldsSize = readUleb128(); 553 int directMethodsSize = readUleb128(); 554 int virtualMethodsSize = readUleb128(); 555 ClassData.Field[] staticFields = readFields(staticFieldsSize); 556 ClassData.Field[] instanceFields = readFields(instanceFieldsSize); 557 ClassData.Method[] directMethods = readMethods(directMethodsSize); 558 ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); 559 return new ClassData(staticFields, instanceFields, directMethods, virtualMethods); 560 } 561 562 private ClassData.Field[] readFields(int count) { 563 ClassData.Field[] result = new ClassData.Field[count]; 564 int fieldIndex = 0; 565 for (int i = 0; i < count; i++) { 566 fieldIndex += readUleb128(); // field index diff 567 int accessFlags = readUleb128(); 568 result[i] = new ClassData.Field(fieldIndex, accessFlags); 569 } 570 return result; 571 } 572 573 private ClassData.Method[] readMethods(int count) { 574 ClassData.Method[] result = new ClassData.Method[count]; 575 int methodIndex = 0; 576 for (int i = 0; i < count; i++) { 577 methodIndex += readUleb128(); // method index diff 578 int accessFlags = readUleb128(); 579 int codeOff = readUleb128(); 580 result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); 581 } 582 return result; 583 } 584 585 /** 586 * Returns a byte array containing the bytes from {@code start} to this 587 * section's current position. 588 */ 589 private byte[] getBytesFrom(int start) { 590 int end = data.position(); 591 byte[] result = new byte[end - start]; 592 data.position(start); 593 data.get(result); 594 return result; 595 } 596 597 public Annotation readAnnotation() { 598 byte visibility = readByte(); 599 int start = data.position(); 600 new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue(); 601 return new Annotation(Dex.this, visibility, new EncodedValue(getBytesFrom(start))); 602 } 603 604 public EncodedValue readEncodedArray() { 605 int start = data.position(); 606 new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); 607 return new EncodedValue(getBytesFrom(start)); 608 } 609 610 public void skip(int count) { 611 if (count < 0) { 612 throw new IllegalArgumentException(); 613 } 614 data.position(data.position() + count); 615 } 616 617 /** 618 * Skips bytes until the position is aligned to a multiple of 4. 619 */ 620 public void alignToFourBytes() { 621 data.position((data.position() + 3) & ~3); 622 } 623 624 /** 625 * Writes 0x00 until the position is aligned to a multiple of 4. 626 */ 627 public void alignToFourBytesWithZeroFill() { 628 while ((data.position() & 3) != 0) { 629 data.put((byte) 0); 630 } 631 } 632 633 public void assertFourByteAligned() { 634 if ((data.position() & 3) != 0) { 635 throw new IllegalStateException("Not four byte aligned!"); 636 } 637 } 638 639 public void write(byte[] bytes) { 640 this.data.put(bytes); 641 } 642 643 @Override 644 public void writeByte(int b) { 645 data.put((byte) b); 646 } 647 648 public void writeShort(short i) { 649 data.putShort(i); 650 } 651 652 public void writeUnsignedShort(int i) { 653 short s = (short) i; 654 if (i != (s & 0xffff)) { 655 throw new IllegalArgumentException("Expected an unsigned short: " + i); 656 } 657 writeShort(s); 658 } 659 660 public void write(short[] shorts) { 661 for (short s : shorts) { 662 writeShort(s); 663 } 664 } 665 666 public void writeInt(int i) { 667 data.putInt(i); 668 } 669 670 public void writeUleb128(int i) { 671 try { 672 Leb128.writeUnsignedLeb128(this, i); 673 } catch (ArrayIndexOutOfBoundsException e) { 674 throw new DexException("Section limit " + data.limit() + " exceeded by " + name); 675 } 676 } 677 678 public void writeSleb128(int i) { 679 try { 680 Leb128.writeSignedLeb128(this, i); 681 } catch (ArrayIndexOutOfBoundsException e) { 682 throw new DexException("Section limit " + data.limit() + " exceeded by " + name); 683 } 684 } 685 686 public void writeStringData(String value) { 687 try { 688 int length = value.length(); 689 writeUleb128(length); 690 write(Mutf8.encode(value)); 691 writeByte(0); 692 } catch (UTFDataFormatException e) { 693 throw new AssertionError(); 694 } 695 } 696 697 public void writeTypeList(TypeList typeList) { 698 short[] types = typeList.getTypes(); 699 writeInt(types.length); 700 for (short type : types) { 701 writeShort(type); 702 } 703 alignToFourBytesWithZeroFill(); 704 } 705 706 /** 707 * Returns the number of bytes used by this section. 708 */ 709 public int used() { 710 return data.position() - initialPosition; 711 } 712 } 713 714 private final class StringTable extends AbstractList<String> implements RandomAccess { 715 @Override public String get(int index) { 716 checkBounds(index, tableOfContents.stringIds.size); 717 return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)) 718 .readString(); 719 } 720 @Override public int size() { 721 return tableOfContents.stringIds.size; 722 } 723 } 724 725 private final class TypeIndexToDescriptorIndexTable extends AbstractList<Integer> 726 implements RandomAccess { 727 @Override public Integer get(int index) { 728 return descriptorIndexFromTypeIndex(index); 729 } 730 @Override public int size() { 731 return tableOfContents.typeIds.size; 732 } 733 } 734 735 private final class TypeIndexToDescriptorTable extends AbstractList<String> 736 implements RandomAccess { 737 @Override public String get(int index) { 738 return strings.get(descriptorIndexFromTypeIndex(index)); 739 } 740 @Override public int size() { 741 return tableOfContents.typeIds.size; 742 } 743 } 744 745 private final class ProtoIdTable extends AbstractList<ProtoId> implements RandomAccess { 746 @Override public ProtoId get(int index) { 747 checkBounds(index, tableOfContents.protoIds.size); 748 return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) 749 .readProtoId(); 750 } 751 @Override public int size() { 752 return tableOfContents.protoIds.size; 753 } 754 } 755 756 private final class FieldIdTable extends AbstractList<FieldId> implements RandomAccess { 757 @Override public FieldId get(int index) { 758 checkBounds(index, tableOfContents.fieldIds.size); 759 return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) 760 .readFieldId(); 761 } 762 @Override public int size() { 763 return tableOfContents.fieldIds.size; 764 } 765 } 766 767 private final class MethodIdTable extends AbstractList<MethodId> implements RandomAccess { 768 @Override public MethodId get(int index) { 769 checkBounds(index, tableOfContents.methodIds.size); 770 return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) 771 .readMethodId(); 772 } 773 @Override public int size() { 774 return tableOfContents.methodIds.size; 775 } 776 } 777 778 private final class ClassDefIterator implements Iterator<ClassDef> { 779 private final Dex.Section in = open(tableOfContents.classDefs.off); 780 private int count = 0; 781 782 @Override 783 public boolean hasNext() { 784 return count < tableOfContents.classDefs.size; 785 } 786 @Override 787 public ClassDef next() { 788 if (!hasNext()) { 789 throw new NoSuchElementException(); 790 } 791 count++; 792 return in.readClassDef(); 793 } 794 @Override 795 public void remove() { 796 throw new UnsupportedOperationException(); 797 } 798 } 799 800 private final class ClassDefIterable implements Iterable<ClassDef> { 801 @Override 802 public Iterator<ClassDef> iterator() { 803 return !tableOfContents.classDefs.exists() 804 ? Collections.<ClassDef>emptySet().iterator() 805 : new ClassDefIterator(); 806 } 807 } 808} 809