Make DexMerger's dex reader general purpose.

Use this to implement FindUsages, which prints references to
fields and methods within a dex. This is the FindUsages output
for "Ljava/lang/Number;", "longValue" on libcore's dex file:

Method referenced by Ljava/io/EmulatedFields;#get invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/io/EmulatedFieldsForDumping;#write invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/io/ObjectOutputStream;#writeFieldValues invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method declared by Ljava/lang/Number;#longValue
Method declared by Ljava/lang/Byte;#longValue
Method declared by Ljava/lang/Double;#longValue
Method declared by Ljava/lang/Float;#longValue
Method declared by Ljava/lang/Integer;#longValue
Method declared by Ljava/lang/Long;#longValue
Method declared by Ljava/lang/Short;#longValue
Method referenced by Ljava/lang/reflect/Array;#set invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#divideBigIntegers invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#divideBigIntegers invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#divideBigIntegers invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#readObject invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#setUnscaledValue invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#valueExact invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/BigDecimal;#doubleValue invoke-virtual/range {vCCCC..vNNNN}, meth@BBBB
Method referenced by Ljava/math/BigDecimal;#doubleValue invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method declared by Ljava/math/BigDecimal;#longValue
Method referenced by Ljava/math/BigDecimal;#longValue invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method declared by Ljava/math/BigInteger;#longValue
Method referenced by Ljava/math/Conversion;#bigInteger2Double invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/math/Conversion;#bigInteger2Double invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/text/NumberFormat;#format invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/text/DateFormat;#format invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/text/DecimalFormat;#format invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/text/DecimalFormat;#parse invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/text/SimpleDateFormat;#formatToCharacterIterator invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transform invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transformFromDateTime invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transformFromInteger invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transformFromInteger invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transformFromInteger invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transformFromInteger invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transform_g invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transform_g invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transform_g invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Formatter;#transform_g invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Ljava/util/Scanner;#nextLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method declared by Ljava/util/concurrent/atomic/AtomicInteger;#longValue
Method declared by Ljava/util/concurrent/atomic/AtomicLong;#longValue
Method referenced by Llibcore/icu/NativeDecimalFormat;#formatToCharacterIterator invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xalan/xslt/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xml/dtm/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xml/dtm/ref/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xml/serializer/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xml/utils/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/apache/xpath/functions/SecuritySupport12;#getLastModified invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSON;#toLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSONArray;#getLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSONArray;#optLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSONObject;#numberToString invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSONObject;#getLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC
Method referenced by Lorg/json/JSONObject;#optLong invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC

Change-Id: Ia50f28f53ce5838799e2d5f03a7a2f1c551299de
diff --git a/dx/src/com/android/dx/command/Main.java b/dx/src/com/android/dx/command/Main.java
index 70a94b0..09f543f 100644
--- a/dx/src/com/android/dx/command/Main.java
+++ b/dx/src/com/android/dx/command/Main.java
@@ -52,6 +52,11 @@
         "human-oriented format.\n" +
         "  dx --junit [-wait] <TestClass>\n" +
         "    Run the indicated unit test.\n" +
+        "  dx --find-usages <file.dex> <declaring type> <member>\n" +
+        "    Find references and declarations to a field or method.\n" +
+        "    declaring type: a class name in internal form, like " +
+        "Ljava/lang/Object;\n" +
+        "    member: a field or method name, like hashCode\n" +
         "  dx -J<option> ... <arguments, in one of the above " +
         "forms>\n" +
         "    Pass VM-specific options to the virtual machine that " +
