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