/* FJBG -- Fast Java Bytecode Generator
 * Copyright 2002-2013 LAMP/EPFL
 * @author  Michel Schinz
 */

package ch.epfl.lamp.fjbg;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;

import ch.epfl.lamp.util.ByteArray;

/**
 * List of instructions, to which Java byte-code instructions can be added.
 *
 * @author Michel Schinz, Thomas Friedli
 * @version 1.0
 */

public class JCode {
    protected boolean frozen = false;

    public static int MAX_CODE_SIZE = 65535;

    protected final FJBGContext context;
    protected final JMethod owner;

    protected final ByteArray codeArray;

    protected final LinkedList/*<ExceptionHandler>*/ exceptionHandlers =
        new LinkedList();

    protected final JConstantPool pool;

    protected final ArrayList/*<OffsetToPatch>*/ offsetToPatch =
        new ArrayList();

    protected static int UNKNOWN_STACK_SIZE = Integer.MIN_VALUE;
    protected int maxStackSize = UNKNOWN_STACK_SIZE;
    protected int[] stackProduction = null;
    protected int[] stackSizes;

    protected JCode(FJBGContext context, JClass clazz, JMethod owner) {
        this.context = context;
        this.pool = clazz.getConstantPool();
        this.owner = owner;
        this.codeArray = new ByteArray();
    }

    protected JCode(FJBGContext context,
                    JClass clazz,
                    JMethod owner,
                    DataInputStream stream)
        throws IOException {
        this.context = context;
        this.pool = clazz.getConstantPool();
        this.owner = owner;
        owner.setCode(this);
        int size = stream.readInt();
        if (size > MAX_CODE_SIZE) // section 4.10
            throw new Error("code size must be less than " + MAX_CODE_SIZE + ": " + size);
        this.codeArray = new ByteArray(stream, size);
    }

    /**
     * Gets the program counter, which is defined as the address of the
     * next instruction.
     * @return The int representing the value of the program counter
     */
    public int getPC() {
        return codeArray.getSize();
    }

    /**
     * Gets the size of the code
     * @return The number of bytes of the code
     */
    public int getSize() {
        return codeArray.getSize();
    }

    /**
     * Gets the method to which the code belongs
     * @return The method to which the code belongs
     */
    public JMethod getOwner() {
        return owner;
    }

    // Stack size
    public int getMaxStackSize() {
        if (maxStackSize == UNKNOWN_STACK_SIZE)
            maxStackSize = computeMaxStackSize();
        return maxStackSize;
    }

    // Freezing
    //////////////////////////////////////////////////////////////////////

    public static class CodeSizeTooBigException extends OffsetTooBigException {
        public int codeSize;

        public CodeSizeTooBigException(int size) {
          codeSize = size;
        }
    }

    public void freeze() throws OffsetTooBigException {
        assert !frozen;

        if (getSize() > MAX_CODE_SIZE) throw new CodeSizeTooBigException(getSize());

        patchAllOffset();
        codeArray.freeze();
        frozen = true;
    }

    // Attributes
    //////////////////////////////////////////////////////////////////////

    protected final LinkedList/*<JAttribute>*/ attributes = new LinkedList();

    public void addAttribute(JAttribute attr) {
        attributes.add(attr);
    }

    public List/*<JAttribute>*/ getAttributes() {
        return attributes;
    }

    // Emitting code
    //////////////////////////////////////////////////////////////////////