@@ -99,6 +104,9 @@
                 } else if (arg.equals("--junit")) {
                     TestRunner.main(without(args, i));
                     break;
+                } else if (arg.equals("--find-usages")) {
+                    com.android.dx.command.findusages.Main.main(without(args, i));
+                    break;
                 } else if (arg.equals("--version")) {
                     version();
                     break;
diff --git a/dx/src/com/android/dx/command/findusages/FindUsages.java b/dx/src/com/android/dx/command/findusages/FindUsages.java
new file mode 100644
index 0000000..59e5cce
--- /dev/null
+++ b/dx/src/com/android/dx/command/findusages/FindUsages.java
@@ -0,0 +1,190 @@
+/*
+ * 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.dx.command.findusages;
+
+import com.android.dx.io.ClassData;
+import com.android.dx.io.ClassDef;
+import com.android.dx.io.CodeReader;
+import com.android.dx.io.DexBuffer;
+import com.android.dx.io.FieldId;
+import com.android.dx.io.MethodId;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class FindUsages {
+    private final DexBuffer dex;
+    private final Set<Integer> methodIds;
+    private final Set<Integer> fieldIds;
+    private final CodeReader codeReader = new CodeReader();
+    private final PrintStream out;
+
+    private ClassDef currentClass;
+    private ClassData.Method currentMethod;
+
+    public FindUsages(DexBuffer dex, String declaredBy, String memberName, final PrintStream out) {
+        this.dex = dex;
+        this.out = out;
+
+        int typeStringIndex = Collections.binarySearch(dex.strings(), declaredBy);
+        int memberNameIndex = Collections.binarySearch(dex.strings(), memberName);
+        if (typeStringIndex < 0 || memberNameIndex < 0) {
+            methodIds = null;
+            fieldIds = null;
+            return; // these symbols are not mentioned in this dex
+        }
+
+        int typeIndex = Collections.binarySearch(dex.typeIds(), typeStringIndex);
+        if (typeIndex < 0) {
+            methodIds = null;
+            fieldIds = null;
+            return; // this type name isn't used as a type in this dex
+        }
+
+        methodIds = getMethodIds(dex, memberNameIndex, typeIndex);
+        fieldIds = getFieldIds(dex, memberNameIndex, typeIndex);
+
+        codeReader.setFieldVisitor(new CodeReader.Visitor() {
+            public void visit(
+                    CodeReader.Instruction instruction, short[] instructions, int offset) {
+                int field = instructions[offset + 1];
+                if (fieldIds.contains(field)) {
+                    out.println("Field referenced by " + location() + " " + instruction);
+                }
+            }
+        });
+
+        codeReader.setMethodVisitor(new CodeReader.Visitor() {
+            public void visit(
+                    CodeReader.Instruction instruction, short[] instructions, int offset) {
+                int methodId = instructions[offset + 1];
+                if (methodIds.contains(methodId)) {
+                    out.println("Method referenced by " + location() + " " + instruction);
+                }
+            }
+        });
+    }
+
+    private String location() {
+        String className = dex.typeNames().get(currentClass.getTypeIndex());
+        if (currentMethod != null) {
+            MethodId methodId = dex.methodIds().get(currentMethod.getMethodIndex());
+            return className + "#" + dex.strings().get(methodId.getNameIndex());
+        } else {
+            return className;
+        }
+    }
+
+    /**
+     * Prints usages to out.
+     */
+    public void findUsages() {
+        if (fieldIds == null || methodIds == null) {
+            return;
+        }
+
+        for (ClassDef classDef : dex.classDefs()) {
+            currentClass = classDef;
+            currentMethod = null;
+
+            if (classDef.getClassDataOffset() == 0) {
+                continue;
+            }
+
+            ClassData classData = dex.readClassData(classDef);
+            for (ClassData.Field field : classData.allFields()) {
+                if (fieldIds.contains(field.getFieldIndex())) {
+                    out.println("Field declared by " + location());
+                }
+            }
+
+            for (ClassData.Method method : classData.allMethods()) {
+                currentMethod = method;
+                if (methodIds.contains(method.getMethodIndex())) {
+                    out.println("Method declared by " + location());
+                }
+                if (method.getCodeOffset() != 0) {
+                    codeReader.visitAll(dex.readCode(method).getInstructions());
+                }
+            }
+        }
+
+        currentClass = null;
+        currentMethod = null;
+    }
+
+    /**
+     * Returns the fields with {@code memberNameIndex} declared by {@code
+     * declaringType}.
+     */
+    private Set<Integer> getFieldIds(DexBuffer dex, int memberNameIndex, int declaringType) {
+        Set<Integer> fields = new HashSet<Integer>();
+        int fieldIndex = 0;
+        for (FieldId fieldId : dex.fieldIds()) {
+            if (fieldId.getNameIndex() == memberNameIndex
+                    && declaringType == (int) fieldId.getDeclaringClassIndex()) {
+                fields.add(fieldIndex);
+            }
+            fieldIndex++;
+        }
+        return fields;
+    }
+
+    /**
+     * Returns the methods with {@code memberNameIndex} declared by {@code
+     * declaringType} and its subtypes.
+     */
+    private Set<Integer> getMethodIds(DexBuffer dex, int memberNameIndex, int declaringType) {
+        Set<Integer> subtypes = findAssignableTypes(dex, declaringType);
+
+        Set<Integer> methods = new HashSet<Integer>();
+        int methodIndex = 0;
+        for (MethodId method : dex.methodIds()) {
+            if (method.getNameIndex() == memberNameIndex
+                    && subtypes.contains((int) method.getDeclaringClassIndex())) {
+                methods.add(methodIndex);
+            }
+            methodIndex++;
+        }
+        return methods;
+    }
+
+    /**
+     * Returns the set of types that can be assigned to {@code typeIndex}.
+     */
+    private Set<Integer> findAssignableTypes(DexBuffer dex, int typeIndex) {
+        Set<Integer> assignableTypes = new HashSet<Integer>();
+        assignableTypes.add(typeIndex);
+
+        for (ClassDef classDef : dex.classDefs()) {
+            if (assignableTypes.contains(classDef.getSupertypeIndex())) {
+                assignableTypes.add(classDef.getTypeIndex());
+                continue;
+            }
+
+            for (int implemented : classDef.getInterfaces()) {
+                if (assignableTypes.contains(implemented)) {
+                    assignableTypes.add(classDef.getTypeIndex());
+                    break;
+                }
+            }
+        }
+
+        return assignableTypes;
+    }
+}
diff --git a/dx/src/com/android/dx/command/findusages/Main.java b/dx/src/com/android/dx/command/findusages/Main.java
new file mode 100644
index 0000000..4b70daf
--- /dev/null
+++ b/dx/src/com/android/dx/command/findusages/Main.java
@@ -0,0 +1,34 @@
+/*
+ * 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.dx.command.findusages;
+
+import com.android.dx.io.DexBuffer;
+import java.io.File;
+import java.io.IOException;
+
+public final class Main {
+    public static void main(String[] args) throws IOException {
+        String dexFile = args[0];
+        String declaredBy = args[1];
+        String memberName = args[2];
+
+        DexBuffer dex = new DexBuffer();
+        dex.loadFrom(new File(dexFile));
+
+        new FindUsages(dex, declaredBy, memberName, System.out).findUsages();
+    }
+}
diff --git a/dx/src/com/android/dx/io/ClassData.java b/dx/src/com/android/dx/io/ClassData.java
new file mode 100644
index 0000000..5da7ddd
--- /dev/null
+++ b/dx/src/com/android/dx/io/ClassData.java
@@ -0,0 +1,104 @@
+/*
+ * 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.dx.io;
+
+public final class ClassData {
+    private final Field[] staticFields;
+    private final Field[] instanceFields;
+    private final Method[] directMethods;
+    private final Method[] virtualMethods;
+
+    public ClassData(Field[] staticFields, Field[] instanceFields,
+            Method[] directMethods, Method[] virtualMethods) {
+        this.staticFields = staticFields;
+        this.instanceFields = instanceFields;
+        this.directMethods = directMethods;
+        this.virtualMethods = virtualMethods;
+    }
+
+    public Field[] getStaticFields() {
+        return staticFields;
+    }
+
+    public Field[] getInstanceFields() {
+        return instanceFields;
+    }
+
+    public Method[] getDirectMethods() {
+        return directMethods;
+    }
+
+    public Method[] getVirtualMethods() {
+        return virtualMethods;
+    }
+
+    public Field[] allFields() {
+        Field[] result = new Field[staticFields.length + instanceFields.length];
+        System.arraycopy(staticFields, 0, result, 0, staticFields.length);
+        System.arraycopy(instanceFields, 0, result, staticFields.length, instanceFields.length);
+        return result;
+    }
+
+    public Method[] allMethods() {
+        Method[] result = new Method[directMethods.length + virtualMethods.length];
+        System.arraycopy(directMethods, 0, result, 0, directMethods.length);
+        System.arraycopy(virtualMethods, 0, result, directMethods.length, virtualMethods.length);
+        return result;
+    }
+
+    public static class Field {
+        private final int fieldIndex;
+        private final int accessFlags;
+
+        public Field(int fieldIndex, int accessFlags) {
+            this.fieldIndex = fieldIndex;
+            this.accessFlags = accessFlags;
+        }
+
+        public int getFieldIndex() {
+            return fieldIndex;
+        }
+
+        public int getAccessFlags() {
+            return accessFlags;
+        }
+    }
+
+    public static class Method {
+        private final int methodIndex;
+        private final int accessFlags;
+        private final int codeOffset;
+
+        public Method(int methodIndex, int accessFlags, int codeOffset) {
+            this.methodIndex = methodIndex;
+            this.accessFlags = accessFlags;
+            this.codeOffset = codeOffset;
+        }
+
+        public int getMethodIndex() {
+            return methodIndex;
+        }
+
+        public int getAccessFlags() {
+            return accessFlags;
+        }
+
+        public int getCodeOffset() {
+            return codeOffset;
+        }
+    }
+}
diff --git a/dx/src/com/android/dx/io/ClassDef.java b/dx/src/com/android/dx/io/ClassDef.java
index 6837a07..a29ea01 100644
--- a/dx/src/com/android/dx/io/ClassDef.java
+++ b/dx/src/com/android/dx/io/ClassDef.java
@@ -95,10 +95,9 @@
         }
 
         StringBuilder result = new StringBuilder();
-        DexBuffer.Section in = buffer.open(0);
-        result.append(in.readTypeName(typeIndex));
+        result.append(buffer.typeNames().get(typeIndex));
         if (supertypeIndex != NO_INDEX) {
-            result.append(" extends ").append(in.readTypeName(supertypeIndex));
+            result.append(" extends ").append(buffer.typeNames().get(supertypeIndex));
         }
         return result.toString();
     }
diff --git a/dx/src/com/android/dx/io/Code.java b/dx/src/com/android/dx/io/Code.java
new file mode 100644
index 0000000..81073f3
--- /dev/null
+++ b/dx/src/com/android/dx/io/Code.java
@@ -0,0 +1,114 @@
+/*
+ * 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.dx.io;
+
+public final class Code {
+    private final short registersSize;
+    private final short insSize;
+    private final short outsSize;
+    private final int debugInfoOffset;
+    private final short[] instructions;
+    private final Try[] tries;
+    private final CatchHandler[] catchHandlers;
+
+    public Code(short registersSize, short insSize, short outsSize, int debugInfoOffset,
+            short[] instructions, Try[] tries, CatchHandler[] catchHandlers) {
+        this.registersSize = registersSize;
+        this.insSize = insSize;
+        this.outsSize = outsSize;
+        this.debugInfoOffset = debugInfoOffset;
+        this.instructions = instructions;
+        this.tries = tries;
+        this.catchHandlers = catchHandlers;
+    }
+
+    public short getRegistersSize() {
+        return registersSize;
+    }
+
+    public short getInsSize() {
+        return insSize;
+    }
+
+    public short getOutsSize() {
+        return outsSize;
+    }
+
+    public int getDebugInfoOffset() {
+        return debugInfoOffset;
+    }
+
+    public short[] getInstructions() {
+        return instructions;
+    }
+
+    public Try[] getTries() {
+        return tries;
+    }
+
+    public CatchHandler[] getCatchHandlers() {
+        return catchHandlers;
+    }
+
+    public static class Try {
+        final int startAddress;
+        final short instructionCount;
+        final short handlerOffset;
+
+        Try(int startAddress, short instructionCount, short handlerOffset) {
+            this.startAddress = startAddress;
+            this.instructionCount = instructionCount;
+            this.handlerOffset = handlerOffset;
+        }
+
+        public int getStartAddress() {
+            return startAddress;
+        }
+
+        public short getInstructionCount() {
+            return instructionCount;
+        }
+
+        public short getHandlerOffset() {
+            return handlerOffset;
+        }
+    }
+
+    public static class CatchHandler {
+        final int[] typeIndexes;
+        final int[] addresses;
+        final int catchAllAddress;
+
+        public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress) {
+            this.typeIndexes = typeIndexes;
+            this.addresses = addresses;
+            this.catchAllAddress = catchAllAddress;
+        }
+
+        public int[] getTypeIndexes() {
+            return typeIndexes;
+        }
+
+        public int[] getAddresses() {
+            return addresses;
+        }
+
+        public int getCatchAllAddress() {
+            return catchAllAddress;
+        }
+    }
+}
diff --git a/dx/src/com/android/dx/io/CodeReader.java b/dx/src/com/android/dx/io/CodeReader.java
new file mode 100644
index 0000000..7ed4a59
--- /dev/null
+++ b/dx/src/com/android/dx/io/CodeReader.java
@@ -0,0 +1,480 @@
+/*
+ * 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.dx.io;
+
+import com.android.dx.dex.DexException;
+import java.util.BitSet;
+
+/**
+ * Walks through a block of code and calls visitor call backs.
+ */
+public final class CodeReader {
+
+    private final Instruction[] instructions = new Instruction[] {
+            // 0x00...0x0f
+            new Instruction(1, "nop"),
+            new Instruction(1, "move vA, vB"),
+            new Instruction(2, "move/from vAA, vBBBB"),
+            new Instruction(3, "move/16 vAAAA, vBBBB"),
+            new Instruction(1, "move-wide, vA, vB"),
+            new Instruction(2, "move-wide/from16 vAA, vBBBB"),
+            new Instruction(3, "move-wide/from16 vAAAA, vBBBB"),
+            new Instruction(1, "move-object vA, vB"),
+            new Instruction(2, "move-object/from16 vAA, vBBBB"),
+            new Instruction(3, "move-object/16 vAAAA, vBBBB"),
+            new Instruction(1, "move-result vAA"),
+            new Instruction(1, "move-result-wide vAA"),
+            new Instruction(1, "move-result-object vAA"),
+            new Instruction(1, "move-exception vAA"),
+            new Instruction(1, "return void"),
+            new Instruction(1, "return vAA"),
+
+            // 0x10...0x1f
+            new Instruction(1, "return-wide vAA"),
+            new Instruction(1, "return-object vAA"),
+            new Instruction(1, "const/4 vA, #+B"),
+            new Instruction(2, "const/16 vAA, #+BBBB"),
+            new Instruction(3, "const vAA, #+BBBBBBBB"),
+            new Instruction(2, "const/high16 vAA, #+BBBB0000"),
+            new Instruction(2, "const-wide/16 vAA, #+BBBB"),
+            new Instruction(3, "const-wide/32 vAA, #+BBBBBBBB"),
+            new Instruction(5, "const-wide vAA, #+BBBBBBBBBBBBBBBB"),
+            new Instruction(2, "const-wide/high16 vAA, #+BBBB000000000000"),
+            new Instruction(2, "const-string vAA, string@BBBB"),
+            new Instruction(3, "const-string/jumbo vAA, string@BBBBBBBB"),
+            new Instruction(2, "const-class vAA, type@BBBB"),
+            new Instruction(1, "monitor-enter vAA"),
+            new Instruction(1, "monitor-exit vAA"),
+            new Instruction(2, "check-cast vAA type@BBBB"),
+
+            // 0x20...0x2f
+            new Instruction(2, "instance-of vA, vB, type@CCCC"),
+            new Instruction(1, "array-length vA, vB"),
+            new Instruction(2, "new-instance vAA, type@BBBB"),
+            new Instruction(2, "new-array vA, vB, type@CCCC"),
+            new Instruction(3, "filled-new-array {vD, vE, vF, vG, vA}, type@CCCC"),
+            new Instruction(3, "filled-new-array/range {vCCCC..vNNNN}, type@BBBB"),
+            new FillArrayInstruction(3, "fill-array-data vAA, +BBBBBBBB"),
+            new Instruction(1, "throw vAA"),
+            new Instruction(1, "goto +AA"),
+            new Instruction(2, "goto/16 +AAAA"),
+            new Instruction(3, "goto/32 +AAAAAAAA"),
+            new PackedSwitchInstruction(3, "packed-switch vAA, +BBBBBBBB"),
+            new SparseSwitchInstruction(3, "sparse-switch vAA, +BBBBBBBB"),
+            new Instruction(2, "cmpl-float vAA, vBB, vCC"),
+            new Instruction(2, "cmpg-float vAA, vBB, vCC"),
+            new Instruction(2, "cmpl-double vAA, vBB, vCC"),
+
+            // 0x30...0x3f
+            new Instruction(2, "cmpg-double vAA, vBB, vCC"),
+            new Instruction(2, "cmp-long vAA, vBB, vCC"),
+            new Instruction(2, "if-eq vA, vB, +CCCC"),
+            new Instruction(2, "if-ne vA, vB, +CCCC"),
+            new Instruction(2, "if-lt vA, vB, +CCCC"),
+            new Instruction(2, "if-ge vA, vB, +CCCC"),
+            new Instruction(2, "if-gt vA, vB, +CCCC"),
+            new Instruction(2, "if-le vA, vB, +CCCC"),
+            new Instruction(2, "if-eqz vAA, +BBBB"),
+            new Instruction(2, "if-nez vAA, +BBBB"),
+            new Instruction(2, "if-ltz vAA, +BBBB"),
+            new Instruction(2, "if-gez vAA, +BBBB"),
+            new Instruction(2, "if-gtz vAA, +BBBB"),
+            new Instruction(2, "if-lez vAA, +BBBB"),
+            new UnusedInstruction(),
+            new UnusedInstruction(),
+
+            // 0x40...0x4f
+            new UnusedInstruction(),
+            new UnusedInstruction(),
+            new UnusedInstruction(),
+            new UnusedInstruction(),
+            new Instruction(2, "aget vAA, vBB, vCC"),
+            new Instruction(2, "aget-wide vAA, vBB, vCC"),
+            new Instruction(2, "aget-object vAA, vBB, vCC"),
+            new Instruction(2, "aget-boolean vAA, vBB, vCC"),
+            new Instruction(2, "aget-byte vAA, vBB, vCC"),
+            new Instruction(2, "aget-char vAA, vBB, vCC"),
+            new Instruction(2, "aget-short vAA, vBB, vCC"),
+            new Instruction(2, "aput vAA, vBB, vCC"),
+            new Instruction(2, "aput-wide vAA, vBB, vCC"),
+            new Instruction(2, "aput-object vAA, vBB, vCC"),
+            new Instruction(2, "aput-boolean vAA, vBB, vCC"),
+            new Instruction(2, "aput-byte vAA, vBB, vCC"),
+
+            // 0x50...0x5f
+            new Instruction(2, "aput-char vAA, vBB, vCC"),
+            new Instruction(2, "aput-short vAA, vBB, vCC"),
+            new Instruction(2, "iget vA, vB, field@CCCC"),
+            new Instruction(2, "iget-wide vA, vB, field@CCCC"),
+            new Instruction(2, "iget-object vA, vB, field@CCCC"),
+            new Instruction(2, "iget-boolean vA, vB, field@CCCC"),
+            new Instruction(2, "iget-byte vA, vB, field@CCCC"),
+            new Instruction(2, "iget-char vA, vB, field@CCCC"),
+            new Instruction(2, "iget-short vA, vB, field@CCCC"),
+            new Instruction(2, "iput vA, vB, field@CCCC"),
+            new Instruction(2, "iput-wide vA, vB, field@CCCC"),
+            new Instruction(2, "iput-object vA, vB, field@CCCC"),
+            new Instruction(2, "iput-boolean vA, vB, field@CCCC"),
+            new Instruction(2, "iput-byte vA, vB, field@CCCC"),
+            new Instruction(2, "iput-char vA, vB, field@CCCC"),
+            new Instruction(2, "iput-short vA, vB, field@CCCC"),
+
+            // 0x60...0x6f
+            new Instruction(2, "sget vAA, field@BBBB"),
+            new Instruction(2, "sget-wide vAA, field@BBBB"),
+            new Instruction(2, "sget-object vAA, field@BBBB"),
+            new Instruction(2, "sget-boolean vAA, field@BBBB"),
+            new Instruction(2, "sget-byte vAA, field@BBBB"),
+            new Instruction(2, "sget-char vAA, field@BBBB"),
+            new Instruction(2, "sget-short vAA, field@BBBB"),
+            new Instruction(2, "sput vAA, field@BBBB"),
+            new Instruction(2, "sput-wide vAA, field@BBBB"),
+            new Instruction(2, "sput-object vAA, field@BBBB"),
+            new Instruction(2, "sput-boolean vAA, field@BBBB"),
+            new Instruction(2, "sput-byte vAA, field@BBBB"),
+            new Instruction(2, "sput-char vAA, field@BBBB"),
+            new Instruction(2, "sput-short vAA, field@BBBB"),
+            new Instruction(3, "invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC"),
+            new Instruction(3, "invoke-super {vD, vE, vF, vG, vA}, meth@CCCC"),
+
+            // 0x70...0x7f
+            new Instruction(3, "invoke-direct {vD, vE, vF, vG, vA}, meth@CCCC"),
+            new Instruction(3, "invoke-static {vD, vE, vF, vG, vA}, meth@CCCC"),
+            new Instruction(3, "invoke-interface {vD, vE, vF, vG, vA}, meth@CCCC"),
+            new UnusedInstruction(),
+            new Instruction(3, "invoke-virtual/range {vCCCC..vNNNN}, meth@BBBB"),
+            new Instruction(3, "invoke-super/range {vCCCC..vNNNN}, meth@BBBB"),
+            new Instruction(3, "invoke-direct/range {vCCCC..vNNNN}, meth@BBBB"),
+            new Instruction(3, "invoke-static/range {vCCCC..vNNNN}, meth@BBBB"),
+            new Instruction(3, "invoke-interface/range {vCCCC..vNNNN}, meth@BBBB"),
+            new UnusedInstruction(),
+            new UnusedInstruction(),
+            new Instruction(1, "neg-int vA, vB"),
+            new Instruction(1, "not-int vA, vB"),
+            new Instruction(1, "neg-long vA, vB"),
+            new Instruction(1, "not-long vA, vB"),
+            new Instruction(1, "neg-float vA, vB"),
+
+            // 0x80...0x8f
+            new Instruction(1, "neg-double vA, vB"),
+            new Instruction(1, "int-to-long vA, vB"),
+            new Instruction(1, "int-to-float vA, vB"),
+            new Instruction(1, "int-to-double vA, vB"),
+            new Instruction(1, "long-to-int vA, vB"),
+            new Instruction(1, "long-to-float vA, vB"),
+            new Instruction(1, "long-to-double vA, vB"),
+            new Instruction(1, "float-to-int vA, vB"),
+            new Instruction(1, "float-to-long vA, vB"),
+            new Instruction(1, "float-to-double vA, vB"),
+            new Instruction(1, "double-to-int vA, vB"),
+            new Instruction(1, "double-to-long vA, vB"),
+            new Instruction(1, "double-to-float vA, vB"),
+            new Instruction(1, "int-to-byte vA, vB"),
+            new Instruction(1, "int-to-char vA, vB"),
+            new Instruction(1, "int-to-short vA, vB"),
+
+            // 0x90...0x9f
+            new Instruction(2, "add-int vAA, vBB, vCC"),
+            new Instruction(2, "sub-int vAA, vBB, vCC"),
+            new Instruction(2, "mul-int vAA, vBB, vCC"),
+            new Instruction(2, "div-int vAA, vBB, vCC"),
+            new Instruction(2, "rem-int vAA, vBB, vCC"),
+            new Instruction(2, "and-int vAA, vBB, vCC"),
+            new Instruction(2, "or-int vAA, vBB, vCC"),
+            new Instruction(2, "xor-int vAA, vBB, vCC"),
+            new Instruction(2, "shl-int vAA, vBB, vCC"),
+            new Instruction(2, "shr-int vAA, vBB, vCC"),
+            new Instruction(2, "ushr-int vAA, vBB, vCC"),
+            new Instruction(2, "add-long vAA, vBB, vCC"),
+            new Instruction(2, "sub-long vAA, vBB, vCC"),
+            new Instruction(2, "mul-long vAA, vBB, vCC"),
+            new Instruction(2, "div-long vAA, vBB, vCC"),
+            new Instruction(2, "rem-long vAA, vBB, vCC"),
+
+            // 0xa0...0xaf
+            new Instruction(2, "and-long vAA, vBB, vCC"),
+            new Instruction(2, "or-long vAA, vBB, vCC"),
+            new Instruction(2, "xor-long vAA, vBB, vCC"),
+            new Instruction(2, "shl-long vAA, vBB, vCC"),
+            new Instruction(2, "shr-long vAA, vBB, vCC"),
+            new Instruction(2, "ushr-long vAA, vBB, vCC"),
+            new Instruction(2, "add-float vAA, vBB, vCC"),
+            new Instruction(2, "sub-float vAA, vBB, vCC"),
+            new Instruction(2, "mul-float vAA, vBB, vCC"),
+            new Instruction(2, "div-float vAA, vBB, vCC"),
+            new Instruction(2, "rem-float vAA, vBB, vCC"),
+            new Instruction(2, "add-double vAA, vBB, vCC"),
+            new Instruction(2, "sub-double vAA, vBB, vCC"),
+            new Instruction(2, "mul-double vAA, vBB, vCC"),
+            new Instruction(2, "div-double vAA, vBB, vCC"),
+            new Instruction(2, "rem-double vAA, vBB, vCC"),
+
+            // 0xb0..0xbf
+            new Instruction(1, "add-int/2addr vA, vB"),
+            new Instruction(1, "sub-int/2addr vA, vB"),
+            new Instruction(1, "mul-int/2addr vA, vB"),
+            new Instruction(1, "div-int/2addr vA, vB"),
+            new Instruction(1, "rem-int/2addr vA, vB"),
+            new Instruction(1, "and-int/2addr vA, vB"),
+            new Instruction(1, "or-int/2addr vA, vB"),
+            new Instruction(1, "xor-int/2addr vA, vB"),
+            new Instruction(1, "shl-int/2addr vA, vB"),
+            new Instruction(1, "shr-int/2addr vA, vB"),
+            new Instruction(1, "ushr-int/2addr vA, vB"),
+            new Instruction(1, "add-long/2addr vA, vB"),
+            new Instruction(1, "sub-long/2addr vA, vB"),
+            new Instruction(1, "mul-long/2addr vA, vB"),
+            new Instruction(1, "div-long/2addr vA, vB"),
+            new Instruction(1, "rem-long/2addr vA, vB"),
+
+            // 0xc0...0xcf
+            new Instruction(1, "and-long/2addr vA, vB"),
+            new Instruction(1, "or-long/2addr vA, vB"),
+            new Instruction(1, "xor-long/2addr vA, vB"),
+            new Instruction(1, "shl-long/2addr vA, vB"),
+            new Instruction(1, "shr-long/2addr vA, vB"),
+            new Instruction(1, "ushr-long/2addr vA, vB"),
+            new Instruction(1, "add-float/2addr vA, vB"),
+            new Instruction(1, "sub-float/2addr vA, vB"),
+            new Instruction(1, "mul-float/2addr vA, vB"),
+            new Instruction(1, "div-float/2addr vA, vB"),
+            new Instruction(1, "rem-float/2addr vA, vB"),
+            new Instruction(1, "add-double/2addr vA, vB"),
+            new Instruction(1, "sub-double/2addr vA, vB"),
+            new Instruction(1, "mul-double/2addr vA, vB"),
+            new Instruction(1, "div-double/2addr vA, vB"),
+            new Instruction(1, "rem-double/2addr vA, vB"),
+
+            // 0xd0...0xdf
+            new Instruction(2, "add-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "rsub-int (reverse subtract) vA, vB, #+CCCC"),
+            new Instruction(2, "mul-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "div-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "rem-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "and-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "or-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "xor-int/lit16 vA, vB, #+CCCC"),
+            new Instruction(2, "add-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "rsub-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "mul-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "div-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "rem-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "and-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "or-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "xor-int/lit8 vAA, vBB, #+CC"),
+
+            // 0xe0...0xef
+            new Instruction(2, "shl-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "shr-int/lit8 vAA, vBB, #+CC"),
+            new Instruction(2, "ushr-int/lit8 vAA, vBB, #+CC"),
+    };
+
+    /**
+     * Sets {@code visitor} as the visitor for all string instructions.
+     */
+    public void setStringVisitor(Visitor visitor) {
+        instructions[0x1a].setVisitor("const-string vAA, string@BBBB", visitor);
+    }
+
+    /**
+     * Sets {@code visitor} as the visitor for all jumbo string instructions.
+     */
+    public void setJumboStringVisitor(Visitor visitor) {
+        instructions[0x1b].setVisitor("const-string/jumbo vAA, string@BBBBBBBB", visitor);
+    }
+
+    /**
+     * Sets {@code visitor} as the visitor for all type instructions.
+     */
+    public void setTypeVisitor(Visitor visitor) {
+        instructions[0x1c].setVisitor("const-class vAA, type@BBBB", visitor);
+        instructions[0x1f].setVisitor("check-cast vAA type@BBBB", visitor);
+        instructions[0x20].setVisitor("instance-of vA, vB, type@CCCC", visitor);
+        instructions[0x22].setVisitor("new-instance vAA, type@BBBB", visitor);
+        instructions[0x23].setVisitor("new-array vA, vB, type@CCCC", visitor);
+        instructions[0x24].setVisitor("filled-new-array {vD, vE, vF, vG, vA}, type@CCCC", visitor);
+        instructions[0x25].setVisitor("filled-new-array/range {vCCCC..vNNNN}, type@BBBB", visitor);
+    }
+
+    /**
+     * Sets {@code visitor} as the visitor for all field instructions.
+     */
+    public void setFieldVisitor(Visitor visitor) {
+        instructions[0x52].setVisitor("iget vA, vB, field@CCCC", visitor);
+        instructions[0x53].setVisitor("iget-wide vA, vB, field@CCCC", visitor);
+        instructions[0x54].setVisitor("iget-object vA, vB, field@CCCC", visitor);
+        instructions[0x55].setVisitor("iget-boolean vA, vB, field@CCCC", visitor);
+        instructions[0x56].setVisitor("iget-byte vA, vB, field@CCCC", visitor);
+        instructions[0x57].setVisitor("iget-char vA, vB, field@CCCC", visitor);
+        instructions[0x58].setVisitor("iget-short vA, vB, field@CCCC", visitor);
+        instructions[0x59].setVisitor("iput vA, vB, field@CCCC", visitor);
+        instructions[0x5a].setVisitor("iput-wide vA, vB, field@CCCC", visitor);
+        instructions[0x5b].setVisitor("iput-object vA, vB, field@CCCC", visitor);
+        instructions[0x5c].setVisitor("iput-boolean vA, vB, field@CCCC", visitor);
+        instructions[0x5d].setVisitor("iput-byte vA, vB, field@CCCC", visitor);
+        instructions[0x5e].setVisitor("iput-char vA, vB, field@CCCC", visitor);
+        instructions[0x5f].setVisitor("iput-short vA, vB, field@CCCC", visitor);
+        instructions[0x60].setVisitor("sget vAA, field@BBBB", visitor);
+        instructions[0x61].setVisitor("sget-wide vAA, field@BBBB", visitor);
+        instructions[0x62].setVisitor("sget-object vAA, field@BBBB", visitor);
+        instructions[0x63].setVisitor("sget-boolean vAA, field@BBBB", visitor);
+        instructions[0x64].setVisitor("sget-byte vAA, field@BBBB", visitor);
+        instructions[0x65].setVisitor("sget-char vAA, field@BBBB", visitor);
+        instructions[0x66].setVisitor("sget-short vAA, field@BBBB", visitor);
+        instructions[0x67].setVisitor("sput vAA, field@BBBB", visitor);
+        instructions[0x68].setVisitor("sput-wide vAA, field@BBBB", visitor);
+        instructions[0x69].setVisitor("sput-object vAA, field@BBBB", visitor);
+        instructions[0x6a].setVisitor("sput-boolean vAA, field@BBBB", visitor);
+        instructions[0x6b].setVisitor("sput-byte vAA, field@BBBB", visitor);
+        instructions[0x6c].setVisitor("sput-char vAA, field@BBBB", visitor);
+        instructions[0x6d].setVisitor("sput-short vAA, field@BBBB", visitor);
+    }
+
+    /**
+     * Sets {@code visitor} as the visitor for all method instructions.
+     */
+    public void setMethodVisitor(Visitor visitor) {
+        instructions[0x6e].setVisitor("invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC", visitor);
+        instructions[0x6f].setVisitor("invoke-super {vD, vE, vF, vG, vA}, meth@CCCC", visitor);
+        instructions[0x70].setVisitor("invoke-direct {vD, vE, vF, vG, vA}, meth@CCCC", visitor);
+        instructions[0x71].setVisitor("invoke-static {vD, vE, vF, vG, vA}, meth@CCCC", visitor);
+        instructions[0x72].setVisitor("invoke-interface {vD, vE, vF, vG, vA}, meth@CCCC", visitor);
+        instructions[0x74].setVisitor("invoke-virtual/range {vCCCC..vNNNN}, meth@BBBB", visitor);
+        instructions[0x75].setVisitor("invoke-super/range {vCCCC..vNNNN}, meth@BBBB", visitor);
+        instructions[0x76].setVisitor("invoke-direct/range {vCCCC..vNNNN}, meth@BBBB", visitor);
+        instructions[0x77].setVisitor("invoke-static/range {vCCCC..vNNNN}, meth@BBBB", visitor);
+        instructions[0x78].setVisitor("invoke-interface/range {vCCCC..vNNNN}, meth@BBBB", visitor);
+    }
+
+    public void visitAll(short[] instructions) throws DexException {
+        BitSet skippedInstructions = new BitSet();
+
+        for (int i = 0; i < instructions.length; ) {
+            if (skippedInstructions.get(i)) {
+                i++;
+                continue;
+            }
+
+            int index = instructions[i] & 0xFF;
+            if (index < 0 || index >= this.instructions.length) {
+                throw new DexException("Unhandled instruction at " + i
+                        + ": " + Integer.toHexString(index));
+            }
+
+            Instruction instruction = this.instructions[index];
+            instruction.mask(instructions, i, skippedInstructions);
+            if (instruction.visitor != null) {
+                instruction.visitor.visit(instruction, instructions, i);
+            }
+            i += instruction.codeUnits;
+        }
+    }
+
+    public static class Instruction {
+        private final String name;
+        private final int codeUnits;
+        private Visitor visitor;
+
+        private Instruction(int codeUnits, String name) {
+            this.name = name;
+            this.codeUnits = codeUnits;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the visitor to be notified when this instruction is encountered,
+         * or null if this instruction has no visitor.
+         */
+        public void setVisitor(String name, Visitor visitor) {
+            if (!this.name.equals(name)) {
+                throw new IllegalArgumentException("Expected " + this.name + " but was " + name);
+            }
+            this.visitor = visitor;
+        }
+
+        protected void mask(short[] instructions, int offset, BitSet skippedInstructions) {}
+
+        @Override public String toString() {
+            return name;
+        }
+    }
+
+    public interface Visitor {
+        void visit(Instruction instruction, short[] instructions, int offset);
+    }
+
+    private static class UnusedInstruction extends Instruction {
+        UnusedInstruction() {
+            super(1, "unused");
+        }
+    }
+
+    private static class PackedSwitchInstruction extends Instruction {
+        public PackedSwitchInstruction(int codeUnits, String name) {
+            super(codeUnits, name);
+        }
+        @Override protected void mask(short[] instructions, int i, BitSet skippedInstructions) {
+            int offset = (instructions[i + 1] & 0xFFFF)
+                    + ((instructions[i + 2] & 0xFFFF) << 16);
+            if (instructions[i + offset] != 0x100) {
+                throw new DexException("Expected packed-switch pseudo-opcode but was 0x"
+                        + Integer.toHexString(instructions[i + offset]));
+            }
+            short size = instructions[i + offset + 1];
+            skippedInstructions.set(i + offset, i + offset + 4 + (size * 2));
+        }
+    }
+
+    private static class SparseSwitchInstruction extends Instruction {
+        public SparseSwitchInstruction(int codeUnits, String name) {
+            super(codeUnits, name);
+        }
+        @Override protected void mask(short[] instructions, int i, BitSet skippedInstructions) {
+            int offset = (instructions[i + 1] & 0xFFFF)
+                    + ((instructions[i + 2] & 0xFFFF) << 16);
+            if (instructions[i + offset] != 0x200) {
+                throw new DexException("Expected sparse-switch pseudo-opcode but was 0x"
+                        + Integer.toHexString(instructions[i + offset]));
+            }
+            short size = instructions[i + offset + 1];
+            skippedInstructions.set(i + offset, i + offset + 2 + (size * 4));
+        }
+    }
+
+    private static class FillArrayInstruction extends Instruction {
+        public FillArrayInstruction(int codeUnits, String name) {
+            super(codeUnits, name);
+        }
+        @Override protected void mask(short[] instructions, int i, BitSet skippedInstructions) {
+            int offset = (instructions[i + 1] & 0xFFFF)
+                    + ((instructions[i + 2] & 0xFFFF) << 16);
+            if (instructions[i + offset] != 0x300) {
+                throw new DexException("Expected fill-array-data pseudo-opcode but was 0x"
+                        + Integer.toHexString(instructions[i + offset]));
+            }
+            int bytesPerElement = instructions[i + offset + 1];
+            int size = (instructions[i + offset + 2] & 0xFFFF)
+                    + ((instructions[i + offset + 3] & 0xFFFF) << 4);
+            int totalBytes = size * bytesPerElement;
+            int totalShorts = (totalBytes + 1) / 2; // round up!
+            skippedInstructions.set(i + offset, i + offset + 4 + totalShorts);
+        }
+    }
+}
diff --git a/dx/src/com/android/dx/io/DexBuffer.java b/dx/src/com/android/dx/io/DexBuffer.java
index e3309cb..7f21702 100644
--- a/dx/src/com/android/dx/io/DexBuffer.java
+++ b/dx/src/com/android/dx/io/DexBuffer.java
@@ -29,7 +29,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.AbstractList;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
 
 /**
  * The bytes of a dex file in memory for reading and writing. All int offsets
@@ -40,6 +44,77 @@
     private final TableOfContents tableOfContents = new TableOfContents();
     private int length;
 
+    private final List<String> strings = new AbstractList<String>() {
+        @Override public String get(int index) {
+            checkBounds(index, tableOfContents.stringIds.size);
+            int offset = open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM))
+                    .readInt();
+            return open(offset).readStringDataItem();
+        }
+        @Override public int size() {
+            return tableOfContents.stringIds.size;
+        }
+    };
+
+    private final List<Integer> typeIds = new AbstractList<Integer>() {
+        @Override public Integer get(int index) {
+            checkBounds(index, tableOfContents.typeIds.size);
+            return open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();
+        }
+        @Override public int size() {
+            return tableOfContents.typeIds.size;
+        }
+    };
+
+    private final List<String> typeNames = new AbstractList<String>() {
+        @Override public String get(int index) {
+            checkBounds(index, tableOfContents.typeIds.size);
+            return strings.get(typeIds.get(index));
+        }
+        @Override public int size() {
+            return tableOfContents.typeIds.size;
+        }
+    };
+
+    private final List<ProtoId> protoIds = new AbstractList<ProtoId>() {
+        @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 List<FieldId> fieldIds = new AbstractList<FieldId>() {
+        @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 List<MethodId> methodIds = new AbstractList<MethodId>() {
+        @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 static void checkBounds(int index, int length) {
+        if (index < 0 || index >= length) {
+            throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
+        }
+    }
+
     public void loadFrom(InputStream in) throws IOException {
         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
         byte[] buffer = new byte[8192];
@@ -77,7 +152,7 @@
         return new Section(position);
     }
 
-    public Section appendSection(int maxByteCount, String name) throws IOException {
+    public Section appendSection(int maxByteCount, String name) {
         Section result = new Section(name, length, length + maxByteCount);
         length = fourByteAlign(length + maxByteCount);
         return result;
@@ -99,11 +174,82 @@
         return data;
     }
 
+    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 Iterable<ClassDef>() {
+            public Iterator<ClassDef> iterator() {
+                return new Iterator<ClassDef>() {
+                    private DexBuffer.Section in = open(tableOfContents.classDefs.off);
+                    private int count = 0;
+
+                    public boolean hasNext() {
+                        return count < tableOfContents.classDefs.size;
+                    }
+                    public ClassDef next() {
+                        if (!hasNext()) {
+                            throw new NoSuchElementException();
+                        }
+                        count++;
+                        return in.readClassDef();
+                    }
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
+    }
+
+    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();
+    }
+
     public final class Section {
         private final String name;
         private int position;
         private final int limit;
 
+        private final DataInput asDataInput = new DataInputStub() {
+            public byte readByte() {
+                return Section.this.readByte();
+            }
+        };
+
         private Section(String name, int position, int limit) {
             this.name = name;
             this.position = position;
@@ -114,12 +260,6 @@
             this("section", position, data.length);
         }
 
-        private final DataInput asDataInput = new DataInputStub() {
-            public byte readByte() {
-                return Section.this.readByte();
-            }
-        };
-
         public int getPosition() {
             return position;
         }
@@ -203,34 +343,6 @@
             }
         }
 
-        /**
-         * Reads a string at the given index. This method does not disturb the position.
-         */
-        public String readString(int index) {
-            int savedPosition = position;
-            position = tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM);
-            position = readInt();
-            String result = readStringDataItem();
-            position = savedPosition;
-            return result;
-        }
-
-        public int readType(int index) {
-            if (index < 0 || index >= tableOfContents.typeIds.size) {
-                throw new IllegalArgumentException("type index out of range: "
-                        + index + " " + tableOfContents.typeIds.size);
-            }
-            int savedPosition = position;
-            position = tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM);
-            int result = readInt();
-            position = savedPosition;
-            return result;
-        }
-
-        public String readTypeName(int index) {
-            return readString(readType(index));
-        }
-
         public FieldId readFieldId() {
             short declaringClassIndex = readShort();
             short typeIndex = readShort();
@@ -245,14 +357,6 @@
             return new MethodId(DexBuffer.this, declaringClassIndex, protoIndex, nameIndex);
         }
 
-        public ProtoId readProtoId(int index) {
-            int savedPosition = position;
-            position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index);
-            ProtoId result = readProtoId();
-            position = savedPosition;
-            return result;
-        }
-
         public ProtoId readProtoId() {
             int shortyIndex = readInt();
             int returnTypeIndex = readInt();
@@ -277,6 +381,86 @@
                     classDataOffset, staticValuesOffset);
         }
 
+        private Code readCode() {
+            short registersSize = readShort();
+            short insSize = readShort();
+            short outsSize = readShort();
+            short triesSize = readShort();
+            int debugInfoOffset = readInt();
+            int instructionsSize = readInt();
+            short[] instructions = readShortArray(instructionsSize);
+            Code.Try[] tries = new Code.Try[triesSize];
+            Code.CatchHandler[] catchHandlers = new Code.CatchHandler[0];
+            if (triesSize > 0) {
+                if (instructions.length % 2 == 1) {
+                    readShort(); // padding
+                }
+
+                for (int i = 0; i < triesSize; i++) {
+                    int startAddress = readInt();
+                    short instructionCount = readShort();
+                    short handlerOffset = readShort();
+                    tries[i] = new Code.Try(startAddress, instructionCount, handlerOffset);
+                }
+
+                int catchHandlersSize = readUleb128();
+                catchHandlers = new Code.CatchHandler[catchHandlersSize];
+                for (int i = 0; i < catchHandlersSize; i++) {
+                    catchHandlers[i] = readCatchHandler();
+                }
+            }
+            return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
+                    tries, catchHandlers);
+        }
+
+        private Code.CatchHandler readCatchHandler() {
+            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 Code.CatchHandler(typeIndexes, addresses, catchAllAddress);
+        }
+
+        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;
+        }
+
         private void checkPosition() {
             if (position > limit) {
                 throw new DexException("Section limit " + limit + " exceeded by " + name);
@@ -286,7 +470,7 @@
         /**
          * Writes 0x00 until the position is aligned to a multiple of 4.
          */
-        public void alignToFourBytes() throws IOException {
+        public void alignToFourBytes() {
             int unalignedCount = position;
             position = DexBuffer.fourByteAlign(position);
             for (int i = unalignedCount; i < position; i++) {
@@ -294,37 +478,37 @@
             }
         }
 
-        public void assertFourByteAligned() throws IOException {
+        public void assertFourByteAligned() {
             if ((position & 3) != 0) {
                 throw new IllegalStateException("Not four byte aligned!");
             }
         }
 
-        public void write(byte[] bytes) throws IOException {
+        public void write(byte[] bytes) {
             System.arraycopy(bytes, 0, data, position, bytes.length);
             position += bytes.length;
             checkPosition();
         }
 
-        public void writeByte(int b) throws IOException {
+        public void writeByte(int b) {
             data[position++] = (byte) b;
             checkPosition();
         }
 
-        public void writeShort(short i) throws IOException {
+        public void writeShort(short i) {
             data[position    ] = (byte) i;
             data[position + 1] = (byte) (i >>> 8);
             position += 2;
             checkPosition();
         }
 
-        public void write(short[] shorts) throws IOException {
+        public void write(short[] shorts) {
             for (short s : shorts) {
                 writeShort(s);
             }
         }
 
-        public void writeInt(int i) throws IOException {
+        public void writeInt(int i) {
             data[position    ] = (byte) i;
             data[position + 1] = (byte) (i >>>  8);
             data[position + 2] = (byte) (i >>> 16);
@@ -333,25 +517,25 @@
             checkPosition();
         }
 
-        public void writeUleb128(int i) throws IOException {
+        public void writeUleb128(int i) {
             position += Leb128Utils.writeUnsignedLeb128(data, position, i);
             checkPosition();
         }
 
-        public void writeSleb128(int i) throws IOException {
+        public void writeSleb128(int i) {
             position += Leb128Utils.writeSignedLeb128(data, position, i);
             checkPosition();
         }
 
-        public void writeStringDataItem(String value) throws IOException {
-            int length = value.length();
-            writeUleb128(length);
-            write(Mutf8.encode(value));
-            writeByte(0);
-        }
-
-        public Section open(int position) {
-            return new Section(position);
+        public void writeStringData(String value) {
+            try {
+                int length = value.length();
+                writeUleb128(length);
+                write(Mutf8.encode(value));
+                writeByte(0);
+            } catch (IOException e) {
+                throw new AssertionError();
+            }
         }
     }
 
diff --git a/dx/src/com/android/dx/io/DexIndexPrinter.java b/dx/src/com/android/dx/io/DexIndexPrinter.java
index e4996bd..a6040f1 100644
--- a/dx/src/com/android/dx/io/DexIndexPrinter.java
+++ b/dx/src/com/android/dx/io/DexIndexPrinter.java
@@ -45,39 +45,42 @@
     }
 
     private void printStrings() throws IOException {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.stringIds.off);
-        for (int i = 0; i < tableOfContents.stringIds.size; i++) {
-            String s = in.readString(i);
-            System.out.println("string " + i + ": " + s);
+        int index = 0;
+        for (String string : dexBuffer.strings()) {
+            System.out.println("string " + index + ": " + string);
+            index++;
         }
     }
 
     private void printTypeIds() throws IOException {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.typeIds.off);
-        for (int i = 0; i < tableOfContents.typeIds.size; i++) {
-            int stringIndex = in.readInt();
-            System.out.println("type " + i + ": " + in.readString(stringIndex));
+        int index = 0;
+        for (Integer type : dexBuffer.typeIds()) {
+            System.out.println("type " + index + ": " + dexBuffer.strings().get(type));
+            index++;
         }
     }
 
     private void printProtoIds() throws IOException {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.protoIds.off);
-        for (int i = 0; i < tableOfContents.protoIds.size; i++) {
-            System.out.println("proto " + i + ": " + in.readProtoId());
+        int index = 0;
+        for (ProtoId protoId : dexBuffer.protoIds()) {
+            System.out.println("proto " + index + ": " + protoId);
+            index++;
         }
     }
 
     private void printFieldIds() throws IOException {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.fieldIds.off);
-        for (int i = 0; i < tableOfContents.fieldIds.size; i++) {
-            System.out.println("field " + i + ": " + in.readFieldId());
+        int index = 0;
+        for (FieldId fieldId : dexBuffer.fieldIds()) {
+            System.out.println("field " + index + ": " + fieldId);
+            index++;
         }
     }
 
     private void printMethodIds() throws IOException {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.methodIds.off);
-        for (int i = 0; i < tableOfContents.methodIds.size; i++) {
-            System.out.println("method " + i + ": " + in.readMethodId());
+        int index = 0;
+        for (MethodId methodId : dexBuffer.methodIds()) {
+            System.out.println("methodId " + index + ": " + methodId);
+            index++;
         }
     }
 
@@ -91,7 +94,7 @@
             int size = in.readInt();
             System.out.print("Type list i=" + i + ", size=" + size + ", elements=");
             for (int t = 0; t < size; t++) {
-                System.out.print(" " + in.readTypeName((int) in.readShort()));
+                System.out.print(" " + dexBuffer.typeNames().get((int) in.readShort()));
             }
             if (size % 2 == 1) {
                 in.readShort(); // retain alignment
@@ -101,9 +104,10 @@
     }
 
     private void printClassDefs() {
-        DexBuffer.Section in = dexBuffer.open(tableOfContents.classDefs.off);
-        for (int i = 0; i < tableOfContents.classDefs.size; i++) {
-            System.out.println("class def " + i + ": " + in.readClassDef());
+        int index = 0;
+        for (ClassDef classDef : dexBuffer.classDefs()) {
+            System.out.println("class def " + index + ": " + classDef);
+            index++;
         }
     }
 
diff --git a/dx/src/com/android/dx/io/FieldId.java b/dx/src/com/android/dx/io/FieldId.java
index c0eeac3..ab481e0 100644
--- a/dx/src/com/android/dx/io/FieldId.java
+++ b/dx/src/com/android/dx/io/FieldId.java
@@ -17,7 +17,6 @@
 package com.android.dx.io;
 
 import com.android.dx.util.Unsigned;
-import java.io.IOException;
 
 public final class FieldId implements Comparable<FieldId> {
     private final DexBuffer buffer;
@@ -54,7 +53,7 @@
         return Unsigned.compare(typeIndex, other.typeIndex); // should always be 0
     }
 
-    public void writeTo(DexBuffer.Section out) throws IOException {
+    public void writeTo(DexBuffer.Section out) {
         out.writeShort(declaringClassIndex);
         out.writeShort(typeIndex);
         out.writeInt(nameIndex);
@@ -64,9 +63,8 @@
         if (buffer == null) {
             return declaringClassIndex + " " + typeIndex + " " + nameIndex;
         }
-        DexBuffer.Section in = buffer.open(0);
-        return in.readType(declaringClassIndex)
-                + " { " + in.readTypeName(typeIndex)
-                + " " + in.readString(nameIndex) + " }";
+        return buffer.typeNames().get(declaringClassIndex)
+                + " { " + buffer.typeNames().get(typeIndex)
+                + " " + buffer.strings().get(nameIndex) + " }";
     }
 }
diff --git a/dx/src/com/android/dx/io/MethodId.java b/dx/src/com/android/dx/io/MethodId.java
index 9498b68..2934497 100644
--- a/dx/src/com/android/dx/io/MethodId.java
+++ b/dx/src/com/android/dx/io/MethodId.java
@@ -17,7 +17,6 @@
 package com.android.dx.io;
 
 import com.android.dx.util.Unsigned;
-import java.io.IOException;
 
 public final class MethodId implements Comparable<MethodId> {
     private final DexBuffer buffer;
@@ -54,7 +53,7 @@
         return Unsigned.compare(protoIndex, other.protoIndex);
     }
 
-    public void writeTo(DexBuffer.Section out) throws IOException {
+    public void writeTo(DexBuffer.Section out) {
         out.writeShort(declaringClassIndex);
         out.writeShort(protoIndex);
         out.writeInt(nameIndex);
@@ -64,9 +63,8 @@
         if (buffer == null) {
             return declaringClassIndex + " " + protoIndex + " " + nameIndex;
         }
-        DexBuffer.Section in = buffer.open(0);
-        return in.readTypeName(declaringClassIndex)
-                + " " + in.readProtoId(protoIndex)
-                + " " + in.readString(nameIndex);
+        return buffer.typeNames().get(declaringClassIndex)
+                + " " + buffer.protoIds().get(protoIndex)
+                + " " + buffer.strings().get(nameIndex);
     }
 }
diff --git a/dx/src/com/android/dx/io/ProtoId.java b/dx/src/com/android/dx/io/ProtoId.java
index dcab90f..da64c44 100644
--- a/dx/src/com/android/dx/io/ProtoId.java
+++ b/dx/src/com/android/dx/io/ProtoId.java
@@ -17,7 +17,6 @@
 package com.android.dx.io;
 
 import com.android.dx.util.Unsigned;
-import java.io.IOException;
 import java.util.Arrays;
 
 public final class ProtoId implements Comparable<ProtoId> {
@@ -57,7 +56,7 @@
         return parameters;
     }
 
-    public void writeTo(DexBuffer.Section out, int typeListOffset) throws IOException {
+    public void writeTo(DexBuffer.Section out, int typeListOffset) {
         out.writeInt(shortyIndex);
         out.writeInt(returnTypeIndex);
         out.writeInt(typeListOffset);
@@ -68,18 +67,17 @@
             return shortyIndex + " " + returnTypeIndex + " " + Arrays.toString(parameters);
         }
 
-        DexBuffer.Section in = buffer.open(0);
         StringBuilder result = new StringBuilder()
-                .append(in.readString(shortyIndex))
+                .append(buffer.strings().get(shortyIndex))
                 .append(": ")
-                .append(in.readTypeName(returnTypeIndex))
+                .append(buffer.typeNames().get(returnTypeIndex))
                 .append(" (");
         int j = 0;
         for (short parameter : parameters) {
             if (j > 0) {
                 result.append(", ");
             }
-            result.append(in.readTypeName(parameter));
+            result.append(buffer.typeNames().get(parameter));
             j++;
         }
         result.append(")");
diff --git a/dx/src/com/android/dx/merge/DexMerger.java b/dx/src/com/android/dx/merge/DexMerger.java
index 7e8321a..a5c7874 100644
--- a/dx/src/com/android/dx/merge/DexMerger.java
+++ b/dx/src/com/android/dx/merge/DexMerger.java
@@ -18,13 +18,14 @@
 
 import com.android.dx.dex.SizeOf;
 import com.android.dx.dex.TableOfContents;
+import com.android.dx.io.ClassData;
 import com.android.dx.io.ClassDef;
+import com.android.dx.io.Code;
 import com.android.dx.io.DexBuffer;
 import com.android.dx.io.DexHasher;
 import com.android.dx.io.FieldId;
 import com.android.dx.io.MethodId;
 import com.android.dx.io.ProtoId;
-import com.android.dx.util.Uint;
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
@@ -43,20 +44,22 @@
     private final DexBuffer.Section mapListWriter;
     private final DexBuffer.Section typeListWriter;
     private final DexBuffer.Section annotationSetRefListWriter;
-    private final DexBuffer.Section annotationSetItemWriter;
-    private final DexBuffer.Section classDataItemWriter;
-    private final DexBuffer.Section codeItemWriter;
-    private final DexBuffer.Section stringDataItemWriter;
-    private final DexBuffer.Section debugInfoItemWriter;
-    private final DexBuffer.Section annotationItemWriter;
-    private final DexBuffer.Section encodedArrayItemWriter;
-    private final DexBuffer.Section annotationsDirectoryItemWriter;
+    private final DexBuffer.Section annotationSetWriter;
+    private final DexBuffer.Section classDataWriter;
+    private final DexBuffer.Section codeWriter;
+    private final DexBuffer.Section stringDataWriter;
+    private final DexBuffer.Section debugInfoWriter;
+    private final DexBuffer.Section annotationWriter;
+    private final DexBuffer.Section encodedArrayWriter;
+    private final DexBuffer.Section annotationsDirectoryWriter;
     private final TableOfContents contentsOut;
 
     private final DexBuffer dexA = new DexBuffer();
     private final DexBuffer dexB = new DexBuffer();
     private final IndexMap aIndexMap;
     private final IndexMap bIndexMap;
+    private final InstructionTransformer aInstructionTransformer;
+    private final InstructionTransformer bInstructionTransformer;
 
     public DexMerger(File dexOut, File a, File b) throws IOException {
         if (!a.exists() || !b.exists()) {
@@ -73,6 +76,8 @@
 
         aIndexMap = new IndexMap(dexWriter, aContents);
         bIndexMap = new IndexMap(dexWriter, bContents);
+        aInstructionTransformer = new InstructionTransformer(aIndexMap);
+        bInstructionTransformer = new InstructionTransformer(bIndexMap);
 
         headerWriter = dexWriter.appendSection(SizeOf.HEADER_ITEM, "header");
 
@@ -100,13 +105,13 @@
          *
          * typeList: we don't deduplicate identical type lists. This should be fixed.
          *
-         * classDataItemWriter: uleb references to code items are larger than
+         * classDataWriter: uleb references to code items are larger than
          *     expected. We should use old & new code_item section offsets to
          *     pick an appropriate blow up size
          *
-         * stringDataItemWriter: this shouldn't have to be larger, but it is
+         * stringDataWriter: this shouldn't have to be larger, but it is
          *
-         * encodedArrayItemWriter: this shouldn't have to be larger, but it is
+         * encodedArrayWriter: this shouldn't have to be larger, but it is
          */
 
         contentsOut.typeLists.off = dexWriter.getLength();
@@ -120,48 +125,48 @@
 
         contentsOut.annotationSets.off = dexWriter.getLength();
         contentsOut.annotationSets.size = 0;
-        annotationSetItemWriter = dexWriter.appendSection(SizeOf.UINT, "annotation set item");
+        annotationSetWriter = dexWriter.appendSection(SizeOf.UINT, "annotation set");
 
         contentsOut.classDatas.off = dexWriter.getLength();
         contentsOut.classDatas.size = 0;
-        int maxClassDataItemBytes = aContents.classDatas.byteCount + bContents.classDatas.byteCount;
-        classDataItemWriter = dexWriter.appendSection(maxClassDataItemBytes * 2, "class data");
+        int maxClassDataBytes = aContents.classDatas.byteCount + bContents.classDatas.byteCount;
+        classDataWriter = dexWriter.appendSection(maxClassDataBytes * 2, "class data");
 
         contentsOut.codes.off = dexWriter.getLength();
         contentsOut.codes.size = 0;
-        int maxCodeItemBytes = aContents.codes.byteCount + bContents.codes.byteCount;
-        codeItemWriter = dexWriter.appendSection(maxCodeItemBytes, "code item");
+        int maxCodeBytes = aContents.codes.byteCount + bContents.codes.byteCount;
+        codeWriter = dexWriter.appendSection(maxCodeBytes, "code");
 
         contentsOut.stringDatas.off = dexWriter.getLength();
         contentsOut.stringDatas.size = 0;
-        int maxStringDataItemBytes = aContents.stringDatas.byteCount
+        int maxStringDataBytes = aContents.stringDatas.byteCount
                 + bContents.stringDatas.byteCount;
-        stringDataItemWriter = dexWriter.appendSection(maxStringDataItemBytes * 2, "string data");
+        stringDataWriter = dexWriter.appendSection(maxStringDataBytes * 2, "string data");
 
         contentsOut.debugInfos.off = dexWriter.getLength();
         contentsOut.debugInfos.size = 0;
-        int maxDebugInfoItemBytes = aContents.debugInfos.byteCount + bContents.debugInfos.byteCount;
-        debugInfoItemWriter = dexWriter.appendSection(maxDebugInfoItemBytes, "debug info");
+        int maxDebugInfoBytes = aContents.debugInfos.byteCount + bContents.debugInfos.byteCount;
+        debugInfoWriter = dexWriter.appendSection(maxDebugInfoBytes, "debug info");
 
         contentsOut.annotations.off = dexWriter.getLength();
         contentsOut.annotations.size = 0;
-        int maxAnnotationItemBytes = aContents.annotations.byteCount
+        int maxAnnotationBytes = aContents.annotations.byteCount
                 + bContents.annotations.byteCount;
-        annotationItemWriter = dexWriter.appendSection(maxAnnotationItemBytes, "annotation");
+        annotationWriter = dexWriter.appendSection(maxAnnotationBytes, "annotation");
 
         contentsOut.encodedArrays.off = dexWriter.getLength();
         contentsOut.encodedArrays.size = 0;
-        int maxEncodedArrayItemBytes = aContents.encodedArrays.byteCount
+        int maxEncodedArrayBytes = aContents.encodedArrays.byteCount
                 + bContents.encodedArrays.byteCount;
-        encodedArrayItemWriter = dexWriter.appendSection(
-                maxEncodedArrayItemBytes * 2, "encoded array");
+        encodedArrayWriter = dexWriter.appendSection(
+                maxEncodedArrayBytes * 2, "encoded array");
 
         contentsOut.annotationsDirectories.off = dexWriter.getLength();
         contentsOut.annotationsDirectories.size = 0;
-        int maxAnnotationsDirectoryItemBytes = aContents.annotationsDirectories.byteCount
+        int maxAnnotationsDirectoryBytes = aContents.annotationsDirectories.byteCount
                 + bContents.annotationsDirectories.byteCount;
-        annotationsDirectoryItemWriter = dexWriter.appendSection(
-                maxAnnotationsDirectoryItemBytes, "annotations");
+        annotationsDirectoryWriter = dexWriter.appendSection(
+                maxAnnotationsDirectoryBytes, "annotations");
 
         dexWriter.noMoreSections();
         contentsOut.dataSize = dexWriter.getLength() - contentsOut.dataOff;
@@ -198,11 +203,9 @@
      * merged dex file. Populates maps from old to new indices in the process.
      */
     abstract class IdMerger<T extends Comparable<T>> {
-        public final void merge() throws IOException {
+        public final void merge() {
             TableOfContents.Section aSection = getSection(dexA.getTableOfContents());
             TableOfContents.Section bSection = getSection(dexB.getTableOfContents());
-            DexBuffer.Section aIn = dexA.open(aSection.off);
-            DexBuffer.Section bIn = dexB.open(bSection.off);
             getSection(contentsOut).off = idsDefsWriter.getPosition();
 
             int aIndex = 0;
@@ -213,10 +216,10 @@
 
             while (true) {
                 if (a == null && aIndex < aSection.size) {
-                    a = read(aIn, aIndexMap, aIndex);
+                    a = read(dexA, aIndexMap, aIndex);
                 }
                 if (b == null && bIndex < bSection.size) {
-                    b = read(bIn, bIndexMap, bIndex);
+                    b = read(dexB, bIndexMap, bIndex);
                 }
 
                 // Write the smaller of a and b. If they're equal, write only once
@@ -253,120 +256,116 @@
         }
 
         abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
-        abstract T read(DexBuffer.Section in, IndexMap indexMap, int index) throws IOException;
+        abstract T read(DexBuffer dexBuffer, IndexMap indexMap, int index);
         abstract void updateIndex(IndexMap indexMap, int oldIndex, int newIndex);
-        abstract void write(T value) throws IOException;
+        abstract void write(T value);
     }
 
-    private void mergeStringIds() throws IOException {
+    private void mergeStringIds() {
         new IdMerger<String>() {
             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
                 return tableOfContents.stringIds;
             }
 
-            @Override String read(DexBuffer.Section in, IndexMap indexMap, int index)
-                    throws IOException {
-                return in.readString(index);
+            @Override String read(DexBuffer dexBuffer, IndexMap indexMap, int index) {
+                return dexBuffer.strings().get(index);
             }
 
             @Override void updateIndex(IndexMap indexMap, int oldIndex, int newIndex) {
                 indexMap.stringIds[oldIndex] = newIndex;
             }
 
-            @Override void write(String value) throws IOException {
+            @Override void write(String value) {
                 contentsOut.stringDatas.size++;
-                idsDefsWriter.writeInt(stringDataItemWriter.getPosition());
-                stringDataItemWriter.writeStringDataItem(value);
+                idsDefsWriter.writeInt(stringDataWriter.getPosition());
+                stringDataWriter.writeStringData(value);
             }
         }.merge();
     }
 
-    private void mergeTypeIds() throws IOException {
-        new IdMerger<Uint>() {
+    private void mergeTypeIds() {
+        new IdMerger<Integer>() {
             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
                 return tableOfContents.typeIds;
             }
 
-            @Override Uint read(DexBuffer.Section in, IndexMap indexMap, int index)
-                    throws IOException {
-                return new Uint(indexMap.adjustString(in.readInt()));
+            @Override Integer read(DexBuffer dexBuffer, IndexMap indexMap, int index) {
+                Integer stringIndex = dexBuffer.typeIds().get(index);
+                return indexMap.adjustString(stringIndex);
             }
 
             @Override void updateIndex(IndexMap indexMap, int oldIndex, int newIndex) {
                 indexMap.typeIds[oldIndex] = (short) newIndex;
             }
 
-            @Override void write(Uint value) throws IOException {
-                idsDefsWriter.writeInt(value.intValue);
+            @Override void write(Integer value) {
+                idsDefsWriter.writeInt(value);
             }
         }.merge();
     }
 
-    private void mergeProtoIds() throws IOException {
+    private void mergeProtoIds() {
         new IdMerger<ProtoId>() {
             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
                 return tableOfContents.protoIds;
             }
 
-            @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index)
-                    throws IOException {
-                return indexMap.adjust(in.readProtoId());
+            @Override ProtoId read(DexBuffer dexBuffer, IndexMap indexMap, int index) {
+                return indexMap.adjust(dexBuffer.protoIds().get(index));
             }
 
             @Override void updateIndex(IndexMap indexMap, int oldIndex, int newIndex) {
                 indexMap.protoIds[oldIndex] = (short) newIndex;
             }
 
-            @Override void write(ProtoId value) throws IOException {
+            @Override void write(ProtoId value) {
                 int typeListPosition = writeTypeList(value.getParameters());
                 value.writeTo(idsDefsWriter, typeListPosition);
             }
         }.merge();
     }
 
-    private void mergeFieldIds() throws IOException {
+    private void mergeFieldIds() {
         new IdMerger<FieldId>() {
             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
                 return tableOfContents.fieldIds;
             }
 
-            @Override
-            FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) throws IOException {
-                return indexMap.adjust(in.readFieldId());
+            @Override FieldId read(DexBuffer dexBuffer, IndexMap indexMap, int index) {
+                return indexMap.adjust(dexBuffer.fieldIds().get(index));
             }
 
             @Override void updateIndex(IndexMap indexMap, int oldIndex, int newIndex) {
                 indexMap.fieldIds[oldIndex] = (short) newIndex;
             }
 
-            @Override void write(FieldId value) throws IOException {
+            @Override void write(FieldId value) {
                 value.writeTo(idsDefsWriter);
             }
         }.merge();
     }
 
-    private void mergeMethodIds() throws IOException {
+    private void mergeMethodIds() {
         new IdMerger<MethodId>() {
             @Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
                 return tableOfContents.methodIds;
             }
 
-            @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index)
-                    throws IOException {
-                return indexMap.adjust(in.readMethodId());
+            @Override MethodId read(DexBuffer dexBuffer, IndexMap indexMap, int index) {
+                return indexMap.adjust(dexBuffer.methodIds().get(index));
             }
 
             @Override void updateIndex(IndexMap indexMap, int oldIndex, int newIndex) {
                 indexMap.methodIds[oldIndex] = (short) newIndex;
             }
 
-            @Override void write(MethodId methodId) throws IOException {
+            @Override void write(MethodId methodId) {
                 methodId.writeTo(idsDefsWriter);
             }
         }.merge();
     }
 
-    private void mergeClassDefs() throws IOException {
+    private void mergeClassDefs() {
         SortableType[] types = getSortedTypes();
         contentsOut.classDefs.off = idsDefsWriter.getPosition();
         contentsOut.classDefs.size = types.length;
@@ -382,7 +381,7 @@
      * Returns the union of classes from both files, sorted in order such that
      * a class is always preceded by its supertype and implemented interfaces.
      */
-    private SortableType[] getSortedTypes() throws IOException {
+    private SortableType[] getSortedTypes() {
         // size is pessimistic; doesn't include arrays
         SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size];
         readSortableTypes(sortableTypes, dexA, aIndexMap);
@@ -419,13 +418,9 @@
      * it later.
      */
     private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer,
-            IndexMap indexMap) throws IOException {
-        TableOfContents tableOfContents = buffer.getTableOfContents();
-        DexBuffer.Section classDefsIn = buffer.open(tableOfContents.classDefs.off);
-
-        for (int i = 0; i < tableOfContents.classDefs.size; i++) {
-            SortableType sortableType = indexMap.adjust(
-                    new SortableType(buffer, classDefsIn.readClassDef()));
+            IndexMap indexMap) {
+        for (ClassDef classDef : buffer.classDefs()) {
+            SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef));
             int t = sortableType.getTypeIndex();
             if (sortableTypes[t] == null) {
                 sortableTypes[t] = sortableType;
@@ -437,8 +432,7 @@
      * Reads a class_def_item beginning at {@code in} and writes the index and
      * data.
      */
-    private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap)
-            throws IOException {
+    private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) {
         idsDefsWriter.assertFourByteAligned();
         idsDefsWriter.writeInt(classDef.getTypeIndex());
         idsDefsWriter.writeInt(classDef.getAccessFlags());
@@ -457,8 +451,8 @@
             idsDefsWriter.writeInt(0);
         } else {
             DexBuffer.Section annotationsIn = in.open(annotationsOff);
-            annotationsDirectoryItemWriter.alignToFourBytes();
-            idsDefsWriter.writeInt(annotationsDirectoryItemWriter.getPosition());
+            annotationsDirectoryWriter.alignToFourBytes();
+            idsDefsWriter.writeInt(annotationsDirectoryWriter.getPosition());
             transformAnnotations(annotationsIn, indexMap);
         }
 
@@ -466,9 +460,9 @@
         if (classDataOff == 0) {
             idsDefsWriter.writeInt(0);
         } else {
-            DexBuffer.Section classDataIn = in.open(classDataOff);
-            idsDefsWriter.writeInt(classDataItemWriter.getPosition());
-            transformClassData(classDataIn, indexMap);
+            idsDefsWriter.writeInt(classDataWriter.getPosition());
+            ClassData classData = in.readClassData(classDef);
+            transformClassData(in, classData, indexMap);
         }
 
         int staticValuesOff = classDef.getStaticValuesOffset();
@@ -476,12 +470,12 @@
             idsDefsWriter.writeInt(0);
         } else {
             DexBuffer.Section staticValuesIn = in.open(staticValuesOff);
-            idsDefsWriter.writeInt(encodedArrayItemWriter.getPosition());
+            idsDefsWriter.writeInt(encodedArrayWriter.getPosition());
             transformStaticValues(staticValuesIn, indexMap);
         }
     }
 
-    private int writeTypeList(short[] interfaces) throws IOException {
+    private int writeTypeList(short[] interfaces) {
         if (interfaces.length == 0) {
             return 0;
         }
@@ -493,160 +487,134 @@
         return cursor;
     }
 
-    private void transformAnnotations(DexBuffer.Section in, IndexMap indexMap) throws IOException {
+    private void transformAnnotations(DexBuffer.Section in, IndexMap indexMap) {
         contentsOut.annotationsDirectories.size++;
 
         // TODO: retain annotations
-        annotationsDirectoryItemWriter.assertFourByteAligned();
+        annotationsDirectoryWriter.assertFourByteAligned();
         in.readInt(); // class annotations off
         in.readInt(); // fields size
         in.readInt(); // annotated methods size
         in.readInt(); // annotated parameters size
 
-        annotationsDirectoryItemWriter.writeInt(0);
-        annotationsDirectoryItemWriter.writeInt(0);
-        annotationsDirectoryItemWriter.writeInt(0);
-        annotationsDirectoryItemWriter.writeInt(0);
+        annotationsDirectoryWriter.writeInt(0);
+        annotationsDirectoryWriter.writeInt(0);
+        annotationsDirectoryWriter.writeInt(0);
+        annotationsDirectoryWriter.writeInt(0);
     }
 
-    private void transformClassData(DexBuffer.Section in, IndexMap indexMap) throws IOException {
+    private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) {
         contentsOut.classDatas.size++;
 
-        int staticFieldsSize = in.readUleb128();
-        classDataItemWriter.writeUleb128(staticFieldsSize);
+        ClassData.Field[] staticFields = classData.getStaticFields();
+        ClassData.Field[] instanceFields = classData.getInstanceFields();
+        ClassData.Method[] directMethods = classData.getDirectMethods();
+        ClassData.Method[] virtualMethods = classData.getVirtualMethods();
 
-        int instanceFieldsSize = in.readUleb128();
-        classDataItemWriter.writeUleb128(instanceFieldsSize);
+        classDataWriter.writeUleb128(staticFields.length);
+        classDataWriter.writeUleb128(instanceFields.length);
+        classDataWriter.writeUleb128(directMethods.length);
+        classDataWriter.writeUleb128(virtualMethods.length);
 
-        int directMethodsSize = in.readUleb128();
-        classDataItemWriter.writeUleb128(directMethodsSize);
-
-        int virtualMethodsSize = in.readUleb128();
-        classDataItemWriter.writeUleb128(virtualMethodsSize);
-
-        transformEncodedFields(in, indexMap, staticFieldsSize);
-        transformEncodedFields(in, indexMap, instanceFieldsSize);
-
-        transformEncodedMethods(in, indexMap, directMethodsSize);
-        transformEncodedMethods(in, indexMap, virtualMethodsSize);
+        transformFields(indexMap, staticFields);
+        transformFields(indexMap, instanceFields);
+        transformMethods(in, indexMap, directMethods);
+        transformMethods(in, indexMap, virtualMethods);
     }
 
-    private void transformEncodedFields(DexBuffer.Section in, IndexMap indexMap, int count)
-            throws IOException {
-        int inFieldIndex = 0;
+    private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
         int lastOutFieldIndex = 0;
-        for (int i = 0; i < count; i++) {
-            inFieldIndex += in.readUleb128(); // field idx diff
-            int outFieldIndex = indexMap.adjustField(inFieldIndex);
-            classDataItemWriter.writeUleb128(outFieldIndex - lastOutFieldIndex);
+        for (ClassData.Field field : fields) {
+            int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
+            classDataWriter.writeUleb128(outFieldIndex - lastOutFieldIndex);
             lastOutFieldIndex = outFieldIndex;
-
-            classDataItemWriter.writeUleb128(in.readUleb128()); // access flags
+            classDataWriter.writeUleb128(field.getAccessFlags());
         }
     }
 
-    /**
-     * Transforms a list of encoded methods.
-     */
-    private void transformEncodedMethods(DexBuffer.Section in, IndexMap indexMap, int count)
-            throws IOException {
-        int inMethodIndex = 0;
+    private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) {
         int lastOutMethodIndex = 0;
-        for (int i = 0; i < count; i++) {
-            inMethodIndex += in.readUleb128(); // method idx diff
-            int outMethodIndex = indexMap.adjustMethod(inMethodIndex);
-            classDataItemWriter.writeUleb128(outMethodIndex - lastOutMethodIndex);
+        for (ClassData.Method method : methods) {
+            int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
+            classDataWriter.writeUleb128(outMethodIndex - lastOutMethodIndex);
             lastOutMethodIndex = outMethodIndex;
 
-            classDataItemWriter.writeUleb128(in.readUleb128()); // access flags
+            classDataWriter.writeUleb128(method.getAccessFlags());
 
-            int codeOff = in.readUleb128(); // code off
-            if (codeOff == 0) {
-                classDataItemWriter.writeUleb128(0);
+            if (method.getCodeOffset() == 0) {
+                classDataWriter.writeUleb128(0);
             } else {
-                codeItemWriter.alignToFourBytes();
-                classDataItemWriter.writeUleb128(codeItemWriter.getPosition());
-                transformCodeItem(in.open(codeOff), indexMap);
+                codeWriter.alignToFourBytes();
+                classDataWriter.writeUleb128(codeWriter.getPosition());
+                transformCode(in, in.readCode(method), indexMap);
             }
         }
     }
 
-    private void transformCodeItem(DexBuffer.Section in, IndexMap indexMap) throws IOException {
+    private void transformCode(DexBuffer in, Code code, IndexMap indexMap) {
         contentsOut.codes.size++;
-        codeItemWriter.assertFourByteAligned();
+        codeWriter.assertFourByteAligned();
 
-        short registersSize = in.readShort();
-        codeItemWriter.writeShort(registersSize); // registers size
-        short insSize = in.readShort();
-        codeItemWriter.writeShort(insSize); // ins size
-        short outsSize = in.readShort();
-        codeItemWriter.writeShort(outsSize); // outs size
-        short triesSize = in.readShort(); // tries size
-        codeItemWriter.writeShort(triesSize);
+        codeWriter.writeShort(code.getRegistersSize());
+        codeWriter.writeShort(code.getInsSize());
+        codeWriter.writeShort(code.getOutsSize());
 
-        in.readInt(); // debug info off
-        codeItemWriter.writeInt(0); // TODO: retain debug info
+        Code.Try[] tries = code.getTries();
+        codeWriter.writeShort((short) tries.length);
 
-        int insnsSize = in.readInt(); // insns_size
-        short[] insns = in.readShortArray(insnsSize); // insns
-        short[] newInstructions = new InstructionTransformer(indexMap).transform(insns);
-        codeItemWriter.writeInt(newInstructions.length);
-        codeItemWriter.write(newInstructions);
+        // TODO: retain debug info
+        // code.getDebugInfoOffset();
+        codeWriter.writeInt(0);
 
-        if (triesSize > 0) {
-            // padding
-            if (insns.length % 2 == 1) {
-                in.readShort();
-            }
+        short[] instructions = code.getInstructions();
+        InstructionTransformer transformer = (in == dexA)
+                ? aInstructionTransformer
+                : bInstructionTransformer;
+        short[] newInstructions = transformer.transform(instructions);
+        codeWriter.writeInt(newInstructions.length);
+        codeWriter.write(newInstructions);
+
+        if (tries.length > 0) {
             if (newInstructions.length % 2 == 1) {
-                codeItemWriter.writeShort((short) 0);
+                codeWriter.writeShort((short) 0); // padding
             }
-
-            // tries
-            for (int i = 0; i < triesSize; i++) {
-                transformTryItem(in, indexMap);
+            for (Code.Try tryItem : tries) {
+                codeWriter.writeInt(tryItem.getStartAddress());
+                codeWriter.writeShort(tryItem.getInstructionCount());
+                codeWriter.writeShort(tryItem.getHandlerOffset());
             }
-
-            // handlers
-            transformEncodedCatchHandlerList(in, indexMap);
+            Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
+            codeWriter.writeUleb128(catchHandlers.length);
+            for (Code.CatchHandler catchHandler : catchHandlers) {
+                transformEncodedCatchHandler(catchHandler, indexMap);
+            }
         }
     }
 
-    private void transformTryItem(DexBuffer.Section in, IndexMap indexMap) throws IOException {
-        codeItemWriter.writeInt(in.readInt()); // start addr
-        codeItemWriter.writeShort(in.readShort()); // insn count
-        codeItemWriter.writeShort(in.readShort()); // handler off
-    }
+    private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
+        int catchAllAddress = catchHandler.getCatchAllAddress();
+        int[] typeIndexes = catchHandler.getTypeIndexes();
+        int[] addresses = catchHandler.getAddresses();
 
-    private void transformEncodedCatchHandlerList(DexBuffer.Section in, IndexMap indexMap)
-            throws IOException {
-        int size = in.readUleb128(); // size
-        codeItemWriter.writeUleb128(size);
+        if (catchAllAddress != -1) {
+            codeWriter.writeSleb128(-typeIndexes.length);
+        } else {
+            codeWriter.writeSleb128(typeIndexes.length);
+        }
 
-        for (int i = 0; i < size; i++) {
-            transformEncodedCatchHandler(in, indexMap);
+        for (int i = 0; i < typeIndexes.length; i++) {
+            codeWriter.writeUleb128(indexMap.adjustType(typeIndexes[i]));
+            codeWriter.writeUleb128(addresses[i]);
+        }
+
+        if (catchAllAddress != -1) {
+            codeWriter.writeUleb128(catchAllAddress);
         }
     }
 
-    private void transformEncodedCatchHandler(DexBuffer.Section in, IndexMap indexMap)
-            throws IOException {
-        int size = in.readSleb128(); // size
-        codeItemWriter.writeSleb128(size);
-
-        int handlersCount = Math.abs(size);
-        for (int i = 0; i < handlersCount; i++) {
-            codeItemWriter.writeUleb128(indexMap.adjustType(in.readUleb128())); // type idx
-            codeItemWriter.writeUleb128(in.readUleb128()); // addr
-        }
-
-        if (size <= 0) {
-            codeItemWriter.writeUleb128(in.readUleb128()); // catch all addr
-        }
-    }
-
-    private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) throws IOException {
+    private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) {
         contentsOut.encodedArrays.size++;
-        new EncodedValueTransformer(indexMap, in, encodedArrayItemWriter).transformArray();
+        new EncodedValueTransformer(indexMap, in, encodedArrayWriter).transformArray();
     }
 
     public static void main(String[] args) throws IOException {
diff --git a/dx/src/com/android/dx/merge/EncodedValueTransformer.java b/dx/src/com/android/dx/merge/EncodedValueTransformer.java
index 68a9cd6..bcf0d72 100644
--- a/dx/src/com/android/dx/merge/EncodedValueTransformer.java
+++ b/dx/src/com/android/dx/merge/EncodedValueTransformer.java
@@ -18,9 +18,25 @@
 
 import com.android.dx.io.DexBuffer;
 import com.android.dx.util.Unsigned;
-import java.io.IOException;
 
 final class EncodedValueTransformer {
+    private static final int ENCODED_BYTE = 0x00;
+    private static final int ENCODED_SHORT = 0x02;
+    private static final int ENCODED_CHAR = 0x03;
+    private static final int ENCODED_INT = 0x04;
+    private static final int ENCODED_LONG = 0x06;
+    private static final int ENCODED_FLOAT = 0x10;
+    private static final int ENCODED_DOUBLE = 0x11;
+    private static final int ENCODED_STRING = 0x17;
+    private static final int ENCODED_TYPE = 0x18;
+    private static final int ENCODED_FIELD = 0x19;
+    private static final int ENCODED_ENUM = 0x1b;
+    private static final int ENCODED_METHOD = 0x1a;
+    private static final int ENCODED_ARRAY = 0x1c;
+    private static final int ENCODED_ANNOTATION = 0x1d;
+    private static final int ENCODED_NULL = 0x1e;
+    private static final int ENCODED_BOOLEAN = 0x1f;
+
     private final IndexMap indexMap;
     private final DexBuffer.Section in;
     private final DexBuffer.Section out;
@@ -31,7 +47,7 @@
         this.out = out;
     }
 
-    public void transformArray() throws IOException {
+    public void transformArray() {
         int size = in.readUleb128(); // size
         out.writeUleb128(size);
         for (int i = 0; i < size; i++) {
@@ -39,7 +55,7 @@
         }
     }
 
-    public void transformAnnotation() throws IOException {
+    public void transformAnnotation() {
         out.writeUleb128(indexMap.adjustType(in.readUleb128())); // type idx
 
         int size = in.readUleb128(); // size
@@ -51,64 +67,64 @@
         }
     }
 
-    public void transformValue() throws IOException {
+    public void transformValue() {
         int argAndType = in.readByte() & 0xff;
         int type = argAndType & 0x1f;
         int arg = (argAndType & 0xe0) >> 5;
         int size = arg + 1;
 
         switch (type) {
-        case 0x00: // byte
-        case 0x02: // short
-        case 0x03: // char
-        case 0x04: // int
-        case 0x06: // long
-        case 0x10: // float
-        case 0x11: // double
+        case ENCODED_BYTE:
+        case ENCODED_SHORT:
+        case ENCODED_CHAR:
+        case ENCODED_INT:
+        case ENCODED_LONG:
+        case ENCODED_FLOAT:
+        case ENCODED_DOUBLE:
             out.writeByte(argAndType);
             copyBytes(in, out, size);
             break;
 
-        case 0x17: // string
+        case ENCODED_STRING:
             int indexIn = readIndex(in, size);
             int indexOut = indexMap.adjustString(indexIn);
             writeTypeAndSizeAndIndex(type, indexOut, out);
             break;
-        case 0x18: // type
+        case ENCODED_TYPE:
             indexIn = readIndex(in, size);
             indexOut = indexMap.adjustType(indexIn);
             writeTypeAndSizeAndIndex(type, indexOut, out);
             break;
-        case 0x19: // field
-        case 0x1b: // enum
+        case ENCODED_FIELD:
+        case ENCODED_ENUM:
             indexIn = readIndex(in, size);
             indexOut = indexMap.adjustField(indexIn);
             writeTypeAndSizeAndIndex(type, indexOut, out);
             break;
-        case 0x1a: // method
+        case ENCODED_METHOD:
             indexIn = readIndex(in, size);
             indexOut = indexMap.adjustMethod(indexIn);
             writeTypeAndSizeAndIndex(type, indexOut, out);
             break;
 
-        case 0x1c: // array
+        case ENCODED_ARRAY:
             out.writeByte(argAndType);
             transformArray();
             break;
 
-        case 0x1d: // annotation
+        case ENCODED_ANNOTATION:
             out.writeByte(argAndType);
             transformAnnotation();
             break;
 
-        case 0x1e: // null
-        case 0x1f: // boolean
+        case ENCODED_NULL:
+        case ENCODED_BOOLEAN:
             out.writeByte(argAndType);
             break;
         }
     }
 
-    private int readIndex(DexBuffer.Section in, int byteCount) throws IOException {
+    private int readIndex(DexBuffer.Section in, int byteCount) {
         int result = 0;
         int shift = 0;
         for (int i = 0; i < byteCount; i++) {
@@ -118,8 +134,7 @@
         return result;
     }
 
-    private void writeTypeAndSizeAndIndex(int type, int index, DexBuffer.Section out)
-            throws IOException {
+    private void writeTypeAndSizeAndIndex(int type, int index, DexBuffer.Section out) {
         int byteCount;
         if (Unsigned.compare(index, 0xff) <= 0) {
             byteCount = 1;
@@ -139,8 +154,7 @@
         }
     }
 
-    private void copyBytes(DexBuffer.Section in, DexBuffer.Section out, int size)
-            throws IOException {
+    private void copyBytes(DexBuffer.Section in, DexBuffer.Section out, int size) {
         for (int i = 0; i < size; i++) {
             out.writeByte(in.readByte());
         }
diff --git a/dx/src/com/android/dx/merge/InstructionTransformer.java b/dx/src/com/android/dx/merge/InstructionTransformer.java
index 1e0aa49..951353e 100644
--- a/dx/src/com/android/dx/merge/InstructionTransformer.java
+++ b/dx/src/com/android/dx/merge/InstructionTransformer.java
@@ -17,330 +17,30 @@
 package com.android.dx.merge;
 
 import com.android.dx.dex.DexException;
-import java.util.BitSet;
+import com.android.dx.io.CodeReader;
 
-/**
- * Adjusts a block of instructions to a new index.
- */
 final class InstructionTransformer {
-
-    private static final Instruction[] INSTRUCTIONS = new Instruction[] {
-            // 0x00...0x0f
-            new Instruction(1, "nop"),
-            new Instruction(1, "move vA, vB"),
-            new Instruction(2, "move/from vAA, vBBBB"),
-            new Instruction(3, "move/16 vAAAA, vBBBB"),
-            new Instruction(1, "move-wide, vA, vB"),
-            new Instruction(2, "move-wide/from16 vAA, vBBBB"),
-            new Instruction(3, "move-wide/from16 vAAAA, vBBBB"),
-            new Instruction(1, "move-object vA, vB"),
-            new Instruction(2, "move-object/from16 vAA, vBBBB"),
-            new Instruction(3, "move-object/16 vAAAA, vBBBB"),
-            new Instruction(1, "move-result vAA"),
-            new Instruction(1, "move-result-wide vAA"),
-            new Instruction(1, "move-result-object vAA"),
-            new Instruction(1, "move-exception vAA"),
-            new Instruction(1, "return void"),
-            new Instruction(1, "return vAA"),
-
-            // 0x10...0x1f
-            new Instruction(1, "return-wide vAA"),
-            new Instruction(1, "return-object vAA"),
-            new Instruction(1, "const/4 vA, #+B"),
-            new Instruction(2, "const/16 vAA, #+BBBB"),
-            new Instruction(3, "const vAA, #+BBBBBBBB"),
-            new Instruction(2, "const/high16 vAA, #+BBBB0000"),
-            new Instruction(2, "const-wide/16 vAA, #+BBBB"),
-            new Instruction(3, "const-wide/32 vAA, #+BBBBBBBB"),
-            new Instruction(5, "const-wide vAA, #+BBBBBBBBBBBBBBBB"),
-            new Instruction(2, "const-wide/high16 vAA, #+BBBB000000000000"),
-            new StringInstruction(2, "const-string vAA, string@BBBB"),
-            new JumboStringInstruction(3, "const-string/jumbo vAA, string@BBBBBBBB"),
-            new TypeInstruction(2, "const-class vAA, type@BBBB"),
-            new Instruction(1, "monitor-enter vAA"),
-            new Instruction(1, "monitor-exit vAA"),
-            new TypeInstruction(2, "check-cast vAA type@BBBB"),
-
-            // 0x20...0x2f
-            new TypeInstruction(2, "instance-of vA, vB, type@CCCC"),
-            new Instruction(1, "array-length vA, vB"),
-            new TypeInstruction(2, "new-instance vAA, type@BBBB"),
-            new TypeInstruction(2, "new-array vA, vB, type@CCCC"),
-            new TypeInstruction(3, "filled-new-array {vD, vE, vF, vG, vA}, type@CCCC"),
-            new TypeInstruction(3, "filled-new-array/range {vCCCC..vNNNN}, type@BBBB"),
-            new FillArrayInstruction(3, "fill-array-data vAA, +BBBBBBBB"),
-            new Instruction(1, "throw vAA"),
-            new Instruction(1, "goto +AA"),
-            new Instruction(2, "goto/16 +AAAA"),
-            new Instruction(3, "goto/32 +AAAAAAAA"),
-            new PackedSwitchInstruction(3, "packed-switch vAA, +BBBBBBBB"),
-            new SparseSwitchInstruction(3, "sparse-switch vAA, +BBBBBBBB"),
-            new Instruction(2, "cmpl-float vAA, vBB, vCC"),
-            new Instruction(2, "cmpg-float vAA, vBB, vCC"),
-            new Instruction(2, "cmpl-double vAA, vBB, vCC"),
-
-            // 0x30...0x3f
-            new Instruction(2, "cmpg-double vAA, vBB, vCC"),
-            new Instruction(2, "cmp-long vAA, vBB, vCC"),
-            new Instruction(2, "if-eq vA, vB, +CCCC"),
-            new Instruction(2, "if-ne vA, vB, +CCCC"),
-            new Instruction(2, "if-lt vA, vB, +CCCC"),
-            new Instruction(2, "if-ge vA, vB, +CCCC"),
-            new Instruction(2, "if-gt vA, vB, +CCCC"),
-            new Instruction(2, "if-le vA, vB, +CCCC"),
-            new Instruction(2, "if-eqz vAA, +BBBB"),
-            new Instruction(2, "if-nez vAA, +BBBB"),
-            new Instruction(2, "if-ltz vAA, +BBBB"),
-            new Instruction(2, "if-gez vAA, +BBBB"),
-            new Instruction(2, "if-gtz vAA, +BBBB"),
-            new Instruction(2, "if-lez vAA, +BBBB"),
-            new UnusedInstruction(),
-            new UnusedInstruction(),
-
-            // 0x40...0x4f
-            new UnusedInstruction(),
-            new UnusedInstruction(),
-            new UnusedInstruction(),
-            new UnusedInstruction(),
-            new Instruction(2, "aget vAA, vBB, vCC"),
-            new Instruction(2, "aget-wide vAA, vBB, vCC"),
-            new Instruction(2, "aget-object vAA, vBB, vCC"),
-            new Instruction(2, "aget-boolean vAA, vBB, vCC"),
-            new Instruction(2, "aget-byte vAA, vBB, vCC"),
-            new Instruction(2, "aget-char vAA, vBB, vCC"),
-            new Instruction(2, "aget-short vAA, vBB, vCC"),
-            new Instruction(2, "aput vAA, vBB, vCC"),
-            new Instruction(2, "aput-wide vAA, vBB, vCC"),
-            new Instruction(2, "aput-object vAA, vBB, vCC"),
-            new Instruction(2, "aput-boolean vAA, vBB, vCC"),
-            new Instruction(2, "aput-byte vAA, vBB, vCC"),
-
-            // 0x50...0x5f
-            new Instruction(2, "aput-char vAA, vBB, vCC"),
-            new Instruction(2, "aput-short vAA, vBB, vCC"),
-            new FieldInstruction(2, "iget vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-wide vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-object vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-boolean vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-byte vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-char vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iget-short vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-wide vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-object vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-boolean vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-byte vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-char vA, vB, field@CCCC"),
-            new FieldInstruction(2, "iput-short vA, vB, field@CCCC"),
-
-            // 0x60...0x6f
-            new FieldInstruction(2, "sget vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-wide vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-object vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-boolean vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-byte vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-char vAA, field@BBBB"),
-            new FieldInstruction(2, "sget-short vAA, field@BBBB"),
-            new FieldInstruction(2, "sput vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-wide vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-object vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-boolean vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-byte vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-char vAA, field@BBBB"),
-            new FieldInstruction(2, "sput-short vAA, field@BBBB"),
-            new MethodInstruction(3, "invoke-virtual {vD, vE, vF, vG, vA}, meth@CCCC"),
-            new MethodInstruction(3, "invoke-super {vD, vE, vF, vG, vA}, meth@CCCC"),
-
-            // 0x70...0x7f
-            new MethodInstruction(3, "invoke-direct {vD, vE, vF, vG, vA}, meth@CCCC"),
-            new MethodInstruction(3, "invoke-static {vD, vE, vF, vG, vA}, meth@CCCC"),
-            new MethodInstruction(3, "invoke-interface {vD, vE, vF, vG, vA}, meth@CCCC"),
-            new UnusedInstruction(),
-            new MethodInstruction(3, "invoke-virtual/range {vCCCC..vNNNN}, meth@BBBB"),
-            new MethodInstruction(3, "invoke-super/range {vCCCC..vNNNN}, meth@BBBB"),
-            new MethodInstruction(3, "invoke-direct/range {vCCCC..vNNNN}, meth@BBBB"),
-            new MethodInstruction(3, "invoke-static/range {vCCCC..vNNNN}, meth@BBBB"),
-            new MethodInstruction(3, "invoke-interface/range {vCCCC..vNNNN}, meth@BBBB"),
-            new UnusedInstruction(),
-            new UnusedInstruction(),
-            new Instruction(1, "neg-int vA, vB"),
-            new Instruction(1, "not-int vA, vB"),
-            new Instruction(1, "neg-long vA, vB"),
-            new Instruction(1, "not-long vA, vB"),
-            new Instruction(1, "neg-float vA, vB"),
-
-            // 0x80...0x8f
-            new Instruction(1, "neg-double vA, vB"),
-            new Instruction(1, "int-to-long vA, vB"),
-            new Instruction(1, "int-to-float vA, vB"),
-            new Instruction(1, "int-to-double vA, vB"),
-            new Instruction(1, "long-to-int vA, vB"),
-            new Instruction(1, "long-to-float vA, vB"),
-            new Instruction(1, "long-to-double vA, vB"),
-            new Instruction(1, "float-to-int vA, vB"),
-            new Instruction(1, "float-to-long vA, vB"),
-            new Instruction(1, "float-to-double vA, vB"),
-            new Instruction(1, "double-to-int vA, vB"),
-            new Instruction(1, "double-to-long vA, vB"),
-            new Instruction(1, "double-to-float vA, vB"),
-            new Instruction(1, "int-to-byte vA, vB"),
-            new Instruction(1, "int-to-char vA, vB"),
-            new Instruction(1, "int-to-short vA, vB"),
-
-            // 0x90...0x9f
-            new Instruction(2, "add-int vAA, vBB, vCC"),
-            new Instruction(2, "sub-int vAA, vBB, vCC"),
-            new Instruction(2, "mul-int vAA, vBB, vCC"),
-            new Instruction(2, "div-int vAA, vBB, vCC"),
-            new Instruction(2, "rem-int vAA, vBB, vCC"),
-            new Instruction(2, "and-int vAA, vBB, vCC"),
-            new Instruction(2, "or-int vAA, vBB, vCC"),
-            new Instruction(2, "xor-int vAA, vBB, vCC"),
-            new Instruction(2, "shl-int vAA, vBB, vCC"),
-            new Instruction(2, "shr-int vAA, vBB, vCC"),
-            new Instruction(2, "ushr-int vAA, vBB, vCC"),
-            new Instruction(2, "add-long vAA, vBB, vCC"),
-            new Instruction(2, "sub-long vAA, vBB, vCC"),
-            new Instruction(2, "mul-long vAA, vBB, vCC"),
-            new Instruction(2, "div-long vAA, vBB, vCC"),
-            new Instruction(2, "rem-long vAA, vBB, vCC"),
-
-            // 0xa0...0xaf
-            new Instruction(2, "and-long vAA, vBB, vCC"),
-            new Instruction(2, "or-long vAA, vBB, vCC"),
-            new Instruction(2, "xor-long vAA, vBB, vCC"),
-            new Instruction(2, "shl-long vAA, vBB, vCC"),
-            new Instruction(2, "shr-long vAA, vBB, vCC"),
-            new Instruction(2, "ushr-long vAA, vBB, vCC"),
-            new Instruction(2, "add-float vAA, vBB, vCC"),
-            new Instruction(2, "sub-float vAA, vBB, vCC"),
-            new Instruction(2, "mul-float vAA, vBB, vCC"),
-            new Instruction(2, "div-float vAA, vBB, vCC"),
-            new Instruction(2, "rem-float vAA, vBB, vCC"),
-            new Instruction(2, "add-double vAA, vBB, vCC"),
-            new Instruction(2, "sub-double vAA, vBB, vCC"),
-            new Instruction(2, "mul-double vAA, vBB, vCC"),
-            new Instruction(2, "div-double vAA, vBB, vCC"),
-            new Instruction(2, "rem-double vAA, vBB, vCC"),
-
-            // 0xb0..0xbf
-            new Instruction(1, "add-int/2addr vA, vB"),
-            new Instruction(1, "sub-int/2addr vA, vB"),
-            new Instruction(1, "mul-int/2addr vA, vB"),
-            new Instruction(1, "div-int/2addr vA, vB"),
-            new Instruction(1, "rem-int/2addr vA, vB"),
-            new Instruction(1, "and-int/2addr vA, vB"),
-            new Instruction(1, "or-int/2addr vA, vB"),
-            new Instruction(1, "xor-int/2addr vA, vB"),
-            new Instruction(1, "shl-int/2addr vA, vB"),
-            new Instruction(1, "shr-int/2addr vA, vB"),
-            new Instruction(1, "ushr-int/2addr vA, vB"),
-            new Instruction(1, "add-long/2addr vA, vB"),
-            new Instruction(1, "sub-long/2addr vA, vB"),
-            new Instruction(1, "mul-long/2addr vA, vB"),
-            new Instruction(1, "div-long/2addr vA, vB"),
-            new Instruction(1, "rem-long/2addr vA, vB"),
-
-            // 0xc0...0xcf
-            new Instruction(1, "and-long/2addr vA, vB"),
-            new Instruction(1, "or-long/2addr vA, vB"),
-            new Instruction(1, "xor-long/2addr vA, vB"),
-            new Instruction(1, "shl-long/2addr vA, vB"),
-            new Instruction(1, "shr-long/2addr vA, vB"),
-            new Instruction(1, "ushr-long/2addr vA, vB"),
-            new Instruction(1, "add-float/2addr vA, vB"),
-            new Instruction(1, "sub-float/2addr vA, vB"),
-            new Instruction(1, "mul-float/2addr vA, vB"),
-            new Instruction(1, "div-float/2addr vA, vB"),
-            new Instruction(1, "rem-float/2addr vA, vB"),
-            new Instruction(1, "add-double/2addr vA, vB"),
-            new Instruction(1, "sub-double/2addr vA, vB"),
-            new Instruction(1, "mul-double/2addr vA, vB"),
-            new Instruction(1, "div-double/2addr vA, vB"),
-            new Instruction(1, "rem-double/2addr vA, vB"),
-
-            // 0xd0...0xdf
-            new Instruction(2, "add-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "rsub-int (reverse subtract) vA, vB, #+CCCC"),
-            new Instruction(2, "mul-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "div-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "rem-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "and-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "or-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "xor-int/lit16 vA, vB, #+CCCC"),
-            new Instruction(2, "add-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "rsub-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "mul-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "div-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "rem-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "and-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "or-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "xor-int/lit8 vAA, vBB, #+CC"),
-
-            // 0xe0...0xef
-            new Instruction(2, "shl-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "shr-int/lit8 vAA, vBB, #+CC"),
-            new Instruction(2, "ushr-int/lit8 vAA, vBB, #+CC"),
-    };
-
     private final IndexMap indexMap;
+    private final CodeReader reader;
 
     public InstructionTransformer(IndexMap indexMap) {
         this.indexMap = indexMap;
+        this.reader = new CodeReader();
+        this.reader.setJumboStringVisitor(new JumboStringInstruction());
+        this.reader.setStringVisitor(new StringInstruction());
+        this.reader.setTypeVisitor(new TypeVisitor());
+        this.reader.setFieldVisitor(new FieldVisitor());
+        this.reader.setMethodVisitor(new MethodVisitor());
     }
 
     public short[] transform(short[] instructions) throws DexException {
         instructions = instructions.clone();
-        BitSet skippedInstructions = new BitSet();
-
-        for (int i = 0; i < instructions.length; ) {
-            if (skippedInstructions.get(i)) {
-                i++;
-                continue;
-            }
-
-            int index = instructions[i] & 0xFF;
-            if (index < 0 || index >= INSTRUCTIONS.length) {
-                throw new DexException("Unhandled instruction at " + i
-                        + ": " + Integer.toHexString(index));
-            }
-
-            Instruction instruction = INSTRUCTIONS[index];
-            instruction.transform(instructions, i, indexMap, skippedInstructions);
-            i += instruction.codeUnits;
-        }
-
+        reader.visitAll(instructions);
         return instructions;
     }
 
-    private static class Instruction {
-        private final String name;
-        private final int codeUnits;
-        Instruction(int codeUnits, String name) {
-            this.name = name;
-            this.codeUnits = codeUnits;
-        }
-
-        public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {}
-
-        @Override public String toString() {
-            return name;
-        }
-    }
-
-    private static class UnusedInstruction extends Instruction {
-        UnusedInstruction() {
-            super(1, "unused");
-        }
-    }
-
-    private static class StringInstruction extends Instruction {
-        StringInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
+    private class StringInstruction implements CodeReader.Visitor {
+        public void visit(CodeReader.Instruction instruction, short[] instructions, int i) {
             int stringIndex = instructions[i + 1] & 0xFFFF;
             int mappedIndex = indexMap.adjustString(stringIndex);
             if (mappedIndex > 0xFFFF) {
@@ -350,103 +50,32 @@
         }
     }
 
-    private static class JumboStringInstruction extends Instruction {
-        JumboStringInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
+    private class JumboStringInstruction implements CodeReader.Visitor {
+        public void visit(CodeReader.Instruction instruction, short[] instructions, int i) {
             throw new UnsupportedOperationException("Jumbo strings not implemented. "
                     + "Due to a lack of dex files requiring jumbo strings, this class doesn't "
                     + "bother to support jumbo strings!");
         }
     }
 
-    private static class FieldInstruction extends Instruction {
-        FieldInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
+    private class FieldVisitor implements CodeReader.Visitor {
+        public void visit(CodeReader.Instruction instruction, short[] instructions, int i) {
             short field = instructions[i + 1];
-            instructions[i + 1] = (short) indexMap.adjustField(field);
+            instructions[i + 1] = indexMap.adjustField(field);
         }
     }
 
-    private static class TypeInstruction extends Instruction {
-        TypeInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
+    private class TypeVisitor implements CodeReader.Visitor {
+        public void visit(CodeReader.Instruction instruction, short[] instructions, int i) {
             short type = instructions[i + 1];
-            instructions[i + 1] = (short) indexMap.adjustType(type);
+            instructions[i + 1] = indexMap.adjustType(type);
         }
     }
 
-    private static class MethodInstruction extends Instruction {
-        MethodInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
+    private class MethodVisitor implements CodeReader.Visitor {
+        public void visit(CodeReader.Instruction instruction, short[] instructions, int i) {
             short method = instructions[i + 1];
-            instructions[i + 1] = (short) indexMap.adjustMethod(method);
-        }
-    }
-
-    private static class PackedSwitchInstruction extends Instruction {
-        public PackedSwitchInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
-            int offset = (instructions[i + 1] & 0xFFFF)
-                    + ((instructions[i + 2] & 0xFFFF) << 16);
-            if (instructions[i + offset] != 0x100) {
-                throw new DexException("Expected packed-switch pseudo-opcode but was 0x"
-                        + Integer.toHexString(instructions[i + offset]));
-            }
-            short size = instructions[i + offset + 1];
-            skippedInstructions.set(i + offset, i + offset + 4 + (size * 2));
-        }
-    }
-
-    private static class SparseSwitchInstruction extends Instruction {
-        public SparseSwitchInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
-            int offset = (instructions[i + 1] & 0xFFFF)
-                    + ((instructions[i + 2] & 0xFFFF) << 16);
-            if (instructions[i + offset] != 0x200) {
-                throw new DexException("Expected sparse-switch pseudo-opcode but was 0x"
-                        + Integer.toHexString(instructions[i + offset]));
-            }
-            short size = instructions[i + offset + 1];
-            skippedInstructions.set(i + offset, i + offset + 2 + (size * 4));
-        }
-    }
-
-    private static class FillArrayInstruction extends Instruction {
-        public FillArrayInstruction(int codeUnits, String name) {
-            super(codeUnits, name);
-        }
-        @Override public void transform(short[] instructions, int i, IndexMap indexMap,
-                BitSet skippedInstructions) throws DexException {
-            int offset = (instructions[i + 1] & 0xFFFF)
-                    + ((instructions[i + 2] & 0xFFFF) << 16);
-            if (instructions[i + offset] != 0x300) {
-                throw new DexException("Expected fill-array-data pseudo-opcode but was 0x"
-                        + Integer.toHexString(instructions[i + offset]));
-            }
-            int bytesPerElement = instructions[i + offset + 1];
-            int size = (instructions[i + offset + 2] & 0xFFFF)
-                    + ((instructions[i + offset + 3] & 0xFFFF) << 4);
-            int totalBytes = size * bytesPerElement;
-            int totalShorts = (totalBytes + 1) / 2; // round up!
-            skippedInstructions.set(i + offset, i + offset + 4 + totalShorts);
+            instructions[i + 1] = indexMap.adjustMethod(method);
         }
     }
 }