content: /*
   * 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.taobao.atlas.dexmerge.dx.io.instructions;
  
  import com.taobao.atlas.dex.DexException2;
  import com.taobao.atlas.dexmerge.dx.io.IndexType;
  import com.taobao.atlas.dexmerge.dx.io.OpcodeInfo;
  import com.taobao.atlas.dexmerge.dx.io.Opcodes;
  import com.taobao.atlas.dexmerge.dx.util.Hex;
  
  import java.io.EOFException;
  
  /**
   * Representation of an instruction format, which knows how to decode into
   * and encode from instances of {@link DecodedInstruction}.
   */
  public enum InstructionCodec {
      FORMAT_00X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return new ZeroRegisterDecodedInstruction(
                      this, opcodeUnit, 0, null,
                      0, 0L);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(insn.getOpcodeUnit());
          }
      },
  
      FORMAT_10X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int literal = byte1(opcodeUnit); // should be zero
              return new ZeroRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(insn.getOpcodeUnit());
          }
      },
  
      FORMAT_12X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int b = nibble3(opcodeUnit);
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, 0L,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcodeUnit(),
                               makeByte(insn.getA(), insn.getB())));
          }
      },
  
      FORMAT_11N() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int literal = (nibble3(opcodeUnit) << 28) >> 28; // sign-extend
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcodeUnit(),
                               makeByte(insn.getA(), insn.getLiteralNibble())));
          }
      },
  
      FORMAT_11X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, 0L,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(codeUnit(insn.getOpcode(), insn.getA()));
          }
      },
  
      FORMAT_10T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int target = (byte) byte1(opcodeUnit); // sign-extend
              return new ZeroRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      baseAddress + target, 0L);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              int relativeTarget = insn.getTargetByte(out.cursor());
              out.write(codeUnit(insn.getOpcode(), relativeTarget));
          }
      },
  
      FORMAT_20T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int literal = byte1(opcodeUnit); // should be zero
              int target = (short) in.read(); // sign-extend
              return new ZeroRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      baseAddress + target, literal);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              short relativeTarget = insn.getTargetUnit(out.cursor());
              out.write(insn.getOpcodeUnit(), relativeTarget);
          }
      },
  
      FORMAT_20BC() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              // Note: We use the literal field to hold the decoded AA value.
              int opcode = byte0(opcodeUnit);
              int literal = byte1(opcodeUnit);
              int index = in.read();
              return new ZeroRegisterDecodedInstruction(
                      this, opcode, index, IndexType.VARIES,
                      0, literal);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getLiteralByte()),
                      insn.getIndexUnit());
          }
      },
  
      FORMAT_22X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int b = in.read();
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, 0L,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      insn.getBUnit());
          }
      },
  
      FORMAT_21T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int target = (short) in.read(); // sign-extend
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      baseAddress + target, 0L,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              short relativeTarget = insn.getTargetUnit(out.cursor());
              out.write(codeUnit(insn.getOpcode(), insn.getA()), relativeTarget);
          }
      },
  
      FORMAT_21S() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int literal = (short) in.read(); // sign-extend
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      insn.getLiteralUnit());
          }
      },
  
      FORMAT_21H() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              long literal = (short) in.read(); // sign-extend
  
              /*
               * Format 21h decodes differently depending on the opcode,
               * because the "signed hat" might represent either a 32-
               * or 64- bit value.
               */
              literal <<= (opcode == Opcodes.CONST_HIGH16) ? 16 : 48;
  
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              // See above.
              int opcode = insn.getOpcode();
              int shift = (opcode == Opcodes.CONST_HIGH16) ? 16 : 48;
              short literal = (short) (insn.getLiteral() >> shift);
  
              out.write(codeUnit(opcode, insn.getA()), literal);
          }
      },
  
      FORMAT_21C() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int index = in.read();
              IndexType indexType = OpcodeInfo.getIndexType(opcode);
              return new OneRegisterDecodedInstruction(
                      this, opcode, index, indexType,
                      0, 0L,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      insn.getIndexUnit());
          }
      },
  
      FORMAT_23X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int bc = in.read();
              int b = byte0(bc);
              int c = byte1(bc);
              return new ThreeRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, 0L,
                      a, b, c);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      codeUnit(insn.getB(), insn.getC()));
          }
      },
  
      FORMAT_22B() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int bc = in.read();
              int b = byte0(bc);
              int literal = (byte) byte1(bc); // sign-extend
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      codeUnit(insn.getB(),
                               insn.getLiteralByte()));
          }
      },
  
      FORMAT_22T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int b = nibble3(opcodeUnit);
              int target = (short) in.read(); // sign-extend
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      baseAddress + target, 0L,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              short relativeTarget = insn.getTargetUnit(out.cursor());
              out.write(
                      codeUnit(insn.getOpcode(),
                               makeByte(insn.getA(), insn.getB())),
                      relativeTarget);
          }
      },
  
      FORMAT_22S() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int b = nibble3(opcodeUnit);
              int literal = (short) in.read(); // sign-extend
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(),
                               makeByte(insn.getA(), insn.getB())),
                      insn.getLiteralUnit());
          }
      },
  
      FORMAT_22C() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int b = nibble3(opcodeUnit);
              int index = in.read();
              IndexType indexType = OpcodeInfo.getIndexType(opcode);
              return new TwoRegisterDecodedInstruction(
                      this, opcode, index, indexType,
                      0, 0L,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(),
                               makeByte(insn.getA(), insn.getB())),
                      insn.getIndexUnit());
          }
      },
  
      FORMAT_22CS() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = nibble2(opcodeUnit);
              int b = nibble3(opcodeUnit);
              int index = in.read();
              return new TwoRegisterDecodedInstruction(
                      this, opcode, index, IndexType.FIELD_OFFSET,
                      0, 0L,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(
                      codeUnit(insn.getOpcode(),
                               makeByte(insn.getA(), insn.getB())),
                      insn.getIndexUnit());
          }
      },
  
      FORMAT_30T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int literal = byte1(opcodeUnit); // should be zero
              int target = in.readInt();
              return new ZeroRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      baseAddress + target, literal);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              int relativeTarget = insn.getTarget(out.cursor());
              out.write(insn.getOpcodeUnit(),
                      unit0(relativeTarget), unit1(relativeTarget));
          }
      },
  
      FORMAT_32X() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int literal = byte1(opcodeUnit); // should be zero
              int a = in.read();
              int b = in.read();
              return new TwoRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a, b);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              out.write(insn.getOpcodeUnit(), insn.getAUnit(), insn.getBUnit());
          }
      },
  
      FORMAT_31I() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int literal = in.readInt();
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              int literal = insn.getLiteralInt();
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      unit0(literal),
                      unit1(literal));
          }
      },
  
      FORMAT_31T() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.cursor() - 1;
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int target = baseAddress + in.readInt();
  
              /*
               * Switch instructions need to "forward" their addresses to their
               * payload target instructions.
               */
              switch (opcode) {
                  case Opcodes.PACKED_SWITCH:
                  case Opcodes.SPARSE_SWITCH: {
                      in.setBaseAddress(target, baseAddress);
                      break;
                  }
              }
  
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      target, 0L,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              int relativeTarget = insn.getTarget(out.cursor());
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      unit0(relativeTarget), unit1(relativeTarget));
          }
      },
  
      FORMAT_31C() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              int index = in.readInt();
              IndexType indexType = OpcodeInfo.getIndexType(opcode);
              return new OneRegisterDecodedInstruction(
                      this, opcode, index, indexType,
                      0, 0L,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              int index = insn.getIndex();
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      unit0(index),
                      unit1(index));
          }
      },
  
      FORMAT_35C() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterList(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterList(insn, out);
          }
      },
  
      FORMAT_35MS() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterList(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterList(insn, out);
          }
      },
  
      FORMAT_35MI() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterList(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterList(insn, out);
          }
      },
  
      FORMAT_3RC() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterRange(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterRange(insn, out);
          }
      },
  
      FORMAT_3RMS() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterRange(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterRange(insn, out);
          }
      },
  
      FORMAT_3RMI() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              return decodeRegisterRange(this, opcodeUnit, in);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              encodeRegisterRange(insn, out);
          }
      },
  
      FORMAT_51L() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int opcode = byte0(opcodeUnit);
              int a = byte1(opcodeUnit);
              long literal = in.readLong();
              return new OneRegisterDecodedInstruction(
                      this, opcode, 0, null,
                      0, literal,
                      a);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              long literal = insn.getLiteral();
              out.write(
                      codeUnit(insn.getOpcode(), insn.getA()),
                      unit0(literal),
                      unit1(literal),
                      unit2(literal),
                      unit3(literal));
          }
      },
  
      FORMAT_PACKED_SWITCH_PAYLOAD() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.baseAddressForCursor() - 1; // already read opcode
              int size = in.read();
              int firstKey = in.readInt();
              int[] targets = new int[size];
  
              for (int i = 0; i < size; i++) {
                  targets[i] = baseAddress + in.readInt();
              }
  
              return new PackedSwitchPayloadDecodedInstruction(
                      this, opcodeUnit, firstKey, targets);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              PackedSwitchPayloadDecodedInstruction payload =
                  (PackedSwitchPayloadDecodedInstruction) insn;
              int[] targets = payload.getTargets();
              int baseAddress = out.baseAddressForCursor();
  
              out.write(payload.getOpcodeUnit());
              out.write(asUnsignedUnit(targets.length));
              out.writeInt(payload.getFirstKey());
  
              for (int target : targets) {
                  out.writeInt(target - baseAddress);
              }
          }
      },
  
      FORMAT_SPARSE_SWITCH_PAYLOAD() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int baseAddress = in.baseAddressForCursor() - 1; // already read opcode
              int size = in.read();
              int[] keys = new int[size];
              int[] targets = new int[size];
  
              for (int i = 0; i < size; i++) {
                  keys[i] = in.readInt();
              }
  
              for (int i = 0; i < size; i++) {
                  targets[i] = baseAddress + in.readInt();
              }
  
              return new SparseSwitchPayloadDecodedInstruction(
                      this, opcodeUnit, keys, targets);
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              SparseSwitchPayloadDecodedInstruction payload =
                  (SparseSwitchPayloadDecodedInstruction) insn;
              int[] keys = payload.getKeys();
              int[] targets = payload.getTargets();
              int baseAddress = out.baseAddressForCursor();
  
              out.write(payload.getOpcodeUnit());
              out.write(asUnsignedUnit(targets.length));
  
              for (int key : keys) {
                  out.writeInt(key);
              }
  
              for (int target : targets) {
                  out.writeInt(target - baseAddress);
              }
          }
      },
  
      FORMAT_FILL_ARRAY_DATA_PAYLOAD() {
          @Override public DecodedInstruction decode(int opcodeUnit,
                  CodeInput in) throws EOFException {
              int elementWidth = in.read();
              int size = in.readInt();
  
              switch (elementWidth) {
                  case 1: {
                      byte[] array = new byte[size];
                      boolean even = true;
                      for (int i = 0, value = 0; i < size; i++, even = !even) {
                          if (even) {
                              value = in.read();
                          }
                          array[i] = (byte) (value & 0xff);
                          value >>= 8;
                      }
                      return new FillArrayDataPayloadDecodedInstruction(
                              this, opcodeUnit, array);
                  }
                  case 2: {
                      short[] array = new short[size];
                      for (int i = 0; i < size; i++) {
                          array[i] = (short) in.read();
                      }
                      return new FillArrayDataPayloadDecodedInstruction(
                              this, opcodeUnit, array);
                  }
                  case 4: {
                      int[] array = new int[size];
                      for (int i = 0; i < size; i++) {
                          array[i] = in.readInt();
                      }
                      return new FillArrayDataPayloadDecodedInstruction(
                              this, opcodeUnit, array);
                  }
                  case 8: {
                      long[] array = new long[size];
                      for (int i = 0; i < size; i++) {
                          array[i] = in.readLong();
                      }
                      return new FillArrayDataPayloadDecodedInstruction(
                              this, opcodeUnit, array);
                  }
              }
  
              throw new DexException2("bogus element_width: "
                      + Hex.u2(elementWidth));
          }
  
          @Override public void encode(DecodedInstruction insn, CodeOutput out) {
              FillArrayDataPayloadDecodedInstruction payload =
                  (FillArrayDataPayloadDecodedInstruction) insn;
              short elementWidth = payload.getElementWidthUnit();
              Object data = payload.getData();
  
              out.write(payload.getOpcodeUnit());
              out.write(elementWidth);
              out.writeInt(payload.getSize());
  
              switch (elementWidth) {
                  case 1: out.write((byte[]) data);  break;
                  case 2: out.write((short[]) data); break;
                  case 4: out.write((int[]) data);   break;
                  case 8: out.write((long[]) data);  break;
                  default: {
                      throw new DexException2("bogus element_width: "
                              + Hex.u2(elementWidth));
                  }
              }
          }
      };
  
      /**
       * Decodes an instruction specified by the given opcode unit, reading
       * any required additional code units from the given input source.
       */
      public abstract DecodedInstruction decode(int opcodeUnit, CodeInput in)
          throws EOFException;
  
      /**
       * Encodes the given instruction.
       */
      public abstract void encode(DecodedInstruction insn, CodeOutput out);
  
      /**
       * Helper method that decodes any of the register-list formats.
       */
      private static DecodedInstruction decodeRegisterList(
              InstructionCodec format, int opcodeUnit, CodeInput in)
              throws EOFException {
          int opcode = byte0(opcodeUnit);
          int e = nibble2(opcodeUnit);
          int registerCount = nibble3(opcodeUnit);
          int index = in.read();
          int abcd = in.read();
          int a = nibble0(abcd);
          int b = nibble1(abcd);
          int c = nibble2(abcd);
          int d = nibble3(abcd);
          IndexType indexType = OpcodeInfo.getIndexType(opcode);
  
          // TODO: Having to switch like this is less than ideal.
          switch (registerCount) {
              case 0:
                  return new ZeroRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L);
              case 1:
                  return new OneRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L,
                          a);
              case 2:
                  return new TwoRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L,
                          a, b);
              case 3:
                  return new ThreeRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L,
                          a, b, c);
              case 4:
                  return new FourRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L,
                          a, b, c, d);
              case 5:
                  return new FiveRegisterDecodedInstruction(
                          format, opcode, index, indexType,
                          0, 0L,
                          a, b, c, d, e);
          }
  
          throw new DexException2("bogus registerCount: "
                  + Hex.uNibble(registerCount));
      }
  
      /**
       * Helper method that encodes any of the register-list formats.
       */
      private static void encodeRegisterList(DecodedInstruction insn,
              CodeOutput out) {
          out.write(codeUnit(insn.getOpcode(),
                          makeByte(insn.getE(), insn.getRegisterCount())),
                  insn.getIndexUnit(),
                  codeUnit(insn.getA(), insn.getB(), insn.getC(), insn.getD()));
      }
  
      /**
       * Helper method that decodes any of the three-unit register-range formats.
       */
      private static DecodedInstruction decodeRegisterRange(
              InstructionCodec format, int opcodeUnit, CodeInput in)
              throws EOFException {
          int opcode = byte0(opcodeUnit);
          int registerCount = byte1(opcodeUnit);
          int index = in.read();
          int a = in.read();
          IndexType indexType = OpcodeInfo.getIndexType(opcode);
          return new RegisterRangeDecodedInstruction(
                  format, opcode, index, indexType,
                  0, 0L,
                  a, registerCount);
      }
  
      /**
       * Helper method that encodes any of the three-unit register-range formats.
       */
      private static void encodeRegisterRange(DecodedInstruction insn,
              CodeOutput out) {
          out.write(codeUnit(insn.getOpcode(), insn.getRegisterCount()),
                  insn.getIndexUnit(),
                  insn.getAUnit());
      }
  
      private static short codeUnit(int lowByte, int highByte) {
          if ((lowByte & ~0xff) != 0) {
              throw new IllegalArgumentException("bogus lowByte");
          }
  
          if ((highByte & ~0xff) != 0) {
              throw new IllegalArgumentException("bogus highByte");
          }
  
          return (short) (lowByte | (highByte << 8));
      }
  
      private static short codeUnit(int nibble0, int nibble1, int nibble2,
              int nibble3) {
          if ((nibble0 & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus nibble0");
          }
  
          if ((nibble1 & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus nibble1");
          }
  
          if ((nibble2 & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus nibble2");
          }
  
          if ((nibble3 & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus nibble3");
          }
  
          return (short) (nibble0 | (nibble1 << 4)
                  | (nibble2 << 8) | (nibble3 << 12));
      }
  
      private static int makeByte(int lowNibble, int highNibble) {
          if ((lowNibble & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus lowNibble");
          }
  
          if ((highNibble & ~0xf) != 0) {
              throw new IllegalArgumentException("bogus highNibble");
          }
  
          return lowNibble | (highNibble << 4);
      }
  
      private static short asUnsignedUnit(int value) {
          if ((value & ~0xffff) != 0) {
              throw new IllegalArgumentException("bogus unsigned code unit");
          }
  
          return (short) value;
      }
  
      private static short unit0(int value) {
          return (short) value;
      }
  
      private static short unit1(int value) {
          return (short) (value >> 16);
      }
  
      private static short unit0(long value) {
          return (short) value;
      }
  
      private static short unit1(long value) {
          return (short) (value >> 16);
      }
  
      private static short unit2(long value) {
          return (short) (value >> 32);
      }
  
      private static short unit3(long value) {
          return (short) (value >> 48);
      }
  
      private static int byte0(int value) {
          return value & 0xff;
      }
  
      private static int byte1(int value) {
          return (value >> 8) & 0xff;
      }
  
      private static int byte2(int value) {
          return (value >> 16) & 0xff;
      }
  
      private static int byte3(int value) {
          return value >>> 24;
      }
  
      private static int nibble0(int value) {
          return value & 0xf;
      }
  
      private static int nibble1(int value) {
          return (value >> 4) & 0xf;
      }
  
      private static int nibble2(int value) {
          return (value >> 8) & 0xf;
      }
  
      private static int nibble3(int value) {
          return (value >> 12) & 0xf;
      }
  }
  