    public void emit(JOpcode opcode) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
    }

    public void emitNOP() { emit(JOpcode.NOP); }

    // Constant loading.
    public void emitACONST_NULL() { emit(JOpcode.ACONST_NULL); }
    public void emitICONST_M1() { emit(JOpcode.ICONST_M1); }
    public void emitICONST_0() { emit(JOpcode.ICONST_0); }
    public void emitICONST_1() { emit(JOpcode.ICONST_1); }
    public void emitICONST_2() { emit(JOpcode.ICONST_2); }
    public void emitICONST_3() { emit(JOpcode.ICONST_3); }
    public void emitICONST_4() { emit(JOpcode.ICONST_4); }
    public void emitICONST_5() { emit(JOpcode.ICONST_5); }
    public void emitLCONST_0() { emit(JOpcode.LCONST_0); }
    public void emitLCONST_1() { emit(JOpcode.LCONST_1); }
    public void emitFCONST_0() { emit(JOpcode.FCONST_0); }
    public void emitFCONST_1() { emit(JOpcode.FCONST_1); }
    public void emitFCONST_2() { emit(JOpcode.FCONST_2); }
    public void emitDCONST_0() { emit(JOpcode.DCONST_0); }
    public void emitDCONST_1() { emit(JOpcode.DCONST_1); }

    public void emitBIPUSH(int b) { emitU1(JOpcode.BIPUSH, b); }
    public void emitSIPUSH(int s) { emitU2(JOpcode.SIPUSH, s); }
    public void emitLDC(int value) {
        emitU1(JOpcode.LDC, pool.addInteger(value));
    }
    public void emitLDC(float value) {
        emitU1(JOpcode.LDC, pool.addFloat(value));
    }
    public void emitLDC(String value) {
        emitU1(JOpcode.LDC, pool.addString(value));
    }
    public void emitLDC_W(int value) {
        emitU1(JOpcode.LDC_W, pool.addInteger(value));
    }
    public void emitLDC_W(float value) {
        emitU1(JOpcode.LDC_W, pool.addFloat(value));
    }
    public void emitLDC_W(String value) {
        emitU1(JOpcode.LDC_W, pool.addString(value));
    }
    public void emitLDC2_W(long value) {
        emitU2(JOpcode.LDC2_W, pool.addLong(value));
    }
    public void emitLDC2_W(double value) {
        emitU2(JOpcode.LDC2_W, pool.addDouble(value));
    }

    // Loading variables.
    public void emitILOAD(int index) { emitU1(JOpcode.ILOAD, index); }
    public void emitLLOAD(int index) { emitU1(JOpcode.LLOAD, index); }
    public void emitFLOAD(int index) { emitU1(JOpcode.FLOAD, index); }
    public void emitDLOAD(int index) { emitU1(JOpcode.DLOAD, index); }
    public void emitALOAD(int index) { emitU1(JOpcode.ALOAD, index); }

    public void emitILOAD_0() { emit(JOpcode.ILOAD_0); }
    public void emitILOAD_1() { emit(JOpcode.ILOAD_1); }
    public void emitILOAD_2() { emit(JOpcode.ILOAD_2); }
    public void emitILOAD_3() { emit(JOpcode.ILOAD_3); }
    public void emitLLOAD_0() { emit(JOpcode.LLOAD_0); }
    public void emitLLOAD_1() { emit(JOpcode.LLOAD_1); }
    public void emitLLOAD_2() { emit(JOpcode.LLOAD_2); }
    public void emitLLOAD_3() { emit(JOpcode.LLOAD_3); }
    public void emitFLOAD_0() { emit(JOpcode.FLOAD_0); }
    public void emitFLOAD_1() { emit(JOpcode.FLOAD_1); }
    public void emitFLOAD_2() { emit(JOpcode.FLOAD_2); }
    public void emitFLOAD_3() { emit(JOpcode.FLOAD_3); }
    public void emitDLOAD_0() { emit(JOpcode.DLOAD_0); }
    public void emitDLOAD_1() { emit(JOpcode.DLOAD_1); }
    public void emitDLOAD_2() { emit(JOpcode.DLOAD_2); }
    public void emitDLOAD_3() { emit(JOpcode.DLOAD_3); }
    public void emitALOAD_0() { emit(JOpcode.ALOAD_0); }
    public void emitALOAD_1() { emit(JOpcode.ALOAD_1); }
    public void emitALOAD_2() { emit(JOpcode.ALOAD_2); }
    public void emitALOAD_3() { emit(JOpcode.ALOAD_3); }

    public void emitIALOAD() { emit(JOpcode.IALOAD); }
    public void emitLALOAD() { emit(JOpcode.LALOAD); }
    public void emitFALOAD() { emit(JOpcode.FALOAD); }
    public void emitDALOAD() { emit(JOpcode.DALOAD); }
    public void emitAALOAD() { emit(JOpcode.AALOAD); }
    public void emitBALOAD() { emit(JOpcode.BALOAD); }
    public void emitCALOAD() { emit(JOpcode.CALOAD); }
    public void emitSALOAD() { emit(JOpcode.SALOAD); }

    // Storing variables.
    public void emitISTORE(int index) { emitU1(JOpcode.ISTORE, index); }
    public void emitLSTORE(int index) { emitU1(JOpcode.LSTORE, index); }
    public void emitFSTORE(int index) { emitU1(JOpcode.FSTORE, index); }
    public void emitDSTORE(int index) { emitU1(JOpcode.DSTORE, index); }
    public void emitASTORE(int index) { emitU1(JOpcode.ASTORE, index); }

    public void emitISTORE_0() { emit(JOpcode.ISTORE_0); }
    public void emitISTORE_1() { emit(JOpcode.ISTORE_1); }
    public void emitISTORE_2() { emit(JOpcode.ISTORE_2); }
    public void emitISTORE_3() { emit(JOpcode.ISTORE_3); }
    public void emitLSTORE_0() { emit(JOpcode.LSTORE_0); }
    public void emitLSTORE_1() { emit(JOpcode.LSTORE_1); }
    public void emitLSTORE_2() { emit(JOpcode.LSTORE_2); }
    public void emitLSTORE_3() { emit(JOpcode.LSTORE_3); }
    public void emitFSTORE_0() { emit(JOpcode.FSTORE_0); }
    public void emitFSTORE_1() { emit(JOpcode.FSTORE_1); }
    public void emitFSTORE_2() { emit(JOpcode.FSTORE_2); }
    public void emitFSTORE_3() { emit(JOpcode.FSTORE_3); }
    public void emitDSTORE_0() { emit(JOpcode.DSTORE_0); }
    public void emitDSTORE_1() { emit(JOpcode.DSTORE_1); }
    public void emitDSTORE_2() { emit(JOpcode.DSTORE_2); }
    public void emitDSTORE_3() { emit(JOpcode.DSTORE_3); }
    public void emitASTORE_0() { emit(JOpcode.ASTORE_0); }
    public void emitASTORE_1() { emit(JOpcode.ASTORE_1); }
    public void emitASTORE_2() { emit(JOpcode.ASTORE_2); }
    public void emitASTORE_3() { emit(JOpcode.ASTORE_3); }

    public void emitIASTORE() { emit(JOpcode.IASTORE); }
    public void emitLASTORE() { emit(JOpcode.LASTORE); }
    public void emitFASTORE() { emit(JOpcode.FASTORE); }
    public void emitDASTORE() { emit(JOpcode.DASTORE); }
    public void emitAASTORE() { emit(JOpcode.AASTORE); }
    public void emitBASTORE() { emit(JOpcode.BASTORE); }
    public void emitCASTORE() { emit(JOpcode.CASTORE); }
    public void emitSASTORE() { emit(JOpcode.SASTORE); }

    // Stack manipulation.
    public void emitPOP() { emit(JOpcode.POP); }
    public void emitPOP2() { emit(JOpcode.POP2); }
    public void emitDUP() { emit(JOpcode.DUP); }
    public void emitDUP_X1() { emit(JOpcode.DUP_X1); }
    public void emitDUP_X2() { emit(JOpcode.DUP_X2); }
    public void emitDUP2() { emit(JOpcode.DUP2); }
    public void emitDUP2_X1() { emit(JOpcode.DUP2_X1); }
    public void emitDUP2_X2() { emit(JOpcode.DUP2_X2); }
    public void emitSWAP() { emit(JOpcode.SWAP); }

    // Artithmetic and logic operations.
    public void emitIADD() { emit(JOpcode.IADD); }
    public void emitLADD() { emit(JOpcode.LADD); }
    public void emitFADD() { emit(JOpcode.FADD); }
    public void emitDADD() { emit(JOpcode.DADD); }

    public void emitISUB() { emit(JOpcode.ISUB); }
    public void emitLSUB() { emit(JOpcode.LSUB); }
    public void emitFSUB() { emit(JOpcode.FSUB); }
    public void emitDSUB() { emit(JOpcode.DSUB); }

    public void emitIMUL() { emit(JOpcode.IMUL); }
    public void emitLMUL() { emit(JOpcode.LMUL); }
    public void emitFMUL() { emit(JOpcode.FMUL); }
    public void emitDMUL() { emit(JOpcode.DMUL); }

    public void emitIDIV() { emit(JOpcode.IDIV); }
    public void emitLDIV() { emit(JOpcode.LDIV); }
    public void emitFDIV() { emit(JOpcode.FDIV); }
    public void emitDDIV() { emit(JOpcode.DDIV); }

    public void emitIREM() { emit(JOpcode.IREM); }
    public void emitLREM() { emit(JOpcode.LREM); }
    public void emitFREM() { emit(JOpcode.FREM); }
    public void emitDREM() { emit(JOpcode.DREM); }

    public void emitINEG() { emit(JOpcode.INEG); }
    public void emitLNEG() { emit(JOpcode.LNEG); }
    public void emitFNEG() { emit(JOpcode.FNEG); }
    public void emitDNEG() { emit(JOpcode.DNEG); }

    public void emitISHL() { emit(JOpcode.ISHL); }
    public void emitLSHL() { emit(JOpcode.LSHL); }

    public void emitISHR() { emit(JOpcode.ISHR); }
    public void emitLSHR() { emit(JOpcode.LSHR); }

    public void emitIUSHR() { emit(JOpcode.IUSHR); }
    public void emitLUSHR() { emit(JOpcode.LUSHR); }

    public void emitIAND() { emit(JOpcode.IAND); }
    public void emitLAND() { emit(JOpcode.LAND); }

    public void emitIOR() { emit(JOpcode.IOR); }
    public void emitLOR() { emit(JOpcode.LOR); }

    public void emitIXOR() { emit(JOpcode.IXOR); }
    public void emitLXOR() { emit(JOpcode.LXOR); }

    public void emitIINC(int index, int increment) {
        emitU1U1(JOpcode.IINC, index, increment);
    }

    // (Numeric) type conversions.
    public void emitI2L() { emit(JOpcode.I2L); }
    public void emitI2F() { emit(JOpcode.I2F); }
    public void emitI2D() { emit(JOpcode.I2D); }
    public void emitL2I() { emit(JOpcode.L2I); }
    public void emitL2F() { emit(JOpcode.L2F); }
    public void emitL2D() { emit(JOpcode.L2D); }
    public void emitF2I() { emit(JOpcode.F2I); }
    public void emitF2L() { emit(JOpcode.F2L); }
    public void emitF2D() { emit(JOpcode.F2D); }
    public void emitD2I() { emit(JOpcode.D2I); }
    public void emitD2L() { emit(JOpcode.D2L); }
    public void emitD2F() { emit(JOpcode.D2F); }
    public void emitI2B() { emit(JOpcode.I2B); }
    public void emitI2C() { emit(JOpcode.I2C); }
    public void emitI2S() { emit(JOpcode.I2S); }

    // Comparisons and tests.
    public void emitLCMP() { emit(JOpcode.LCMP); }
    public void emitFCMPL() { emit(JOpcode.FCMPL); }
    public void emitFCMPG() { emit(JOpcode.FCMPG); }
    public void emitDCMPL() { emit(JOpcode.DCMPL); }
    public void emitDCMPG() { emit(JOpcode.DCMPG); }

    protected void emitGenericIF(JOpcode opcode, Label label)
        throws OffsetTooBigException {
        emitU2(opcode, label.getOffset16(getPC() + 1, getPC()));
    }

    public void emitIFEQ(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFEQ, label);
    }
    public void emitIFEQ(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFEQ, targetPC - getPC());
    }
    public void emitIFEQ() {
        emitU2(JOpcode.IFEQ, 0);
    }

    public void emitIFNE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFNE, label);
    }
    public void emitIFNE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFNE, targetPC - getPC());
    }
    public void emitIFNE() {
        emitU2(JOpcode.IFNE, 0);
    }

    public void emitIFLT(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFLT, label);
    }
    public void emitIFLT(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFLT, targetPC - getPC());
    }
    public void emitIFLT() {
        emitU2(JOpcode.IFLT, 0);
    }

    public void emitIFGE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFGE, label);
    }
    public void emitIFGE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFGE, targetPC - getPC());
    }
    public void emitIFGE() {
        emitU2(JOpcode.IFGE, 0);
    }

    public void emitIFGT(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFGT, label);
    }
    public void emitIFGT(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFGT, targetPC - getPC());
    }
    public void emitIFGT() {
        emitU2(JOpcode.IFGT, 0);
    }

    public void emitIFLE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFLE, label);
    }
    public void emitIFLE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFLE, targetPC - getPC());
    }
    public void emitIFLE() {
        emitU2(JOpcode.IFLE, 0);
    }

    public void emitIF_ICMPEQ(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPEQ, label);
    }
    public void emitIF_ICMPEQ(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPEQ, targetPC - getPC());
    }
    public void emitIF_ICMPEQ() {
        emitU2(JOpcode.IF_ICMPEQ, 0);
    }

    public void emitIF_ICMPNE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPNE, label);
    }
    public void emitIF_ICMPNE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPNE, targetPC - getPC());
    }
    public void emitIF_ICMPNE() {
        emitU2(JOpcode.IF_ICMPNE, 0);
    }

    public void emitIF_ICMPLT(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPLT, label);
    }
    public void emitIF_ICMPLT(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPLT, targetPC - getPC());
    }
    public void emitIF_ICMPLT() {
        emitU2(JOpcode.IF_ICMPLT, 0);
    }

    public void emitIF_ICMPGE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPGE, label);
    }
    public void emitIF_ICMPGE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPGE, targetPC - getPC());
    }
    public void emitIF_ICMPGE() {
        emitU2(JOpcode.IF_ICMPGE, 0);
    }

    public void emitIF_ICMPGT(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPGT, label);
    }
    public void emitIF_ICMPGT(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPGT, targetPC - getPC());
    }
    public void emitIF_ICMPGT() {
        emitU2(JOpcode.IF_ICMPGT, 0);
    }

    public void emitIF_ICMPLE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ICMPLE, label);
    }
    public void emitIF_ICMPLE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ICMPLE, targetPC - getPC());
    }
    public void emitIF_ICMPLE() {
        emitU2(JOpcode.IF_ICMPLE, 0);
    }

    public void emitIF_ACMPEQ(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ACMPEQ, label);
    }
    public void emitIF_ACMPEQ(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ACMPEQ, targetPC - getPC());
    }
    public void emitIF_ACMPEQ() {
        emitU2(JOpcode.IF_ACMPEQ, 0);
    }

    public void emitIF_ACMPNE(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IF_ACMPNE, label);
    }
    public void emitIF_ACMPNE(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IF_ACMPNE, targetPC - getPC());
    }
    public void emitIF_ACMPNE() {
        emitU2(JOpcode.IF_ACMPNE, 0);
    }

    public void emitIFNULL(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFNULL, label);
    }
    public void emitIFNULL(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFNULL, targetPC - getPC());
    }
    public void emitIFNULL() {
        emitU2(JOpcode.IFNULL, 0);
    }

    public void emitIFNONNULL(Label label) throws OffsetTooBigException {
        emitGenericIF(JOpcode.IFNONNULL, label);
    }
    public void emitIFNONNULL(int targetPC) throws OffsetTooBigException {
        emitU2(JOpcode.IFNONNULL, targetPC - getPC());
    }
    public void emitIFNONNULL() {
        emitU2(JOpcode.IFNONNULL, 0);
    }

    public void emitGOTO(Label label) throws OffsetTooBigException {
        emitU2(JOpcode.GOTO, label.getOffset16(getPC() + 1, getPC()));
    }
    public void emitGOTO(int targetPC) throws OffsetTooBigException {
        int offset = targetPC - getPC();
        checkOffset16(offset);
        emitU2(JOpcode.GOTO, offset);
    }
    public void emitGOTO() {
        emitU2(JOpcode.GOTO, 0);
    }

    public void emitGOTO_W(Label label) {
        emitU4(JOpcode.GOTO_W, label.getOffset32(getPC() + 1, getPC()));
    }
    public void emitGOTO_W(int targetPC) {
        emitU4(JOpcode.GOTO_W, targetPC - getPC());
    }
    public void emitGOTO_W() {
        emitU4(JOpcode.GOTO_W, 0);
    }

    public void emitJSR(Label label) throws OffsetTooBigException {
        emitU2(JOpcode.JSR, label.getOffset16(getPC() + 1, getPC()));
    }
    public void emitJSR(int targetPC) {
        emitU2(JOpcode.JSR, targetPC - getPC());
    }
    public void emitJSR() {
        emitU2(JOpcode.JSR, 0);
    }

    public void emitJSR_W(Label label) {
        emitU4(JOpcode.JSR_W, label.getOffset32(getPC() + 1, getPC()));
    }
    public void emitJSR_W(int targetPC) {
        emitU4(JOpcode.JSR_W, targetPC - getPC());
    }
    public void emitJSR_W() {
        emitU4(JOpcode.JSR_W, 0);
    }

    /*
    public void emitRET(Label label) throws OffsetTooBigException {
        emitU2(JOpcode.RET, label.getOffset16(getPC() + 1, getPC()));
    }
    public void emitRET(int targetPC) {
        emitU1(JOpcode.RET, targetPC);
    }
    public void emitRET() {
        emitU1(JOpcode.RET, 0);
    }
    */

    public void emitRET(int index) {
        emitU1(JOpcode.RET, index);
    }

    public void emitRET(JLocalVariable var) {
        emitRET(var.getIndex());
    }

    public void emitTABLESWITCH(int[] keys,
                     Label[] branches,
                     Label defaultBranch) {
        assert keys.length == branches.length;

        int low = keys[0], high = keys[keys.length - 1];
        int instrPC = getPC();

        setStackProduction(instrPC, JOpcode.TABLESWITCH);
        codeArray.addU1(JOpcode.cTABLESWITCH);
        while (getPC() % 4 != 0) codeArray.addU1(0);

        codeArray.addU4(defaultBranch.getOffset32(getPC(), instrPC));
        codeArray.addU4(low);
        codeArray.addU4(high);
        for (int i = 0; i < branches.length; i++) {
            assert keys[i] == low + i;
            codeArray.addU4(branches[i].getOffset32(getPC(), instrPC));
        }
    }

    public void emitLOOKUPSWITCH(int[] keys,
                     Label[] branches,
                     Label defaultBranch) {
        assert keys.length == branches.length;

        int instrPC = getPC();
        setStackProduction(getPC(), JOpcode.LOOKUPSWITCH);
        codeArray.addU1(JOpcode.cLOOKUPSWITCH);
        while (getPC() % 4 != 0) codeArray.addU1(0);

        codeArray.addU4(defaultBranch.getOffset32(getPC(), instrPC));
        codeArray.addU4(branches.length);
        for (int i = 0; i < branches.length; i++) {
            codeArray.addU4(keys[i]);
            codeArray.addU4(branches[i].getOffset32(getPC(), instrPC));
        }
    }

    public void emitIRETURN() { emit(JOpcode.IRETURN); }
    public void emitLRETURN() { emit(JOpcode.LRETURN); }
    public void emitFRETURN() { emit(JOpcode.FRETURN); }
    public void emitDRETURN() { emit(JOpcode.DRETURN); }
    public void emitARETURN() { emit(JOpcode.ARETURN); }
    public void emitRETURN() { emit(JOpcode.RETURN); }

    // Field access
    public void emitGETSTATIC(String className, String name, JType type) {
        setStackProduction(getPC(), type.getSize());
        int index = pool.addFieldRef(className, name, type.getSignature());
        emitU2(JOpcode.GETSTATIC, index);
    }
    public void emitPUTSTATIC(String className, String name, JType type) {
        setStackProduction(getPC(), -type.getSize());
        int index = pool.addFieldRef(className, name, type.getSignature());
        emitU2(JOpcode.PUTSTATIC, index);
    }
    public void emitGETFIELD(String className, String name, JType type) {
        setStackProduction(getPC(), type.getSize() - 1);
        int index = pool.addFieldRef(className, name, type.getSignature());
        emitU2(JOpcode.GETFIELD, index);
    }
    public void emitPUTFIELD(String className, String name, JType type) {
        setStackProduction(getPC(), -(type.getSize() + 1));
        int index = pool.addFieldRef(className, name, type.getSignature());
        emitU2(JOpcode.PUTFIELD, index);
    }

    // Method invocation
    public void emitINVOKEVIRTUAL(String className,
                                  String name,
                                  JMethodType type) {
        setStackProduction(getPC(), type.getProducedStack() - 1);
        int index =
            pool.addClassMethodRef(className, name, type.getSignature());
        emitU2(JOpcode.INVOKEVIRTUAL, index);
    }
    public void emitINVOKESPECIAL(String className,
                                  String name,
                                  JMethodType type) {
        setStackProduction(getPC(), type.getProducedStack() - 1);
        int index =
            pool.addClassMethodRef(className, name, type.getSignature());
        emitU2(JOpcode.INVOKESPECIAL, index);
    }
    public void emitINVOKESTATIC(String className,
                                 String name,
                                 JMethodType type) {
        setStackProduction(getPC(), type.getProducedStack());
        int index =
            pool.addClassMethodRef(className, name, type.getSignature());
        emitU2(JOpcode.INVOKESTATIC, index);
    }
    public void emitINVOKEINTERFACE(String className,
                                    String name,
                                    JMethodType type) {
        setStackProduction(getPC(), type.getProducedStack() - 1);
        int index =
            pool.addInterfaceMethodRef(className, name, type.getSignature());
        emitU2U1U1(JOpcode.INVOKEINTERFACE, index, type.getArgsSize() + 1, 0);
    }

    // Object creation
    public void emitNEW(String className) {
        emitU2(JOpcode.NEW, pool.addClass(className));
    }
    public void emitNEWARRAY(JType elemType) {
        emitU1(JOpcode.NEWARRAY, elemType.getTag());
    }
    public void emitANEWARRAY(JReferenceType elemType) {
        emitU2(JOpcode.ANEWARRAY, pool.addDescriptor(elemType));
    }
    public void emitMULTIANEWARRAY(JReferenceType elemType, int dimensions) {
        setStackProduction(getPC(), -dimensions + 1);
        emitU2U1(JOpcode.MULTIANEWARRAY,
                 pool.addDescriptor(elemType),
                 dimensions);
    }
    public void emitARRAYLENGTH() { emit(JOpcode.ARRAYLENGTH); }

    // Exception throwing
    public void emitATHROW() { emit(JOpcode.ATHROW); }

    // Dynamic typing
    public void emitCHECKCAST(JReferenceType type) {
        emitU2(JOpcode.CHECKCAST, pool.addDescriptor(type));
    }
    public void emitINSTANCEOF(JReferenceType type) {
        emitU2(JOpcode.INSTANCEOF, pool.addDescriptor(type));
    }

    // Monitors
    public void emitMONITORENTER() { emit(JOpcode.MONITORENTER); }
    public void emitMONITOREXIT() { emit(JOpcode.MONITOREXIT); }

    // Wide variants
    // FIXME setStackProd. will here raise an exception
    public void emitWIDE(JOpcode opcode, int index) {
        assert (opcode.code == JOpcode.cILOAD)
            || (opcode.code == JOpcode.cLLOAD)
            || (opcode.code == JOpcode.cFLOAD)
            || (opcode.code == JOpcode.cDLOAD)
            || (opcode.code == JOpcode.cALOAD)
            || (opcode.code == JOpcode.cISTORE)
            || (opcode.code == JOpcode.cLSTORE)
            || (opcode.code == JOpcode.cFSTORE)
            || (opcode.code == JOpcode.cDSTORE)
            || (opcode.code == JOpcode.cASTORE)
            || (opcode.code == JOpcode.cRET)
            : "invalide opcode for WIDE: " + opcode;

        setStackProduction(getPC(), opcode);
        codeArray.addU1(JOpcode.WIDE.code);
        codeArray.addU1(opcode.code);
        codeArray.addU2(index);
    }
    public void emitWIDE(JOpcode opcode, int index, int constant) {
        assert opcode.code == JOpcode.cIINC
            : "invalid opcode for WIDE: " + opcode;

        setStackProduction(getPC(), opcode);
        codeArray.addU1(JOpcode.cWIDE);
        codeArray.addU1(opcode.code);
        codeArray.addU2(index);
        codeArray.addU2(constant);
    }

    protected void emitU1(JOpcode opcode, int i1) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU1(i1);
    }

    protected void emitU1U1(JOpcode opcode, int i1, int i2) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU1(i1);
        codeArray.addU1(i2);
    }

    protected void emitU2(JOpcode opcode, int i1) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU2(i1);
    }

    protected void emitU2U1(JOpcode opcode, int i1, int i2) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU2(i1);
        codeArray.addU1(i2);
    }

    protected void emitU2U1U1(JOpcode opcode, int i1, int i2, int i3) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU2(i1);
        codeArray.addU1(i2);
        codeArray.addU1(i3);
    }

    protected void emitU4(JOpcode opcode, int i1) {
        setStackProduction(getPC(), opcode);
        codeArray.addU1(opcode.code);
        codeArray.addU4(i1);
    }

    protected int getU1(int sourcePos) {
        return codeArray.getU1(sourcePos);
    }

    protected int getU2(int sourcePos) {
        return codeArray.getU2(sourcePos);
    }

    protected int getU4(int sourcePos) {
        return codeArray.getU4(sourcePos);
    }

    protected int getS1(int sourcePos) {
        return codeArray.getS1(sourcePos);
    }

    protected int getS2(int sourcePos) {
        return codeArray.getS2(sourcePos);
    }

    protected int getS4(int sourcePos) {
        return codeArray.getS4(sourcePos);
    }

    // Stack size computation
    //////////////////////////////////////////////////////////////////////

    protected int getStackProduction(int pc) {
        if (stackProduction == null || pc >= stackProduction.length)
            return UNKNOWN_STACK_SIZE;
        else
            return stackProduction[pc];
    }

    protected void setStackProduction(int pc, int production) {
        if (stackProduction == null) {
            stackProduction = new int[256];
            Arrays.fill(stackProduction, UNKNOWN_STACK_SIZE);
        } else {
            while (pc >= stackProduction.length) {
                    int[] newStackProduction = new int[stackProduction.length * 2];
                    System.arraycopy(stackProduction, 0,
                                     newStackProduction, 0,
                                     stackProduction.length);
                    Arrays.fill(newStackProduction,
                                stackProduction.length,
                                newStackProduction.length,
                                UNKNOWN_STACK_SIZE);
                    stackProduction = newStackProduction;
                }
        }
        stackProduction[pc] = production;
    }

    protected void setStackProduction(int pc, JOpcode opcode) {
        // TODO we should instead check whether the opcode has known
        // stack consumption/production.
        if (getStackProduction(pc) == UNKNOWN_STACK_SIZE)
//                && opcode.hasKnownProducedDataSize()
//                && opcode.hasKnownConsumedDataSize())
            setStackProduction(pc,
                               opcode.getProducedDataSize()
                               - opcode.getConsumedDataSize());
    }

    protected int computeMaxStackSize() {
        if (stackSizes == null) {
            stackSizes = new int[getSize()];
            Arrays.fill(stackSizes, UNKNOWN_STACK_SIZE);
            stackSizes[0] = 0;
        }
        int size = computeMaxStackSize(0, 0, 0);

        // compute stack sizes for exception handlers too
        ExceptionHandler exh = null;
        for (Iterator it = exceptionHandlers.iterator();
             it.hasNext();) {
            exh = (ExceptionHandler)it.next();
            int exhSize = computeMaxStackSize(exh.getHandlerPC(), 1, 1);
            if (size < exhSize)
                size = exhSize;
        }

        return size;
    }

    protected int computeMaxStackSize(int pc, int stackSize, int maxStackSize) {
        JCodeIterator iterator = new JCodeIterator(this, pc);
        for (;;) {
            int successors = iterator.getSuccessorCount();
            if (successors == 0)
                return maxStackSize;
            else {
                assert stackProduction[iterator.getPC()] != UNKNOWN_STACK_SIZE
                    : "unknown stack production, pc=" + iterator.getPC()
                    + " in method " + owner.getName();
                stackSize += stackProduction[iterator.getPC()];
                if (stackSize > maxStackSize)
                    maxStackSize = stackSize;
                int nextPC = -1;
                for (int i = 0; i < successors; ++i) {
                    int succPC = iterator.getSuccessorPC(i);
                    assert succPC >= 0 && succPC < stackSizes.length
                        : iterator.getPC() + ": invalid pc: " + succPC
                        + " op: " + iterator.getOpcode();
                    if (stackSizes[succPC] == UNKNOWN_STACK_SIZE) {
                        stackSizes[succPC] = stackSize;
                        if (nextPC == -1)
                            nextPC = succPC;
                        else
                            maxStackSize = computeMaxStackSize(succPC,
                                                               stackSize,
                                                               maxStackSize);
                    }
                }
                if (nextPC == -1)
                    return maxStackSize;
                else
                    iterator.moveTo(nextPC);
            }
        }
    }

    // Labels
    //////////////////////////////////////////////////////////////////////

    public static class OffsetTooBigException extends Exception {
        public OffsetTooBigException() { super(); }
        public OffsetTooBigException(String message) { super(message); }
    }

    protected void checkOffset16(int offset) throws OffsetTooBigException {
        if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE)
            throw new OffsetTooBigException("offset too big to fit"
                                            + " in 16 bits: " + offset);
    }

    public class Label {
        protected boolean anchored = false;
        protected int targetPC = 0;

        public void anchorToNext() {
            assert !anchored;
            this.targetPC = getPC();
            anchored = true;
        }

        public int getAnchor() {
            assert anchored;
            return targetPC;
        }

        protected int getOffset16(int pc, int instrPC)
            throws OffsetTooBigException {
            if (anchored) {
                int offset = targetPC - instrPC;
                checkOffset16(offset);
                return offset;
            } else {
                recordOffsetToPatch(pc, 16, instrPC, this);
                return 0;
            }
        }

        protected int getOffset32(int pc, int instrPC) {
            if (anchored)
                return targetPC - instrPC;
            else {
                recordOffsetToPatch(pc, 32, instrPC, this);
                return 0;
            }
        }
    }

    public Label newLabel() {
        return new Label();
    }

    public Label[] newLabels(int count) {
        Label[] labels = new Label[count];
        for (int i = 0; i < labels.length; ++i)
            labels[i] = newLabel();
        return labels;
    }

    protected static class OffsetToPatch {
        public final int pc;
        public final int size;
        public final int instrPC;
        public final Label label;

        public OffsetToPatch(int pc, int size, int instrPC, Label label) {
            this.pc = pc;
            this.size = size;
            this.instrPC = instrPC;
            this.label = label;
        }
    }

    protected void recordOffsetToPatch(int offsetPC,
                                       int size,
                                       int instrPC,
                                       Label label) {
        offsetToPatch.add(new OffsetToPatch(offsetPC, size, instrPC, label));
    }

    protected void patchAllOffset() throws OffsetTooBigException {
        Iterator offsetIt = offsetToPatch.iterator();
        while (offsetIt.hasNext()) {
            OffsetToPatch offset = (OffsetToPatch)offsetIt.next();
            int offsetValue = offset.label.getAnchor() - offset.instrPC;
            if (offset.size == 16) {
                checkOffset16(offsetValue);
                codeArray.putU2(offset.pc, offsetValue);
            } else
                codeArray.putU4(offset.pc, offsetValue);
        }
    }

    // Exception handling
    //////////////////////////////////////////////////////////////////////

    public class ExceptionHandler {
        protected int startPC, endPC, handlerPC;
        protected final String catchType;
        protected final int catchTypeIndex;

        public void setStartPC(int pc) {
            this.startPC = pc;
        }

        public int getStartPC() {
            return this.startPC;
        }

        public void setEndPC(int pc) {
            this.endPC = pc;
        }

        public int getEndPC() {
            return this.endPC;
        }

        public void setHandlerPC(int pc) {
            this.handlerPC = pc;
        }

        public int getHandlerPC() {
            return this.handlerPC;
        }

        public ExceptionHandler(String catchType) {
            this(0, 0, 0, catchType);
        }

        public ExceptionHandler(int startPC,
                                   int endPC,
                                   int handlerPC,
                                   String catchType) {
            this.startPC = startPC;
            this.endPC = endPC;
            this.handlerPC = handlerPC;
            this.catchType = catchType;
            this.catchTypeIndex = (catchType == null
                                   ? 0
                                   : pool.addClass(catchType));
        }

        public ExceptionHandler(DataInputStream stream) throws IOException {
            this.startPC = stream.readShort();
            this.endPC = stream.readShort();
            this.handlerPC = stream.readShort();
            this.catchTypeIndex = stream.readShort();
            this.catchType = (catchTypeIndex == 0
                              ? null
                              : pool.lookupClass(catchTypeIndex));
        }

        public void writeTo(DataOutputStream stream) throws IOException {
            stream.writeShort(startPC);
            stream.writeShort(endPC);
            stream.writeShort(handlerPC);
            stream.writeShort(catchTypeIndex);
        }

        // Follows javap output format for exception handlers.
        /*@Override*/public String toString() {
            StringBuffer buf = new StringBuffer("    ");
            if (startPC < 10) buf.append(" ");
            buf.append(startPC);
            buf.append("    ");
            if (endPC < 10) buf.append(" ");
            buf.append(endPC);
            buf.append("    ");
            buf.append(handlerPC);
            buf.append("   ");
            if (catchType != null) {
                buf.append("Class ");
                buf.append(catchType);
            }
            else
                buf.append("any");
            return buf.toString();
        }

    }

    public void addExceptionHandler(ExceptionHandler handler) {
        assert !frozen;
        exceptionHandlers.add(handler);
    }

    public void addExceptionHandler(int startPC,
                                    int endPC,
                                    int handlerPC,
                                    String catchType) {
        addExceptionHandler(new ExceptionHandler(startPC,
                                                 endPC,
                                                 handlerPC,
                                                 catchType));
    }

    public void addFinallyHandler(int startPC, int endPC, int handlerPC) {
        assert !frozen;
        addExceptionHandler(startPC, endPC, handlerPC, null);
    }

    public List/*<ExceptionHandler>*/ getExceptionHandlers() {
        return exceptionHandlers;
    }

    // Line numbers
    //////////////////////////////////////////////////////////////////////

    protected int[] lineNumbers = null;
    protected void ensureLineNumberCapacity(int endPC) {
        assert !frozen;
        if (lineNumbers == null) {
            lineNumbers = new int[endPC];
            addAttribute(context.JLineNumberTableAttribute(owner.getOwner(),
                                                           this));
        } else if (lineNumbers.length < endPC) {
            int[] newLN = new int[Math.max(endPC, lineNumbers.length * 2)];
            System.arraycopy(lineNumbers, 0, newLN, 0, lineNumbers.length);
            lineNumbers = newLN;
        }
    }

    /**
     * Set all line numbers in the interval [startPC, endPC) to
     * line, overwriting existing line numbers.
     */
    public void setLineNumber(int startPC, int endPC, int line) {
        ensureLineNumberCapacity(endPC);
        Arrays.fill(lineNumbers, startPC, endPC, line);
    }

    public void setLineNumber(int instrPC, int line) {
        setLineNumber(instrPC, instrPC + 1, line);
    }

    /** Sets all non-filled line numbers in the interval [startPC, endPC)
     *  to 'line'.
     */
    public void completeLineNumber(int startPC, int endPC, int line) {
        ensureLineNumberCapacity(endPC);
        for (int pc = startPC; pc < endPC; ++pc)
            if (lineNumbers[pc] == 0) lineNumbers[pc] = line;
    }

    public int[] getLineNumbers() {
        assert frozen;
        if (lineNumbers == null) return new int[0];
        else if (lineNumbers.length == getPC()) return lineNumbers;
        else {
            int[] trimmedLN = new int[getPC()];
            System.arraycopy(lineNumbers, 0,
                             trimmedLN, 0,
                             Math.min(lineNumbers.length, trimmedLN.length));
            return trimmedLN;
        }
    }

    // Output
    //////////////////////////////////////////////////////////////////////

    public void writeTo(DataOutputStream stream) throws IOException {
        assert frozen;
        stream.writeInt(getSize());
        codeArray.writeTo(stream);
    }

    // Follows javap output format for opcodes.
    /*@Override*/ public String toString() {
        StringBuffer buf = new StringBuffer();
        JOpcode opcode = null;
        int pc = 0, addr = 0;
        while (pc < codeArray.getSize()) {
            buf.append("\n   ");
            buf.append(pc);
            buf.append(":\t");
            opcode = JOpcode.OPCODES[codeArray.getU1(pc)];
            buf.append(decode(opcode, pc));
            if (opcode.code == JOpcode.cTABLESWITCH ||
                opcode.code == JOpcode.cLOOKUPSWITCH) {
                addr = ((pc / 4 + 1) + 1) * 4; // U4 aligned data
                int low = codeArray.getU4(addr);
                int high = codeArray.getU4(addr+4);
                pc = addr + (2/*low+high*/ + (high - low + 1)/*targets*/) * 4;
            } else
                pc += opcode.getSize();
        }
        if (exceptionHandlers.size() > 0) {
            buf.append("\n  Exception table:\n   from   to  target type\n");
            Iterator it = exceptionHandlers.iterator();
            while (it.hasNext()) {
                ExceptionHandler exh = (ExceptionHandler)it.next();
                buf.append(exh);
                buf.append("\n");
            }
        }
        return buf.toString();
    }

    private String decode(JOpcode opcode, int pc) {
        String ownerClassName = owner.getOwner().getName();
        int data, data2;
        StringBuilder buf = new StringBuilder();
        buf.append(opcode.name.toLowerCase());
        switch (opcode.code) {
        case JOpcode.cALOAD: case JOpcode.cASTORE: case JOpcode.cBIPUSH:
        case JOpcode.cDLOAD: case JOpcode.cDSTORE:
        case JOpcode.cFLOAD: case JOpcode.cFSTORE:
        case JOpcode.cILOAD: case JOpcode.cISTORE:
        case JOpcode.cLLOAD: case JOpcode.cLSTORE:
            data = codeArray.getU1(pc+1);
            buf.append("\t");
            buf.append(data);
            break;
        case JOpcode.cLDC:
            data = codeArray.getU1(pc+1);
            buf.append("\t#");
            buf.append(data);
            buf.append("; ");
            buf.append(pool.lookupEntry(data).toComment(ownerClassName));
            break;
        case JOpcode.cNEWARRAY:
            data = codeArray.getU1(pc+1);
            buf.append(" ");
            buf.append(JType.tagToString(data));
            break;
        case JOpcode.cIINC:
            data = codeArray.getU1(pc+1);
            data2 = codeArray.getU1(pc+2);
            buf.append("\t");
            buf.append(data);
            buf.append(", ");
            buf.append(data2);
            break;
        case JOpcode.cSIPUSH:
            data = codeArray.getU2(pc+1);
            buf.append("\t");
            buf.append(data);
            break;
        case JOpcode.cANEWARRAY: case JOpcode.cCHECKCAST:
        case JOpcode.cGETFIELD: case JOpcode.cGETSTATIC:
        case JOpcode.cINSTANCEOF:
        case JOpcode.cINVOKESPECIAL: case JOpcode.cINVOKESTATIC:
        case JOpcode.cINVOKEVIRTUAL:
        case JOpcode.cLDC_W: case JOpcode.cLDC2_W: case JOpcode.cNEW:
        case JOpcode.cPUTFIELD: case JOpcode.cPUTSTATIC:
            data = codeArray.getU2(pc+1);
            buf.append("\t#");
            buf.append(data);
            buf.append("; ");
            buf.append(pool.lookupEntry(data).toComment(ownerClassName));
            break;
        case JOpcode.cIF_ACMPEQ: case JOpcode.cIF_ACMPNE:
        case JOpcode.cIFEQ: case JOpcode.cIFGE: case JOpcode.cIFGT:
        case JOpcode.cIFLE: case JOpcode.cIFLT: case JOpcode.cIFNE:
        case JOpcode.cIFNONNULL: case JOpcode.cIFNULL:
        case JOpcode.cIF_ICMPEQ: case JOpcode.cIF_ICMPGE:
        case JOpcode.cIF_ICMPGT: case JOpcode.cIF_ICMPLE:
        case JOpcode.cIF_ICMPLT: case JOpcode.cIF_ICMPNE:
            data = codeArray.getU2(pc+1); // maybe S2 offset
            buf.append("\t");
            buf.append(pc+data);
            break;
        case JOpcode.cGOTO:
            data = codeArray.getS2(pc+1); // always S2 offset
            buf.append("\t");
            buf.append(pc+data);
            break;
        case JOpcode.cINVOKEINTERFACE:
            data = codeArray.getU2(pc+1);
            data2 = codeArray.getU1(pc+3);
            buf.append("\t#");
            buf.append(data);
            buf.append(",  ");
            buf.append(data2);
            buf.append("; ");
            buf.append(pool.lookupEntry(data).toComment(ownerClassName));
            break;
        case JOpcode.cTABLESWITCH:
            buf.append("{ //");
            int addr = ((pc / 4 + 1) + 1) * 4; // U4 aligned data
            int low = codeArray.getU4(addr);
            int high = codeArray.getU4(addr+4);
            buf.append(low);
            buf.append(" to ");
            buf.append(high);
            for (int i = low; i <= high; ++i) {
                buf.append("\n\t\t");
                buf.append(i);
                buf.append(": ");
                buf.append(pc+codeArray.getU4(addr+(i-1)*4));
                buf.append(";");
            }
            buf.append("\n\t\tdefault: ");
            buf.append(pc+codeArray.getU4(addr-4));
            buf.append(" }");
        default:
        }
        return buf.toString();
    }
}
