/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Roger Lawrence
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.classfile;

import org.mozilla.javascript.ObjToIntMap;
import org.mozilla.javascript.ObjArray;
import org.mozilla.javascript.UintMap;

import java.io.*;

/**
 * ClassFileWriter
 *
 * A ClassFileWriter is used to write a Java class file. Methods are
 * provided to create fields and methods, and within methods to write
 * Java bytecodes.
 *
 * @author Roger Lawrence
 */
public class ClassFileWriter {
    
    /**
     * Thrown for cases where the error in generating the class file is
     * due to a program size constraints rather than a likely bug in the
     * compiler.
     */
    public static class ClassFileFormatException extends RuntimeException {

        private static final long serialVersionUID = 1263998431033790599L;

        ClassFileFormatException(String message) {
            super(message);
        }
    }

    /**
     * Construct a ClassFileWriter for a class.
     *
     * @param className the name of the class to write, including
     *        full package qualification.
     * @param superClassName the name of the superclass of the class
     *        to write, including full package qualification.
     * @param sourceFileName the name of the source file to use for
     *        producing debug information, or null if debug information
     *        is not desired
     */
    public ClassFileWriter(String className, String superClassName,
                           String sourceFileName)
    {
        generatedClassName = className;
        itsConstantPool = new ConstantPool(this);
        itsThisClassIndex = itsConstantPool.addClass(className);
        itsSuperClassIndex = itsConstantPool.addClass(superClassName);
        if (sourceFileName != null)
            itsSourceFileNameIndex = itsConstantPool.addUtf8(sourceFileName);
        itsFlags = ACC_PUBLIC;
    }

    public final String getClassName()
    {
        return generatedClassName;
    }

    /**
     * Add an interface implemented by this class.
     *
     * This method may be called multiple times for classes that
     * implement multiple interfaces.
     *
     * @param interfaceName a name of an interface implemented
     *        by the class being written, including full package
     *        qualification.
     */
    public void addInterface(String interfaceName) {
        short interfaceIndex = itsConstantPool.addClass(interfaceName);
        itsInterfaces.add(new Short(interfaceIndex));
    }

    public static final short
        ACC_PUBLIC = 0x0001,
        ACC_PRIVATE = 0x0002,
        ACC_PROTECTED = 0x0004,
        ACC_STATIC = 0x0008,
        ACC_FINAL = 0x0010,
        ACC_SYNCHRONIZED = 0x0020,
        ACC_VOLATILE = 0x0040,
        ACC_TRANSIENT = 0x0080,
        ACC_NATIVE = 0x0100,
        ACC_ABSTRACT = 0x0400;

    /**
     * Set the class's flags.
     *
     * Flags must be a set of the following flags, bitwise or'd
     * together:
     *      ACC_PUBLIC
     *      ACC_PRIVATE
     *      ACC_PROTECTED
     *      ACC_FINAL
     *      ACC_ABSTRACT
     * TODO: check that this is the appropriate set
     * @param flags the set of class flags to set
     */
    public void setFlags(short flags) {
        itsFlags = flags;
    }

    static String getSlashedForm(String name)
    {
        return name.replace('.', '/');
    }

    /**
     * Convert Java class name in dot notation into
     * "Lname-with-dots-replaced-by-slashes;" form suitable for use as
     * JVM type signatures.
     */
    public static String classNameToSignature(String name)
    {
        int nameLength = name.length();
        int colonPos = 1 + nameLength;
        char[] buf = new char[colonPos + 1];
        buf[0] = 'L';
        buf[colonPos] = ';';
        name.getChars(0, nameLength, buf, 1);
        for (int i = 1; i != colonPos; ++i) {
            if (buf[i] == '.') {
                buf[i] = '/';
            }
        }
        return new String(buf, 0, colonPos + 1);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     */
    public void addField(String fieldName, String type, short flags) {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        itsFields.add(new ClassFileField(fieldNameIndex, typeIndex, flags));
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial integral value
     */
    public void addField(String fieldName, String type, short flags,
                         int value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)0,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial long value
     */
    public void addField(String fieldName, String type, short flags,
                         long value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)2,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add a field to the class.
     *
     * @param fieldName the name of the field
     * @param type the type of the field using ...
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     * @param value an initial double value
     */
    public void addField(String fieldName, String type, short flags,
                         double value)
    {
        short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
        short typeIndex = itsConstantPool.addUtf8(type);
        ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
                                                  flags);
        field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
                            (short)0,
                            (short)2,
                            itsConstantPool.addConstant(value));
        itsFields.add(field);
    }

    /**
     * Add Information about java variable to use when generating the local
     * variable table.
     *
     * @param name variable name.
     * @param type variable type as bytecode descriptor string.
     * @param startPC the starting bytecode PC where this variable is live,
     *                 or -1 if it does not have a Java register.
     * @param register the Java register number of variable
     *                 or -1 if it does not have a Java register.
     */
    public void addVariableDescriptor(String name, String type, int startPC, int register)
    {
        int nameIndex = itsConstantPool.addUtf8(name);
        int descriptorIndex = itsConstantPool.addUtf8(type);
        int [] chunk = { nameIndex, descriptorIndex, startPC, register };
        if (itsVarDescriptors == null) {
            itsVarDescriptors = new ObjArray();
        }
        itsVarDescriptors.add(chunk);
    }

    /**
     * Add a method and begin adding code.
     *
     * This method must be called before other methods for adding code,
     * exception tables, etc. can be invoked.
     *
     * @param methodName the name of the method
     * @param type a string representing the type
     * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
     *        bitwise or'd together
     */
    public void startMethod(String methodName, String type, short flags) {
        short methodNameIndex = itsConstantPool.addUtf8(methodName);
        short typeIndex = itsConstantPool.addUtf8(type);
        itsCurrentMethod = new ClassFileMethod(methodNameIndex, typeIndex,
                                               flags);
        itsMethods.add(itsCurrentMethod);
    }

    /**
     * Complete generation of the method.
     *
     * After this method is called, no more code can be added to the
     * method begun with <code>startMethod</code>.
     *
     * @param maxLocals the maximum number of local variable slots
     *        (a.k.a. Java registers) used by the method
     */
    public void stopMethod(short maxLocals) {
        if (itsCurrentMethod == null)
            throw new IllegalStateException("No method to stop");

        fixLabelGotos();

        itsMaxLocals = maxLocals;

        int lineNumberTableLength = 0;
        if (itsLineNumberTable != null) {
            // 6 bytes for the attribute header
            // 2 bytes for the line number count
            // 4 bytes for each entry
            lineNumberTableLength = 6 + 2 + (itsLineNumberTableTop * 4);
        }

        int variableTableLength = 0;
        if (itsVarDescriptors != null) {
            // 6 bytes for the attribute header
            // 2 bytes for the variable count
            // 10 bytes for each entry
            variableTableLength = 6 + 2 + (itsVarDescriptors.size() * 10);
        }

        int attrLength = 2 +                    // attribute_name_index
                         4 +                    // attribute_length
                         2 +                    // max_stack
                         2 +                    // max_locals
                         4 +                    // code_length
                         itsCodeBufferTop +
                         2 +                    // exception_table_length
                         (itsExceptionTableTop * 8) +
                         2 +                    // attributes_count
                         lineNumberTableLength +
                         variableTableLength;

        if (attrLength > 65536) {
            // See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html,
            // section 4.10, "The amount of code per non-native, non-abstract 
            // method is limited to 65536 bytes...
            throw new ClassFileFormatException(
                "generated bytecode for method exceeds 64K limit.");
        }
        byte[] codeAttribute = new byte[attrLength];
        int index = 0;
        int codeAttrIndex = itsConstantPool.addUtf8("Code");
        index = putInt16(codeAttrIndex, codeAttribute, index);
        attrLength -= 6;                 // discount the attribute header
        index = putInt32(attrLength, codeAttribute, index);
        index = putInt16(itsMaxStack, codeAttribute, index);
        index = putInt16(itsMaxLocals, codeAttribute, index);
        index = putInt32(itsCodeBufferTop, codeAttribute, index);
        System.arraycopy(itsCodeBuffer, 0, codeAttribute, index,
                         itsCodeBufferTop);
        index += itsCodeBufferTop;

        if (itsExceptionTableTop > 0) {
            index = putInt16(itsExceptionTableTop, codeAttribute, index);
            for (int i = 0; i < itsExceptionTableTop; i++) {
                ExceptionTableEntry ete = itsExceptionTable[i];
                short startPC = (short)getLabelPC(ete.itsStartLabel);
                short endPC = (short)getLabelPC(ete.itsEndLabel);
                short handlerPC = (short)getLabelPC(ete.itsHandlerLabel);
                short catchType = ete.itsCatchType;
                if (startPC == -1)
                    throw new IllegalStateException("start label not defined");
                if (endPC == -1)
                    throw new IllegalStateException("end label not defined");
                if (handlerPC == -1)
                    throw new IllegalStateException(
                        "handler label not defined");

                index = putInt16(startPC, codeAttribute, index);
                index = putInt16(endPC, codeAttribute, index);
                index = putInt16(handlerPC, codeAttribute, index);
                index = putInt16(catchType, codeAttribute, index);
            }
        }
        else {
            // write 0 as exception table length
            index = putInt16(0, codeAttribute, index);
        }

        int attributeCount = 0;
        if (itsLineNumberTable != null)
            attributeCount++;
        if (itsVarDescriptors != null)
            attributeCount++;
        index = putInt16(attributeCount, codeAttribute, index);

        if (itsLineNumberTable != null) {
            int lineNumberTableAttrIndex
                    = itsConstantPool.addUtf8("LineNumberTable");
            index = putInt16(lineNumberTableAttrIndex, codeAttribute, index);
            int tableAttrLength = 2 + (itsLineNumberTableTop * 4);
            index = putInt32(tableAttrLength, codeAttribute, index);
            index = putInt16(itsLineNumberTableTop, codeAttribute, index);
            for (int i = 0; i < itsLineNumberTableTop; i++) {
                index = putInt32(itsLineNumberTable[i], codeAttribute, index);
            }
        }

        if (itsVarDescriptors != null) {
            int variableTableAttrIndex
                    = itsConstantPool.addUtf8("LocalVariableTable");
            index = putInt16(variableTableAttrIndex, codeAttribute, index);
            int varCount = itsVarDescriptors.size();
            int tableAttrLength = 2 + (varCount * 10);
            index = putInt32(tableAttrLength, codeAttribute, index);
            index = putInt16(varCount, codeAttribute, index);
            for (int i = 0; i < varCount; i++) {
                int[] chunk = (int[])itsVarDescriptors.get(i);
                int nameIndex       = chunk[0];
                int descriptorIndex = chunk[1];
                int startPC         = chunk[2];
                int register        = chunk[3];
                int length = itsCodeBufferTop - startPC;

                index = putInt16(startPC, codeAttribute, index);
                index = putInt16(length, codeAttribute, index);
                index = putInt16(nameIndex, codeAttribute, index);
                index = putInt16(descriptorIndex, codeAttribute, index);
                index = putInt16(register, codeAttribute, index);
            }
        }

        itsCurrentMethod.setCodeAttribute(codeAttribute);

        itsExceptionTable = null;
        itsExceptionTableTop = 0;
        itsLineNumberTableTop = 0;
        itsCodeBufferTop = 0;
        itsCurrentMethod = null;
        itsMaxStack = 0;
        itsStackTop = 0;
        itsLabelTableTop = 0;
        itsFixupTableTop = 0;
        itsVarDescriptors = null;
    }

    /**
     * Add the single-byte opcode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     */
    public void add(int theOpCode) {
        if (opcodeCount(theOpCode) != 0)
            throw new IllegalArgumentException("Unexpected operands");
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        if (DEBUGCODE)
            System.out.println("Add " + bytecodeStr(theOpCode));
        addToCodeBuffer(theOpCode);
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    /**
     * Add a single-operand opcode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     * @param theOperand the operand of the bytecode
     */
    public void add(int theOpCode, int theOperand) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+Integer.toHexString(theOperand));
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        switch (theOpCode) {
            case ByteCode.GOTO :
                // fallthru...
            case ByteCode.IFEQ :
            case ByteCode.IFNE :
            case ByteCode.IFLT :
            case ByteCode.IFGE :
            case ByteCode.IFGT :
            case ByteCode.IFLE :
            case ByteCode.IF_ICMPEQ :
            case ByteCode.IF_ICMPNE :
            case ByteCode.IF_ICMPLT :
            case ByteCode.IF_ICMPGE :
            case ByteCode.IF_ICMPGT :
            case ByteCode.IF_ICMPLE :
            case ByteCode.IF_ACMPEQ :
            case ByteCode.IF_ACMPNE :
            case ByteCode.JSR :
            case ByteCode.IFNULL :
            case ByteCode.IFNONNULL : {
                    if ((theOperand & 0x80000000) != 0x80000000) {
                        if ((theOperand < 0) || (theOperand > 65535))
                            throw new IllegalArgumentException(
                                "Bad label for branch");
                    }
                    int branchPC = itsCodeBufferTop;
                    addToCodeBuffer(theOpCode);
                    if ((theOperand & 0x80000000) != 0x80000000) {
                            // hard displacement
                        addToCodeInt16(theOperand);
                    }
                    else {  // a label
                        int targetPC = getLabelPC(theOperand);
                        if (DEBUGLABELS) {
                            int theLabel = theOperand & 0x7FFFFFFF;
                            System.out.println("Fixing branch to " +
                                               theLabel + " at " + targetPC +
                                               " from " + branchPC);
                        }
                        if (targetPC != -1) {
                            int offset = targetPC - branchPC;
                            addToCodeInt16(offset);
                        }
                        else {
                            addLabelFixup(theOperand, branchPC + 1);
                            addToCodeInt16(0);
                        }
                    }
                }
                break;

            case ByteCode.BIPUSH :
                if ((byte)theOperand != theOperand)
                    throw new IllegalArgumentException("out of range byte");
                addToCodeBuffer(theOpCode);
                addToCodeBuffer((byte)theOperand);
                break;

            case ByteCode.SIPUSH :
                if ((short)theOperand != theOperand)
                    throw new IllegalArgumentException("out of range short");
                addToCodeBuffer(theOpCode);
                   addToCodeInt16(theOperand);
                break;

            case ByteCode.NEWARRAY :
                if (!(0 <= theOperand && theOperand < 256))
                    throw new IllegalArgumentException("out of range index");
                addToCodeBuffer(theOpCode);
                addToCodeBuffer(theOperand);
                break;

            case ByteCode.GETFIELD :
            case ByteCode.PUTFIELD :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new IllegalArgumentException("out of range field");
                addToCodeBuffer(theOpCode);
                addToCodeInt16(theOperand);
                break;

            case ByteCode.LDC :
            case ByteCode.LDC_W :
            case ByteCode.LDC2_W :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new IllegalArgumentException("out of range index");
                if (theOperand >= 256
                    || theOpCode == ByteCode.LDC_W
                    || theOpCode == ByteCode.LDC2_W)
                {
                    if (theOpCode == ByteCode.LDC) {
                        addToCodeBuffer(ByteCode.LDC_W);
                    } else {
                        addToCodeBuffer(theOpCode);
                    }
                    addToCodeInt16(theOperand);
                } else {
                    addToCodeBuffer(theOpCode);
                    addToCodeBuffer(theOperand);
                }
                break;

            case ByteCode.RET :
            case ByteCode.ILOAD :
            case ByteCode.LLOAD :
            case ByteCode.FLOAD :
            case ByteCode.DLOAD :
            case ByteCode.ALOAD :
            case ByteCode.ISTORE :
            case ByteCode.LSTORE :
            case ByteCode.FSTORE :
            case ByteCode.DSTORE :
            case ByteCode.ASTORE :
                if (!(0 <= theOperand && theOperand < 65536))
                    throw new ClassFileFormatException("out of range variable");
                if (theOperand >= 256) {
                    addToCodeBuffer(ByteCode.WIDE);
                    addToCodeBuffer(theOpCode);
                    addToCodeInt16(theOperand);
                }
                else {
                    addToCodeBuffer(theOpCode);
                    addToCodeBuffer(theOperand);
                }
                break;

            default :
                throw new IllegalArgumentException(
                    "Unexpected opcode for 1 operand");
        }

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    /**
     * Generate the load constant bytecode for the given integer.
     *
     * @param k the constant
     */
    public void addLoadConstant(int k) {
        switch (k) {
            case 0: add(ByteCode.ICONST_0); break;
            case 1: add(ByteCode.ICONST_1); break;
            case 2: add(ByteCode.ICONST_2); break;
            case 3: add(ByteCode.ICONST_3); break;
            case 4: add(ByteCode.ICONST_4); break;
            case 5: add(ByteCode.ICONST_5); break;
            default:
                add(ByteCode.LDC, itsConstantPool.addConstant(k));
                break;
        }
    }

    /**
     * Generate the load constant bytecode for the given long.
     *
     * @param k the constant
     */
    public void addLoadConstant(long k) {
        add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given float.
     *
     * @param k the constant
     */
    public void addLoadConstant(float k) {
        add(ByteCode.LDC, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given double.
     *
     * @param k the constant
     */
    public void addLoadConstant(double k) {
        add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
    }

    /**
     * Generate the load constant bytecode for the given string.
     *
     * @param k the constant
     */
    public void addLoadConstant(String k) {
        add(ByteCode.LDC, itsConstantPool.addConstant(k));
    }

    /**
     * Add the given two-operand bytecode to the current method.
     *
     * @param theOpCode the opcode of the bytecode
     * @param theOperand1 the first operand of the bytecode
     * @param theOperand2 the second operand of the bytecode
     */
    public void add(int theOpCode, int theOperand1, int theOperand2) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+Integer.toHexString(theOperand1)
                               +", "+Integer.toHexString(theOperand2));
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        if (theOpCode == ByteCode.IINC) {
            if (!(0 <= theOperand1 && theOperand1 < 65536))
                throw new ClassFileFormatException("out of range variable");
            if (!(0 <= theOperand2 && theOperand2 < 65536))
                throw new ClassFileFormatException("out of range increment");

            if (theOperand1 > 255 || theOperand2 < -128 || theOperand2 > 127) {
                addToCodeBuffer(ByteCode.WIDE);
                addToCodeBuffer(ByteCode.IINC);
                addToCodeInt16(theOperand1);
                addToCodeInt16(theOperand2);
            }
            else {
                addToCodeBuffer(ByteCode.WIDE);
                addToCodeBuffer(ByteCode.IINC);
                addToCodeBuffer(theOperand1);
                addToCodeBuffer(theOperand2);
            }
        }
        else if (theOpCode == ByteCode.MULTIANEWARRAY) {
            if (!(0 <= theOperand1 && theOperand1 < 65536))
                throw new IllegalArgumentException("out of range index");
            if (!(0 <= theOperand2 && theOperand2 < 256))
                throw new IllegalArgumentException("out of range dimensions");

            addToCodeBuffer(ByteCode.MULTIANEWARRAY);
            addToCodeInt16(theOperand1);
            addToCodeBuffer(theOperand2);
        }
        else {
            throw new IllegalArgumentException(
                "Unexpected opcode for 2 operands");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }

    }

    public void add(int theOpCode, String className) {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className);
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        switch (theOpCode) {
            case ByteCode.NEW :
            case ByteCode.ANEWARRAY :
            case ByteCode.CHECKCAST :
            case ByteCode.INSTANCEOF : {
                short classIndex = itsConstantPool.addClass(className);
                addToCodeBuffer(theOpCode);
                addToCodeInt16(classIndex);
            }
            break;

            default :
                throw new IllegalArgumentException(
                    "bad opcode for class reference");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }


    public void add(int theOpCode, String className, String fieldName,
                    String fieldType)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className+", "+fieldName+", "+fieldType);
        }
        int newStack = itsStackTop + stackChange(theOpCode);
        char fieldTypeChar = fieldType.charAt(0);
        int fieldSize = (fieldTypeChar == 'J' || fieldTypeChar == 'D')
                        ? 2 : 1;
        switch (theOpCode) {
            case ByteCode.GETFIELD :
            case ByteCode.GETSTATIC :
                newStack += fieldSize;
                break;
            case ByteCode.PUTSTATIC :
            case ByteCode.PUTFIELD :
                newStack -= fieldSize;
                break;
            default :
                throw new IllegalArgumentException(
                    "bad opcode for field reference");
        }
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        short fieldRefIndex = itsConstantPool.addFieldRef(className,
                                             fieldName, fieldType);
        addToCodeBuffer(theOpCode);
        addToCodeInt16(fieldRefIndex);

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    public void addInvoke(int theOpCode, String className, String methodName,
                          String methodType)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(theOpCode)
                               +", "+className+", "+methodName+", "
                               +methodType);
        }
        int parameterInfo = sizeOfParameters(methodType);
        int parameterCount = parameterInfo >>> 16;
        int stackDiff = (short)parameterInfo;

        int newStack = itsStackTop + stackDiff;
        newStack += stackChange(theOpCode);     // adjusts for 'this'
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        switch (theOpCode) {
            case ByteCode.INVOKEVIRTUAL :
            case ByteCode.INVOKESPECIAL :
            case ByteCode.INVOKESTATIC :
            case ByteCode.INVOKEINTERFACE : {
                    addToCodeBuffer(theOpCode);
                    if (theOpCode == ByteCode.INVOKEINTERFACE) {
                        short ifMethodRefIndex
                                    = itsConstantPool.addInterfaceMethodRef(
                                               className, methodName,
                                               methodType);
                        addToCodeInt16(ifMethodRefIndex);
                        addToCodeBuffer(parameterCount + 1);
                        addToCodeBuffer(0);
                    }
                    else {
                        short methodRefIndex = itsConstantPool.addMethodRef(
                                               className, methodName,
                                               methodType);
                        addToCodeInt16(methodRefIndex);
                    }
                }
                break;

            default :
                throw new IllegalArgumentException(
                    "bad opcode for method reference");
        }
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(theOpCode)
                               +" stack = "+itsStackTop);
        }
    }

    /**
     * Generate code to load the given integer on stack.
     *
     * @param k the constant
     */
    public void addPush(int k)
    {
        if ((byte)k == k) {
            if (k == -1) {
                add(ByteCode.ICONST_M1);
            } else if (0 <= k && k <= 5) {
                add((byte)(ByteCode.ICONST_0 + k));
            } else {
                add(ByteCode.BIPUSH, (byte)k);
            }
        } else if ((short)k == k) {
            add(ByteCode.SIPUSH, (short)k);
        } else {
            addLoadConstant(k);
        }
    }

    public void addPush(boolean k)
    {
        add(k ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
    }

    /**
     * Generate code to load the given long on stack.
     *
     * @param k the constant
     */
    public void addPush(long k)
    {
        int ik = (int)k;
        if (ik == k) {
            addPush(ik);
            add(ByteCode.I2L);
        } else {
            addLoadConstant(k);
        }
    }

    /**
     * Generate code to load the given double on stack.
     *
     * @param k the constant
     */
    public void addPush(double k)
    {
        if (k == 0.0) {
            // zero
            add(ByteCode.DCONST_0);
            if (1.0 / k < 0) {
                // Negative zero
                add(ByteCode.DNEG);
            }
        } else if (k == 1.0 || k == -1.0) {
            add(ByteCode.DCONST_1);
            if (k < 0) {
                add(ByteCode.DNEG);
            }
        } else {
            addLoadConstant(k);
        }
    }

    /**
     * Generate the code to leave on stack the given string even if the
     * string encoding exeeds the class file limit for single string constant
     *
     * @param k the constant
     */
    public void addPush(String k) {
        int length = k.length();
        int limit = itsConstantPool.getUtfEncodingLimit(k, 0, length);
        if (limit == length) {
            addLoadConstant(k);
            return;
        }
        // Split string into picies fitting the UTF limit and generate code for
        // StringBuffer sb = new StringBuffer(length);
        // sb.append(loadConstant(piece_1));
        // ...
        // sb.append(loadConstant(piece_N));
        // sb.toString();
        final String SB = "java/lang/StringBuffer";
        add(ByteCode.NEW, SB);
        add(ByteCode.DUP);
        addPush(length);
        addInvoke(ByteCode.INVOKESPECIAL, SB, "<init>", "(I)V");
        int cursor = 0;
        for (;;) {
            add(ByteCode.DUP);
            String s = k.substring(cursor, limit);
            addLoadConstant(s);
            addInvoke(ByteCode.INVOKEVIRTUAL, SB, "append",
                      "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
            add(ByteCode.POP);
            if (limit == length) {
                break;
            }
            cursor = limit;
            limit = itsConstantPool.getUtfEncodingLimit(k, limit, length);
        }
        addInvoke(ByteCode.INVOKEVIRTUAL, SB, "toString",
                  "()Ljava/lang/String;");
    }

    /**
     * Check if k fits limit on string constant size imposed by class file
     * format.
     *
     * @param k the string constant
     */
    public boolean isUnderStringSizeLimit(String k)
    {
        return itsConstantPool.isUnderUtfEncodingLimit(k);
    }

    /**
     * Store integer from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addIStore(int local)
    {
        xop(ByteCode.ISTORE_0, ByteCode.ISTORE, local);
    }

    /**
     * Store long from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addLStore(int local)
    {
        xop(ByteCode.LSTORE_0, ByteCode.LSTORE, local);
    }

    /**
     * Store float from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addFStore(int local)
    {
        xop(ByteCode.FSTORE_0, ByteCode.FSTORE, local);
    }

    /**
     * Store double from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addDStore(int local)
    {
        xop(ByteCode.DSTORE_0, ByteCode.DSTORE, local);
    }

    /**
     * Store object from stack top into the given local.
     *
     * @param local number of local register
     */
    public void addAStore(int local)
    {
        xop(ByteCode.ASTORE_0, ByteCode.ASTORE, local);
    }

    /**
     * Load integer from the given local into stack.
     *
     * @param local number of local register
     */
    public void addILoad(int local)
    {
        xop(ByteCode.ILOAD_0, ByteCode.ILOAD, local);
    }

    /**
     * Load long from the given local into stack.
     *
     * @param local number of local register
     */
    public void addLLoad(int local)
    {
        xop(ByteCode.LLOAD_0, ByteCode.LLOAD, local);
    }

    /**
     * Load float from the given local into stack.
     *
     * @param local number of local register
     */
    public void addFLoad(int local)
    {
        xop(ByteCode.FLOAD_0, ByteCode.FLOAD, local);
    }

    /**
     * Load double from the given local into stack.
     *
     * @param local number of local register
     */
    public void addDLoad(int local)
    {
        xop(ByteCode.DLOAD_0, ByteCode.DLOAD, local);
    }

    /**
     * Load object from the given local into stack.
     *
     * @param local number of local register
     */
    public void addALoad(int local)
    {
        xop(ByteCode.ALOAD_0, ByteCode.ALOAD, local);
    }

    /**
     * Load "this" into stack.
     */
    public void addLoadThis()
    {
        add(ByteCode.ALOAD_0);
    }

    private void xop(int shortOp, int op, int local)
    {
        switch (local) {
          case 0:
            add(shortOp);
            break;
          case 1:
            add(shortOp + 1);
            break;
          case 2:
            add(shortOp + 2);
            break;
          case 3:
            add(shortOp + 3);
            break;
          default:
            add(op, local);
        }
    }

    public int addTableSwitch(int low, int high)
    {
        if (DEBUGCODE) {
            System.out.println("Add "+bytecodeStr(ByteCode.TABLESWITCH)
                               +" "+low+" "+high);
        }
        if (low > high)
            throw new ClassFileFormatException("Bad bounds: "+low+' '+ high);

        int newStack = itsStackTop + stackChange(ByteCode.TABLESWITCH);
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);

        int entryCount = high - low + 1;
        int padSize = 3 & ~itsCodeBufferTop; // == 3 - itsCodeBufferTop % 4

        int N = addReservedCodeSpace(1 + padSize + 4 * (1 + 2 + entryCount));
        int switchStart = N;
        itsCodeBuffer[N++] = (byte)ByteCode.TABLESWITCH;
        while (padSize != 0) {
            itsCodeBuffer[N++] = 0;
            --padSize;
        }
        N += 4; // skip default offset
        N = putInt32(low, itsCodeBuffer, N);
        putInt32(high, itsCodeBuffer, N);

        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+bytecodeStr(ByteCode.TABLESWITCH)
                               +" stack = "+itsStackTop);
        }

        return switchStart;
    }

    public final void markTableSwitchDefault(int switchStart)
    {
        setTableSwitchJump(switchStart, -1, itsCodeBufferTop);
    }

    public final void markTableSwitchCase(int switchStart, int caseIndex)
    {
        setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
    }

    public final void markTableSwitchCase(int switchStart, int caseIndex,
                                          int stackTop)
    {
        if (!(0 <= stackTop && stackTop <= itsMaxStack))
            throw new IllegalArgumentException("Bad stack index: "+stackTop);
        itsStackTop = (short)stackTop;
        setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
    }

    public void setTableSwitchJump(int switchStart, int caseIndex,
                                   int jumpTarget)
    {
        if (!(0 <= jumpTarget && jumpTarget <= itsCodeBufferTop))
            throw new IllegalArgumentException("Bad jump target: "+jumpTarget);
        if (!(caseIndex >= -1))
            throw new IllegalArgumentException("Bad case index: "+caseIndex);

        int padSize = 3 & ~switchStart; // == 3 - switchStart % 4
        int caseOffset;
        if (caseIndex < 0) {
            // default label
            caseOffset = switchStart + 1 + padSize;
        } else {
            caseOffset = switchStart + 1 + padSize + 4 * (3 + caseIndex);
        }
        if (!(0 <= switchStart
              && switchStart <= itsCodeBufferTop - 4 * 4 - padSize - 1))
        {
            throw new IllegalArgumentException(
                switchStart+" is outside a possible range of tableswitch"
                +" in already generated code");
        }
        if ((0xFF & itsCodeBuffer[switchStart]) != ByteCode.TABLESWITCH) {
            throw new IllegalArgumentException(
                switchStart+" is not offset of tableswitch statement");
        }
        if (!(0 <= caseOffset && caseOffset + 4 <= itsCodeBufferTop)) {
            // caseIndex >= -1 does not guarantee that caseOffset >= 0 due
            // to a possible overflow.
            throw new ClassFileFormatException(
                "Too big case index: "+caseIndex);
        }
        // ALERT: perhaps check against case bounds?
        putInt32(jumpTarget - switchStart, itsCodeBuffer, caseOffset);
    }

    public int acquireLabel()
    {
        int top = itsLabelTableTop;
        if (itsLabelTable == null || top == itsLabelTable.length) {
            if (itsLabelTable == null) {
                itsLabelTable = new int[MIN_LABEL_TABLE_SIZE];
            }else {
                int[] tmp = new int[itsLabelTable.length * 2];
                System.arraycopy(itsLabelTable, 0, tmp, 0, top);
                itsLabelTable = tmp;
            }
        }
        itsLabelTableTop = top + 1;
        itsLabelTable[top] = -1;
        return top | 0x80000000;
    }

    public void markLabel(int label)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");

        label &= 0x7FFFFFFF;
        if (label > itsLabelTableTop)
            throw new IllegalArgumentException("Bad label");

        if (itsLabelTable[label] != -1) {
            throw new IllegalStateException("Can only mark label once");
        }

        itsLabelTable[label] = itsCodeBufferTop;
    }

    public void markLabel(int label, short stackTop)
    {
        markLabel(label);
        itsStackTop = stackTop;
    }

    public void markHandler(int theLabel) {
        itsStackTop = 1;
        markLabel(theLabel);
    }

    private int getLabelPC(int label)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");
        label &= 0x7FFFFFFF;
        if (!(label < itsLabelTableTop))
            throw new IllegalArgumentException("Bad label");
        return itsLabelTable[label];
    }

    private void addLabelFixup(int label, int fixupSite)
    {
        if (!(label < 0))
            throw new IllegalArgumentException("Bad label, no biscuit");
        label &= 0x7FFFFFFF;
        if (!(label < itsLabelTableTop))
            throw new IllegalArgumentException("Bad label");
        int top = itsFixupTableTop;
        if (itsFixupTable == null || top == itsFixupTable.length) {
            if (itsFixupTable == null) {
                itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE];
            }else {
                long[] tmp = new long[itsFixupTable.length * 2];
                System.arraycopy(itsFixupTable, 0, tmp, 0, top);
                itsFixupTable = tmp;
            }
        }
        itsFixupTableTop = top + 1;
        itsFixupTable[top] = ((long)label << 32) | fixupSite;
    }

    private  void fixLabelGotos()
    {
        byte[] codeBuffer = itsCodeBuffer;
        for (int i = 0; i < itsFixupTableTop; i++) {
            long fixup = itsFixupTable[i];
            int label = (int)(fixup >> 32);
            int fixupSite = (int)fixup;
            int pc = itsLabelTable[label];
            if (pc == -1) {
                // Unlocated label
                throw new RuntimeException();
            }
            // -1 to get delta from instruction start
            int offset = pc - (fixupSite - 1);
            if ((short)offset != offset) {
                throw new ClassFileFormatException
                    ("Program too complex: too big jump offset");
            }
            codeBuffer[fixupSite] = (byte)(offset >> 8);
            codeBuffer[fixupSite + 1] = (byte)offset;
        }
        itsFixupTableTop = 0;
    }

    /**
     * Get the current offset into the code of the current method.
     *
     * @return an integer representing the offset
     */
    public int getCurrentCodeOffset() {
        return itsCodeBufferTop;
    }

    public short getStackTop() {
        return itsStackTop;
    }

    public void setStackTop(short n) {
        itsStackTop = n;
    }

    public void adjustStackTop(int delta) {
        int newStack = itsStackTop + delta;
        if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
        itsStackTop = (short)newStack;
        if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
        if (DEBUGSTACK) {
            System.out.println("After "+"adjustStackTop("+delta+")"
                               +" stack = "+itsStackTop);
        }
    }

    private void addToCodeBuffer(int b)
    {
        int N = addReservedCodeSpace(1);
        itsCodeBuffer[N] = (byte)b;
    }

    private void addToCodeInt16(int value)
    {
        int N = addReservedCodeSpace(2);
        putInt16(value, itsCodeBuffer, N);
    }

    private int addReservedCodeSpace(int size)
    {
        if (itsCurrentMethod == null)
            throw new IllegalArgumentException("No method to add to");
        int oldTop = itsCodeBufferTop;
        int newTop = oldTop + size;
        if (newTop > itsCodeBuffer.length) {
            int newSize = itsCodeBuffer.length * 2;
            if (newTop > newSize) { newSize = newTop; }
            byte[] tmp = new byte[newSize];
            System.arraycopy(itsCodeBuffer, 0, tmp, 0, oldTop);
            itsCodeBuffer = tmp;
        }
        itsCodeBufferTop = newTop;
        return oldTop;
    }

    public void addExceptionHandler(int startLabel, int endLabel,
                                    int handlerLabel, String catchClassName)
    {
        if ((startLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad startLabel");
        if ((endLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad endLabel");
        if ((handlerLabel & 0x80000000) != 0x80000000)
            throw new IllegalArgumentException("Bad handlerLabel");

        /*
         * If catchClassName is null, use 0 for the catch_type_index; which
         * means catch everything.  (Even when the verifier has let you throw
         * something other than a Throwable.)
         */
        short catch_type_index = (catchClassName == null)
                                 ? 0
                                 : itsConstantPool.addClass(catchClassName);
        ExceptionTableEntry newEntry = new ExceptionTableEntry(
                                           startLabel,
                                           endLabel,
                                           handlerLabel,
                                           catch_type_index);
        int N = itsExceptionTableTop;
        if (N == 0) {
            itsExceptionTable = new ExceptionTableEntry[ExceptionTableSize];
        } else if (N == itsExceptionTable.length) {
            ExceptionTableEntry[] tmp = new ExceptionTableEntry[N * 2];
            System.arraycopy(itsExceptionTable, 0, tmp, 0, N);
            itsExceptionTable = tmp;
        }
        itsExceptionTable[N] = newEntry;
        itsExceptionTableTop = N + 1;

    }

    public void addLineNumberEntry(short lineNumber) {
        if (itsCurrentMethod == null)
            throw new IllegalArgumentException("No method to stop");
        int N = itsLineNumberTableTop;
        if (N == 0) {
            itsLineNumberTable = new int[LineNumberTableSize];
        } else if (N == itsLineNumberTable.length) {
            int[] tmp = new int[N * 2];
            System.arraycopy(itsLineNumberTable, 0, tmp, 0, N);
            itsLineNumberTable = tmp;
        }
        itsLineNumberTable[N] = (itsCodeBufferTop << 16) + lineNumber;
        itsLineNumberTableTop = N + 1;
    }

    /**
     * Write the class file to the OutputStream.
     *
     * @param oStream the stream to write to
     * @throws IOException if writing to the stream produces an exception
     */
    public void write(OutputStream oStream)
        throws IOException
    {
        byte[] array = toByteArray();
        oStream.write(array);
    }

    private int getWriteSize()
    {
        int size = 0;

        if (itsSourceFileNameIndex != 0) {
            itsConstantPool.addUtf8("SourceFile");
        }

        size += 8; //writeLong(FileHeaderConstant);
        size += itsConstantPool.getWriteSize();
        size += 2; //writeShort(itsFlags);
        size += 2; //writeShort(itsThisClassIndex);
        size += 2; //writeShort(itsSuperClassIndex);
        size += 2; //writeShort(itsInterfaces.size());
        size += 2 * itsInterfaces.size();

        size += 2; //writeShort(itsFields.size());
        for (int i = 0; i < itsFields.size(); i++) {
            size += ((ClassFileField)(itsFields.get(i))).getWriteSize();
        }

        size += 2; //writeShort(itsMethods.size());
        for (int i = 0; i < itsMethods.size(); i++) {
            size += ((ClassFileMethod)(itsMethods.get(i))).getWriteSize();
        }

        if (itsSourceFileNameIndex != 0) {
            size += 2; //writeShort(1);  attributes count
            size += 2; //writeShort(sourceFileAttributeNameIndex);
            size += 4; //writeInt(2);
            size += 2; //writeShort(itsSourceFileNameIndex);
        }else {
            size += 2; //out.writeShort(0);  no attributes
        }

        return size;
    }

    /**
     * Get the class file as array of bytesto the OutputStream.
     */
    public byte[] toByteArray()
    {
        int dataSize = getWriteSize();
        byte[] data = new byte[dataSize];
        int offset = 0;

        short sourceFileAttributeNameIndex = 0;
        if (itsSourceFileNameIndex != 0) {
            sourceFileAttributeNameIndex = itsConstantPool.addUtf8(
                                               "SourceFile");
        }

        offset = putInt64(FileHeaderConstant, data, offset);
        offset = itsConstantPool.write(data, offset);
        offset = putInt16(itsFlags, data, offset);
        offset = putInt16(itsThisClassIndex, data, offset);
        offset = putInt16(itsSuperClassIndex, data, offset);
        offset = putInt16(itsInterfaces.size(), data, offset);
        for (int i = 0; i < itsInterfaces.size(); i++) {
            int interfaceIndex = ((Short)(itsInterfaces.get(i))).shortValue();
            offset = putInt16(interfaceIndex, data, offset);
        }
        offset = putInt16(itsFields.size(), data, offset);
        for (int i = 0; i < itsFields.size(); i++) {
            ClassFileField field = (ClassFileField)itsFields.get(i);
            offset = field.write(data, offset);
        }
        offset = putInt16(itsMethods.size(), data, offset);
        for (int i = 0; i < itsMethods.size(); i++) {
            ClassFileMethod method = (ClassFileMethod)itsMethods.get(i);
            offset = method.write(data, offset);
        }
        if (itsSourceFileNameIndex != 0) {
            offset = putInt16(1, data, offset); // attributes count
            offset = putInt16(sourceFileAttributeNameIndex, data, offset);
            offset = putInt32(2, data, offset);
            offset = putInt16(itsSourceFileNameIndex, data, offset);
        } else {
            offset = putInt16(0, data, offset); // no attributes
        }

        if (offset != dataSize) {
            // Check getWriteSize is consistent with write!
            throw new RuntimeException();
        }

        return data;
    }

    static int putInt64(long value, byte[] array, int offset)
    {
        offset = putInt32((int)(value >>> 32), array, offset);
        return putInt32((int)value, array, offset);
    }

    private static void badStack(int value)
    {
        String s;
        if (value < 0) { s = "Stack underflow: "+value; }
        else { s = "Too big stack: "+value; }
        throw new IllegalStateException(s);
    }

    /*
        Really weird. Returns an int with # parameters in hi 16 bits, and
        stack difference removal of parameters from stack and pushing the
        result (it does not take into account removal of this in case of
        non-static methods).
        If Java really supported references we wouldn't have to be this
        perverted.
    */
    private static int sizeOfParameters(String pString)
    {
        int length = pString.length();
        int rightParenthesis = pString.lastIndexOf(')');
        if (3 <= length /* minimal signature takes at least 3 chars: ()V */
            && pString.charAt(0) == '('
            && 1 <= rightParenthesis && rightParenthesis + 1 < length)
        {
            boolean ok = true;
            int index = 1;
            int stackDiff = 0;
            int count = 0;
        stringLoop:
            while (index != rightParenthesis) {
                switch (pString.charAt(index)) {
                    default:
                        ok = false;
                        break stringLoop;
                    case 'J' :
                    case 'D' :
                        --stackDiff;
                        // fall thru
                    case 'B' :
                    case 'S' :
                    case 'C' :
                    case 'I' :
                    case 'Z' :
                    case 'F' :
                        --stackDiff;
                        ++count;
                        ++index;
                        continue;
                    case '[' :
                        ++index;
                        int c = pString.charAt(index);
                        while (c == '[') {
                            ++index;
                            c = pString.charAt(index);
                        }
                        switch (c) {
                            default:
                                ok = false;
                                break stringLoop;
                            case 'J' :
                            case 'D' :
                            case 'B' :
                            case 'S' :
                            case 'C' :
                            case 'I' :
                            case 'Z' :
                            case 'F' :
                                --stackDiff;
                                ++count;
                                ++index;
                                continue;
                            case 'L':
                                // fall thru
                        }
                          // fall thru
                    case 'L' : {
                        --stackDiff;
                        ++count;
                        ++index;
                        int semicolon = pString.indexOf(';',  index);
                        if (!(index + 1 <= semicolon
                            && semicolon < rightParenthesis))
                        {
                            ok = false;
                            break stringLoop;
                        }
                        index = semicolon + 1;
                        continue;
                    }
                }
            }
            if (ok) {
                switch (pString.charAt(rightParenthesis + 1)) {
                    default:
                        ok = false;
                        break;
                    case 'J' :
                    case 'D' :
                        ++stackDiff;
                        // fall thru
                    case 'B' :
                    case 'S' :
                    case 'C' :
                    case 'I' :
                    case 'Z' :
                    case 'F' :
                    case 'L' :
                    case '[' :
                        ++stackDiff;
                        // fall thru
                    case 'V' :
                        break;
                }
                if (ok) {
                    return ((count << 16) | (0xFFFF & stackDiff));
                }
            }
        }
        throw new IllegalArgumentException(
            "Bad parameter signature: "+pString);
    }

    static int putInt16(int value, byte[] array, int offset)
    {
        array[offset + 0] = (byte)(value >>> 8);
        array[offset + 1] = (byte)value;
        return offset + 2;
    }

    static int putInt32(int value, byte[] array, int offset)
    {
        array[offset + 0] = (byte)(value >>> 24);
        array[offset + 1] = (byte)(value >>> 16);
        array[offset + 2] = (byte)(value >>> 8);
        array[offset + 3] = (byte)value;
        return offset + 4;
    }

    /**
     * Number of operands accompanying the opcode.
     */
    static int opcodeCount(int opcode)
    {
        switch (opcode) {
            case ByteCode.AALOAD:
            case ByteCode.AASTORE:
            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.ARETURN:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.BASTORE:
            case ByteCode.BREAKPOINT:
            case ByteCode.CALOAD:
            case ByteCode.CASTORE:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.D2L:
            case ByteCode.DADD:
            case ByteCode.DALOAD:
            case ByteCode.DASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DDIV:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DMUL:
            case ByteCode.DNEG:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.DUP:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2I:
            case ByteCode.F2L:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FASTORE:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FDIV:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.FMUL:
            case ByteCode.FNEG:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2D:
            case ByteCode.I2F:
            case ByteCode.I2L:
            case ByteCode.I2S:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IASTORE:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.IDIV:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.IMUL:
            case ByteCode.INEG:
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2D:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LADD:
            case ByteCode.LALOAD:
            case ByteCode.LAND:
            case ByteCode.LASTORE:
            case ByteCode.LCMP:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDIV:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
            case ByteCode.LMUL:
            case ByteCode.LNEG:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LUSHR:
            case ByteCode.LXOR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.NOP:
            case ByteCode.POP:
            case ByteCode.POP2:
            case ByteCode.RETURN:
            case ByteCode.SALOAD:
            case ByteCode.SASTORE:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 0;
            case ByteCode.ALOAD:
            case ByteCode.ANEWARRAY:
            case ByteCode.ASTORE:
            case ByteCode.BIPUSH:
            case ByteCode.CHECKCAST:
            case ByteCode.DLOAD:
            case ByteCode.DSTORE:
            case ByteCode.FLOAD:
            case ByteCode.FSTORE:
            case ByteCode.GETFIELD:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.GOTO_W:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.ILOAD:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKEINTERFACE:
            case ByteCode.INVOKESPECIAL:
            case ByteCode.INVOKESTATIC:
            case ByteCode.INVOKEVIRTUAL:
            case ByteCode.ISTORE:
            case ByteCode.JSR:
            case ByteCode.JSR_W:
            case ByteCode.LDC:
            case ByteCode.LDC2_W:
            case ByteCode.LDC_W:
            case ByteCode.LLOAD:
            case ByteCode.LSTORE:
            case ByteCode.NEW:
            case ByteCode.NEWARRAY:
            case ByteCode.PUTFIELD:
            case ByteCode.PUTSTATIC:
            case ByteCode.RET:
            case ByteCode.SIPUSH:
                return 1;

            case ByteCode.IINC:
            case ByteCode.MULTIANEWARRAY:
                return 2;

            case ByteCode.LOOKUPSWITCH:
            case ByteCode.TABLESWITCH:
                return -1;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }

    /**
     *  The effect on the operand stack of a given opcode.
     */
    static int stackChange(int opcode)
    {
        // For INVOKE... accounts only for popping this (unless static),
        // ignoring parameters and return type
        switch (opcode) {
            case ByteCode.DASTORE:
            case ByteCode.LASTORE:
                return -4;

            case ByteCode.AASTORE:
            case ByteCode.BASTORE:
            case ByteCode.CASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.FASTORE:
            case ByteCode.IASTORE:
            case ByteCode.LCMP:
            case ByteCode.SASTORE:
                return -3;

            case ByteCode.DADD:
            case ByteCode.DDIV:
            case ByteCode.DMUL:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.LADD:
            case ByteCode.LAND:
            case ByteCode.LDIV:
            case ByteCode.LMUL:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSTORE:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LXOR:
            case ByteCode.POP2:
                return -2;

            case ByteCode.AALOAD:
            case ByteCode.ARETURN:
            case ByteCode.ASTORE:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.CALOAD:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FDIV:
            case ByteCode.FMUL:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.GETFIELD:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IDIV:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IMUL:
            case ByteCode.INVOKEINTERFACE:       //
            case ByteCode.INVOKESPECIAL:         // but needs to account for
            case ByteCode.INVOKEVIRTUAL:         // pops 'this' (unless static)
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LOOKUPSWITCH:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LUSHR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.POP:
            case ByteCode.PUTFIELD:
            case ByteCode.SALOAD:
            case ByteCode.TABLESWITCH:
                return -1;

            case ByteCode.ANEWARRAY:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.BREAKPOINT:
            case ByteCode.CHECKCAST:
            case ByteCode.D2L:
            case ByteCode.DALOAD:
            case ByteCode.DNEG:
            case ByteCode.F2I:
            case ByteCode.FNEG:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.GOTO_W:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2F:
            case ByteCode.I2S:
            case ByteCode.IINC:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.INEG:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKESTATIC:
            case ByteCode.L2D:
            case ByteCode.LALOAD:
            case ByteCode.LNEG:
            case ByteCode.NEWARRAY:
            case ByteCode.NOP:
            case ByteCode.PUTSTATIC:
            case ByteCode.RET:
            case ByteCode.RETURN:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 0;

            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.BIPUSH:
            case ByteCode.DUP:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2L:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FLOAD:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.I2D:
            case ByteCode.I2L:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.ILOAD:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.JSR:
            case ByteCode.JSR_W:
            case ByteCode.LDC:
            case ByteCode.LDC_W:
            case ByteCode.MULTIANEWARRAY:
            case ByteCode.NEW:
            case ByteCode.SIPUSH:
                return 1;

            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DLOAD:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDC2_W:
            case ByteCode.LLOAD:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
                return 2;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }

        /*
         * Number of bytes of operands generated after the opcode.
         * Not in use currently.
         */
/*
    int extra(int opcode)
    {
        switch (opcode) {
            case ByteCode.AALOAD:
            case ByteCode.AASTORE:
            case ByteCode.ACONST_NULL:
            case ByteCode.ALOAD_0:
            case ByteCode.ALOAD_1:
            case ByteCode.ALOAD_2:
            case ByteCode.ALOAD_3:
            case ByteCode.ARETURN:
            case ByteCode.ARRAYLENGTH:
            case ByteCode.ASTORE_0:
            case ByteCode.ASTORE_1:
            case ByteCode.ASTORE_2:
            case ByteCode.ASTORE_3:
            case ByteCode.ATHROW:
            case ByteCode.BALOAD:
            case ByteCode.BASTORE:
            case ByteCode.BREAKPOINT:
            case ByteCode.CALOAD:
            case ByteCode.CASTORE:
            case ByteCode.D2F:
            case ByteCode.D2I:
            case ByteCode.D2L:
            case ByteCode.DADD:
            case ByteCode.DALOAD:
            case ByteCode.DASTORE:
            case ByteCode.DCMPG:
            case ByteCode.DCMPL:
            case ByteCode.DCONST_0:
            case ByteCode.DCONST_1:
            case ByteCode.DDIV:
            case ByteCode.DLOAD_0:
            case ByteCode.DLOAD_1:
            case ByteCode.DLOAD_2:
            case ByteCode.DLOAD_3:
            case ByteCode.DMUL:
            case ByteCode.DNEG:
            case ByteCode.DREM:
            case ByteCode.DRETURN:
            case ByteCode.DSTORE_0:
            case ByteCode.DSTORE_1:
            case ByteCode.DSTORE_2:
            case ByteCode.DSTORE_3:
            case ByteCode.DSUB:
            case ByteCode.DUP2:
            case ByteCode.DUP2_X1:
            case ByteCode.DUP2_X2:
            case ByteCode.DUP:
            case ByteCode.DUP_X1:
            case ByteCode.DUP_X2:
            case ByteCode.F2D:
            case ByteCode.F2I:
            case ByteCode.F2L:
            case ByteCode.FADD:
            case ByteCode.FALOAD:
            case ByteCode.FASTORE:
            case ByteCode.FCMPG:
            case ByteCode.FCMPL:
            case ByteCode.FCONST_0:
            case ByteCode.FCONST_1:
            case ByteCode.FCONST_2:
            case ByteCode.FDIV:
            case ByteCode.FLOAD_0:
            case ByteCode.FLOAD_1:
            case ByteCode.FLOAD_2:
            case ByteCode.FLOAD_3:
            case ByteCode.FMUL:
            case ByteCode.FNEG:
            case ByteCode.FREM:
            case ByteCode.FRETURN:
            case ByteCode.FSTORE_0:
            case ByteCode.FSTORE_1:
            case ByteCode.FSTORE_2:
            case ByteCode.FSTORE_3:
            case ByteCode.FSUB:
            case ByteCode.I2B:
            case ByteCode.I2C:
            case ByteCode.I2D:
            case ByteCode.I2F:
            case ByteCode.I2L:
            case ByteCode.I2S:
            case ByteCode.IADD:
            case ByteCode.IALOAD:
            case ByteCode.IAND:
            case ByteCode.IASTORE:
            case ByteCode.ICONST_0:
            case ByteCode.ICONST_1:
            case ByteCode.ICONST_2:
            case ByteCode.ICONST_3:
            case ByteCode.ICONST_4:
            case ByteCode.ICONST_5:
            case ByteCode.ICONST_M1:
            case ByteCode.IDIV:
            case ByteCode.ILOAD_0:
            case ByteCode.ILOAD_1:
            case ByteCode.ILOAD_2:
            case ByteCode.ILOAD_3:
            case ByteCode.IMPDEP1:
            case ByteCode.IMPDEP2:
            case ByteCode.IMUL:
            case ByteCode.INEG:
            case ByteCode.IOR:
            case ByteCode.IREM:
            case ByteCode.IRETURN:
            case ByteCode.ISHL:
            case ByteCode.ISHR:
            case ByteCode.ISTORE_0:
            case ByteCode.ISTORE_1:
            case ByteCode.ISTORE_2:
            case ByteCode.ISTORE_3:
            case ByteCode.ISUB:
            case ByteCode.IUSHR:
            case ByteCode.IXOR:
            case ByteCode.L2D:
            case ByteCode.L2F:
            case ByteCode.L2I:
            case ByteCode.LADD:
            case ByteCode.LALOAD:
            case ByteCode.LAND:
            case ByteCode.LASTORE:
            case ByteCode.LCMP:
            case ByteCode.LCONST_0:
            case ByteCode.LCONST_1:
            case ByteCode.LDIV:
            case ByteCode.LLOAD_0:
            case ByteCode.LLOAD_1:
            case ByteCode.LLOAD_2:
            case ByteCode.LLOAD_3:
            case ByteCode.LMUL:
            case ByteCode.LNEG:
            case ByteCode.LOR:
            case ByteCode.LREM:
            case ByteCode.LRETURN:
            case ByteCode.LSHL:
            case ByteCode.LSHR:
            case ByteCode.LSTORE_0:
            case ByteCode.LSTORE_1:
            case ByteCode.LSTORE_2:
            case ByteCode.LSTORE_3:
            case ByteCode.LSUB:
            case ByteCode.LUSHR:
            case ByteCode.LXOR:
            case ByteCode.MONITORENTER:
            case ByteCode.MONITOREXIT:
            case ByteCode.NOP:
            case ByteCode.POP2:
            case ByteCode.POP:
            case ByteCode.RETURN:
            case ByteCode.SALOAD:
            case ByteCode.SASTORE:
            case ByteCode.SWAP:
            case ByteCode.WIDE:
                return 0;

            case ByteCode.ALOAD:
            case ByteCode.ASTORE:
            case ByteCode.BIPUSH:
            case ByteCode.DLOAD:
            case ByteCode.DSTORE:
            case ByteCode.FLOAD:
            case ByteCode.FSTORE:
            case ByteCode.ILOAD:
            case ByteCode.ISTORE:
            case ByteCode.LDC:
            case ByteCode.LLOAD:
            case ByteCode.LSTORE:
            case ByteCode.NEWARRAY:
            case ByteCode.RET:
                return 1;

            case ByteCode.ANEWARRAY:
            case ByteCode.CHECKCAST:
            case ByteCode.GETFIELD:
            case ByteCode.GETSTATIC:
            case ByteCode.GOTO:
            case ByteCode.IFEQ:
            case ByteCode.IFGE:
            case ByteCode.IFGT:
            case ByteCode.IFLE:
            case ByteCode.IFLT:
            case ByteCode.IFNE:
            case ByteCode.IFNONNULL:
            case ByteCode.IFNULL:
            case ByteCode.IF_ACMPEQ:
            case ByteCode.IF_ACMPNE:
            case ByteCode.IF_ICMPEQ:
            case ByteCode.IF_ICMPGE:
            case ByteCode.IF_ICMPGT:
            case ByteCode.IF_ICMPLE:
            case ByteCode.IF_ICMPLT:
            case ByteCode.IF_ICMPNE:
            case ByteCode.IINC:
            case ByteCode.INSTANCEOF:
            case ByteCode.INVOKEINTERFACE:
            case ByteCode.INVOKESPECIAL:
            case ByteCode.INVOKESTATIC:
            case ByteCode.INVOKEVIRTUAL:
            case ByteCode.JSR:
            case ByteCode.LDC2_W:
            case ByteCode.LDC_W:
            case ByteCode.NEW:
            case ByteCode.PUTFIELD:
            case ByteCode.PUTSTATIC:
            case ByteCode.SIPUSH:
                return 2;

            case ByteCode.MULTIANEWARRAY:
                return 3;

            case ByteCode.GOTO_W:
            case ByteCode.JSR_W:
                return 4;

            case ByteCode.LOOKUPSWITCH:    // depends on alignment
            case ByteCode.TABLESWITCH: // depends on alignment
                return -1;
        }
        throw new IllegalArgumentException("Bad opcode: "+opcode);
    }
*/
    private static String bytecodeStr(int code)
    {
        if (DEBUGSTACK || DEBUGCODE) {
            switch (code) {
                case ByteCode.NOP:              return "nop";
                case ByteCode.ACONST_NULL:      return "aconst_null";
                case ByteCode.ICONST_M1:        return "iconst_m1";
                case ByteCode.ICONST_0:         return "iconst_0";
                case ByteCode.ICONST_1:         return "iconst_1";
                case ByteCode.ICONST_2:         return "iconst_2";
                case ByteCode.ICONST_3:         return "iconst_3";
                case ByteCode.ICONST_4:         return "iconst_4";
                case ByteCode.ICONST_5:         return "iconst_5";
                case ByteCode.LCONST_0:         return "lconst_0";
                case ByteCode.LCONST_1:         return "lconst_1";
                case ByteCode.FCONST_0:         return "fconst_0";
                case ByteCode.FCONST_1:         return "fconst_1";
                case ByteCode.FCONST_2:         return "fconst_2";
                case ByteCode.DCONST_0:         return "dconst_0";
                case ByteCode.DCONST_1:         return "dconst_1";
                case ByteCode.BIPUSH:           return "bipush";
                case ByteCode.SIPUSH:           return "sipush";
                case ByteCode.LDC:              return "ldc";
                case ByteCode.LDC_W:            return "ldc_w";
                case ByteCode.LDC2_W:           return "ldc2_w";
                case ByteCode.ILOAD:            return "iload";
                case ByteCode.LLOAD:            return "lload";
                case ByteCode.FLOAD:            return "fload";
                case ByteCode.DLOAD:            return "dload";
                case ByteCode.ALOAD:            return "aload";
                case ByteCode.ILOAD_0:          return "iload_0";
                case ByteCode.ILOAD_1:          return "iload_1";
                case ByteCode.ILOAD_2:          return "iload_2";
                case ByteCode.ILOAD_3:          return "iload_3";
                case ByteCode.LLOAD_0:          return "lload_0";
                case ByteCode.LLOAD_1:          return "lload_1";
                case ByteCode.LLOAD_2:          return "lload_2";
                case ByteCode.LLOAD_3:          return "lload_3";
                case ByteCode.FLOAD_0:          return "fload_0";
                case ByteCode.FLOAD_1:          return "fload_1";
                case ByteCode.FLOAD_2:          return "fload_2";
                case ByteCode.FLOAD_3:          return "fload_3";
                case ByteCode.DLOAD_0:          return "dload_0";
                case ByteCode.DLOAD_1:          return "dload_1";
                case ByteCode.DLOAD_2:          return "dload_2";
                case ByteCode.DLOAD_3:          return "dload_3";
                case ByteCode.ALOAD_0:          return "aload_0";
                case ByteCode.ALOAD_1:          return "aload_1";
                case ByteCode.ALOAD_2:          return "aload_2";
                case ByteCode.ALOAD_3:          return "aload_3";
                case ByteCode.IALOAD:           return "iaload";
                case ByteCode.LALOAD:           return "laload";
                case ByteCode.FALOAD:           return "faload";
                case ByteCode.DALOAD:           return "daload";
                case ByteCode.AALOAD:           return "aaload";
                case ByteCode.BALOAD:           return "baload";
                case ByteCode.CALOAD:           return "caload";
                case ByteCode.SALOAD:           return "saload";
                case ByteCode.ISTORE:           return "istore";
                case ByteCode.LSTORE:           return "lstore";
                case ByteCode.FSTORE:           return "fstore";
                case ByteCode.DSTORE:           return "dstore";
                case ByteCode.ASTORE:           return "astore";
                case ByteCode.ISTORE_0:         return "istore_0";
                case ByteCode.ISTORE_1:         return "istore_1";
                case ByteCode.ISTORE_2:         return "istore_2";
                case ByteCode.ISTORE_3:         return "istore_3";
                case ByteCode.LSTORE_0:         return "lstore_0";
                case ByteCode.LSTORE_1:         return "lstore_1";
                case ByteCode.LSTORE_2:         return "lstore_2";
                case ByteCode.LSTORE_3:         return "lstore_3";
                case ByteCode.FSTORE_0:         return "fstore_0";
                case ByteCode.FSTORE_1:         return "fstore_1";
                case ByteCode.FSTORE_2:         return "fstore_2";
                case ByteCode.FSTORE_3:         return "fstore_3";
                case ByteCode.DSTORE_0:         return "dstore_0";
                case ByteCode.DSTORE_1:         return "dstore_1";
                case ByteCode.DSTORE_2:         return "dstore_2";
                case ByteCode.DSTORE_3:         return "dstore_3";
                case ByteCode.ASTORE_0:         return "astore_0";
                case ByteCode.ASTORE_1:         return "astore_1";
                case ByteCode.ASTORE_2:         return "astore_2";
                case ByteCode.ASTORE_3:         return "astore_3";
                case ByteCode.IASTORE:          return "iastore";
                case ByteCode.LASTORE:          return "lastore";
                case ByteCode.FASTORE:          return "fastore";
                case ByteCode.DASTORE:          return "dastore";
                case ByteCode.AASTORE:          return "aastore";
                case ByteCode.BASTORE:          return "bastore";
                case ByteCode.CASTORE:          return "castore";
                case ByteCode.SASTORE:          return "sastore";
                case ByteCode.POP:              return "pop";
                case ByteCode.POP2:             return "pop2";
                case ByteCode.DUP:              return "dup";
                case ByteCode.DUP_X1:           return "dup_x1";
                case ByteCode.DUP_X2:           return "dup_x2";
                case ByteCode.DUP2:             return "dup2";
                case ByteCode.DUP2_X1:          return "dup2_x1";
                case ByteCode.DUP2_X2:          return "dup2_x2";
                case ByteCode.SWAP:             return "swap";
                case ByteCode.IADD:             return "iadd";
                case ByteCode.LADD:             return "ladd";
                case ByteCode.FADD:             return "fadd";
                case ByteCode.DADD:             return "dadd";
                case ByteCode.ISUB:             return "isub";
                case ByteCode.LSUB:             return "lsub";
                case ByteCode.FSUB:             return "fsub";
                case ByteCode.DSUB:             return "dsub";
                case ByteCode.IMUL:             return "imul";
                case ByteCode.LMUL:             return "lmul";
                case ByteCode.FMUL:             return "fmul";
                case ByteCode.DMUL:             return "dmul";
                case ByteCode.IDIV:             return "idiv";
                case ByteCode.LDIV:             return "ldiv";
                case ByteCode.FDIV:             return "fdiv";
                case ByteCode.DDIV:             return "ddiv";
                case ByteCode.IREM:             return "irem";
                case ByteCode.LREM:             return "lrem";
                case ByteCode.FREM:             return "frem";
                case ByteCode.DREM:             return "drem";
                case ByteCode.INEG:             return "ineg";
                case ByteCode.LNEG:             return "lneg";
                case ByteCode.FNEG:             return "fneg";
                case ByteCode.DNEG:             return "dneg";
                case ByteCode.ISHL:             return "ishl";
                case ByteCode.LSHL:             return "lshl";
                case ByteCode.ISHR:             return "ishr";
                case ByteCode.LSHR:             return "lshr";
                case ByteCode.IUSHR:            return "iushr";
                case ByteCode.LUSHR:            return "lushr";
                case ByteCode.IAND:             return "iand";
                case ByteCode.LAND:             return "land";
                case ByteCode.IOR:              return "ior";
                case ByteCode.LOR:              return "lor";
                case ByteCode.IXOR:             return "ixor";
                case ByteCode.LXOR:             return "lxor";
                case ByteCode.IINC:             return "iinc";
                case ByteCode.I2L:              return "i2l";
                case ByteCode.I2F:              return "i2f";
                case ByteCode.I2D:              return "i2d";
                case ByteCode.L2I:              return "l2i";
                case ByteCode.L2F:              return "l2f";
                case ByteCode.L2D:              return "l2d";
                case ByteCode.F2I:              return "f2i";
                case ByteCode.F2L:              return "f2l";
                case ByteCode.F2D:              return "f2d";
                case ByteCode.D2I:              return "d2i";
                case ByteCode.D2L:              return "d2l";
                case ByteCode.D2F:              return "d2f";
                case ByteCode.I2B:              return "i2b";
                case ByteCode.I2C:              return "i2c";
                case ByteCode.I2S:              return "i2s";
                case ByteCode.LCMP:             return "lcmp";
                case ByteCode.FCMPL:            return "fcmpl";
                case ByteCode.FCMPG:            return "fcmpg";
                case ByteCode.DCMPL:            return "dcmpl";
                case ByteCode.DCMPG:            return "dcmpg";
                case ByteCode.IFEQ:             return "ifeq";
                case ByteCode.IFNE:             return "ifne";
                case ByteCode.IFLT:             return "iflt";
                case ByteCode.IFGE:             return "ifge";
                case ByteCode.IFGT:             return "ifgt";
                case ByteCode.IFLE:             return "ifle";
                case ByteCode.IF_ICMPEQ:        return "if_icmpeq";
                case ByteCode.IF_ICMPNE:        return "if_icmpne";
                case ByteCode.IF_ICMPLT:        return "if_icmplt";
                case ByteCode.IF_ICMPGE:        return "if_icmpge";
                case ByteCode.IF_ICMPGT:        return "if_icmpgt";
                case ByteCode.IF_ICMPLE:        return "if_icmple";
                case ByteCode.IF_ACMPEQ:        return "if_acmpeq";
                case ByteCode.IF_ACMPNE:        return "if_acmpne";
                case ByteCode.GOTO:             return "goto";
                case ByteCode.JSR:              return "jsr";
                case ByteCode.RET:              return "ret";
                case ByteCode.TABLESWITCH:      return "tableswitch";
                case ByteCode.LOOKUPSWITCH:     return "lookupswitch";
                case ByteCode.IRETURN:          return "ireturn";
                case ByteCode.LRETURN:          return "lreturn";
                case ByteCode.FRETURN:          return "freturn";
                case ByteCode.DRETURN:          return "dreturn";
                case ByteCode.ARETURN:          return "areturn";
                case ByteCode.RETURN:           return "return";
                case ByteCode.GETSTATIC:        return "getstatic";
                case ByteCode.PUTSTATIC:        return "putstatic";
                case ByteCode.GETFIELD:         return "getfield";
                case ByteCode.PUTFIELD:         return "putfield";
                case ByteCode.INVOKEVIRTUAL:    return "invokevirtual";
                case ByteCode.INVOKESPECIAL:    return "invokespecial";
                case ByteCode.INVOKESTATIC:     return "invokestatic";
                case ByteCode.INVOKEINTERFACE:  return "invokeinterface";
                case ByteCode.NEW:              return "new";
                case ByteCode.NEWARRAY:         return "newarray";
                case ByteCode.ANEWARRAY:        return "anewarray";
                case ByteCode.ARRAYLENGTH:      return "arraylength";
                case ByteCode.ATHROW:           return "athrow";
                case ByteCode.CHECKCAST:        return "checkcast";
                case ByteCode.INSTANCEOF:       return "instanceof";
                case ByteCode.MONITORENTER:     return "monitorenter";
                case ByteCode.MONITOREXIT:      return "monitorexit";
                case ByteCode.WIDE:             return "wide";
                case ByteCode.MULTIANEWARRAY:   return "multianewarray";
                case ByteCode.IFNULL:           return "ifnull";
                case ByteCode.IFNONNULL:        return "ifnonnull";
                case ByteCode.GOTO_W:           return "goto_w";
                case ByteCode.JSR_W:            return "jsr_w";
                case ByteCode.BREAKPOINT:       return "breakpoint";

                case ByteCode.IMPDEP1:          return "impdep1";
                case ByteCode.IMPDEP2:          return "impdep2";
            }
        }
        return "";
    }

    final char[] getCharBuffer(int minimalSize)
    {
        if (minimalSize > tmpCharBuffer.length) {
            int newSize = tmpCharBuffer.length * 2;
            if (minimalSize > newSize) { newSize = minimalSize; }
            tmpCharBuffer = new char[newSize];
        }
        return tmpCharBuffer;
    }

    private static final int LineNumberTableSize = 16;
    private static final int ExceptionTableSize = 4;

    private final static long FileHeaderConstant = 0xCAFEBABE0003002DL;
    // Set DEBUG flags to true to get better checking and progress info.
    private static final boolean DEBUGSTACK = false;
    private static final boolean DEBUGLABELS = false;
    private static final boolean DEBUGCODE = false;

    private String generatedClassName;

    private ExceptionTableEntry itsExceptionTable[];
    private int itsExceptionTableTop;

    private int itsLineNumberTable[];   // pack start_pc & line_number together
    private int itsLineNumberTableTop;

    private byte[] itsCodeBuffer = new byte[256];
    private int itsCodeBufferTop;

    private ConstantPool itsConstantPool;

    private ClassFileMethod itsCurrentMethod;
    private short itsStackTop;

    private short itsMaxStack;
    private short itsMaxLocals;

    private ObjArray itsMethods = new ObjArray();
    private ObjArray itsFields = new ObjArray();
    private ObjArray itsInterfaces = new ObjArray();

    private short itsFlags;
    private short itsThisClassIndex;
    private short itsSuperClassIndex;
    private short itsSourceFileNameIndex;

    private static final int MIN_LABEL_TABLE_SIZE = 32;
    private int[] itsLabelTable;
    private int itsLabelTableTop;

// itsFixupTable[i] = (label_index << 32) | fixup_site
    private static final int MIN_FIXUP_TABLE_SIZE = 40;
    private long[] itsFixupTable;
    private int itsFixupTableTop;
    private ObjArray itsVarDescriptors;

    private char[] tmpCharBuffer = new char[64];
}

final class ExceptionTableEntry
{

    ExceptionTableEntry(int startLabel, int endLabel,
                        int handlerLabel, short catchType)
    {
        itsStartLabel = startLabel;
        itsEndLabel = endLabel;
        itsHandlerLabel = handlerLabel;
        itsCatchType = catchType;
    }

    int itsStartLabel;
    int itsEndLabel;
    int itsHandlerLabel;
    short itsCatchType;
}

final class ClassFileField
{

    ClassFileField(short nameIndex, short typeIndex, short flags)
    {
        itsNameIndex = nameIndex;
        itsTypeIndex = typeIndex;
        itsFlags = flags;
        itsHasAttributes = false;
    }

    void setAttributes(short attr1, short attr2, short attr3, int index)
    {
        itsHasAttributes = true;
        itsAttr1 = attr1;
        itsAttr2 = attr2;
        itsAttr3 = attr3;
        itsIndex = index;
    }

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16(itsFlags, data, offset);
        offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
        offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
        if (!itsHasAttributes) {
            // write 0 attributes
            offset = ClassFileWriter.putInt16(0, data, offset);
        } else {
            offset = ClassFileWriter.putInt16(1, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr1, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr2, data, offset);
            offset = ClassFileWriter.putInt16(itsAttr3, data, offset);
            offset = ClassFileWriter.putInt16(itsIndex, data, offset);
        }
        return offset;
    }

    int getWriteSize()
    {
        int size = 2 * 3;
        if (!itsHasAttributes) {
            size += 2;
        } else {
            size += 2 + 2 * 4;
        }
        return size;
    }

    private short itsNameIndex;
    private short itsTypeIndex;
    private short itsFlags;
    private boolean itsHasAttributes;
    private short itsAttr1, itsAttr2, itsAttr3;
    private int itsIndex;
}

final class ClassFileMethod
{

    ClassFileMethod(short nameIndex, short typeIndex, short flags)
    {
        itsNameIndex = nameIndex;
        itsTypeIndex = typeIndex;
        itsFlags = flags;
    }

    void setCodeAttribute(byte codeAttribute[])
    {
        itsCodeAttribute = codeAttribute;
    }

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16(itsFlags, data, offset);
        offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
        offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
        // Code attribute only
        offset = ClassFileWriter.putInt16(1, data, offset);
        System.arraycopy(itsCodeAttribute, 0, data, offset,
                         itsCodeAttribute.length);
        offset += itsCodeAttribute.length;
        return offset;
    }

    int getWriteSize()
    {
        return 2 * 4 + itsCodeAttribute.length;
    }

    private short itsNameIndex;
    private short itsTypeIndex;
    private short itsFlags;
    private byte[] itsCodeAttribute;

}

final class ConstantPool
{

    ConstantPool(ClassFileWriter cfw)
    {
        this.cfw = cfw;
        itsTopIndex = 1;       // the zero'th entry is reserved
        itsPool = new byte[ConstantPoolSize];
        itsTop = 0;
    }

    private static final int ConstantPoolSize = 256;
    private static final byte
        CONSTANT_Class = 7,
        CONSTANT_Fieldref = 9,
        CONSTANT_Methodref = 10,
        CONSTANT_InterfaceMethodref = 11,
        CONSTANT_String = 8,
        CONSTANT_Integer = 3,
        CONSTANT_Float = 4,
        CONSTANT_Long = 5,
        CONSTANT_Double = 6,
        CONSTANT_NameAndType = 12,
        CONSTANT_Utf8 = 1;

    int write(byte[] data, int offset)
    {
        offset = ClassFileWriter.putInt16((short)itsTopIndex, data, offset);
        System.arraycopy(itsPool, 0, data, offset, itsTop);
        offset += itsTop;
        return offset;
    }

    int getWriteSize()
    {
        return 2 + itsTop;
    }

    int addConstant(int k)
    {
        ensure(5);
        itsPool[itsTop++] = CONSTANT_Integer;
        itsTop = ClassFileWriter.putInt32(k, itsPool, itsTop);
        return (short)(itsTopIndex++);
    }

    int addConstant(long k)
    {
        ensure(9);
        itsPool[itsTop++] = CONSTANT_Long;
        itsTop = ClassFileWriter.putInt64(k, itsPool, itsTop);
        int index = itsTopIndex;
        itsTopIndex += 2;
        return index;
    }

    int addConstant(float k)
    {
        ensure(5);
        itsPool[itsTop++] = CONSTANT_Float;
        int bits = Float.floatToIntBits(k);
        itsTop = ClassFileWriter.putInt32(bits, itsPool, itsTop);
        return itsTopIndex++;
    }

    int addConstant(double k)
    {
        ensure(9);
        itsPool[itsTop++] = CONSTANT_Double;
        long bits = Double.doubleToLongBits(k);
        itsTop = ClassFileWriter.putInt64(bits, itsPool, itsTop);
        int index = itsTopIndex;
        itsTopIndex += 2;
        return index;
    }

    int addConstant(String k)
    {
        int utf8Index = 0xFFFF & addUtf8(k);
        int theIndex = itsStringConstHash.getInt(utf8Index, -1);
        if (theIndex == -1) {
            theIndex = itsTopIndex++;
            ensure(3);
            itsPool[itsTop++] = CONSTANT_String;
            itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
            itsStringConstHash.put(utf8Index, theIndex);
        }
        return theIndex;
    }

    boolean isUnderUtfEncodingLimit(String s)
    {
        int strLen = s.length();
        if (strLen * 3 <= MAX_UTF_ENCODING_SIZE) {
            return true;
        } else if (strLen > MAX_UTF_ENCODING_SIZE) {
            return false;
        }
        return strLen == getUtfEncodingLimit(s, 0, strLen);
    }

    /**
     * Get maximum i such that <tt>start <= i <= end</tt> and
     * <tt>s.substring(start, i)</tt> fits JVM UTF string encoding limit.
     */
    int getUtfEncodingLimit(String s, int start, int end)
    {
        if ((end - start) * 3 <= MAX_UTF_ENCODING_SIZE) {
            return end;
        }
        int limit = MAX_UTF_ENCODING_SIZE;
        for (int i = start; i != end; i++) {
            int c = s.charAt(i);
            if (0 != c && c <= 0x7F) {
                --limit;
            } else if (c < 0x7FF) {
                limit -= 2;
            } else {
                limit -= 3;
            }
            if (limit < 0) {
                return i;
            }
        }
        return end;
    }

    short addUtf8(String k)
    {
        int theIndex = itsUtf8Hash.get(k, -1);
        if (theIndex == -1) {
            int strLen = k.length();
            boolean tooBigString;
            if (strLen > MAX_UTF_ENCODING_SIZE) {
                tooBigString = true;
            } else {
                tooBigString = false;
                // Ask for worst case scenario buffer when each char takes 3
                // bytes
                ensure(1 + 2 + strLen * 3);
                int top = itsTop;

                itsPool[top++] = CONSTANT_Utf8;
                top += 2; // skip length

                char[] chars = cfw.getCharBuffer(strLen);
                k.getChars(0, strLen, chars, 0);

                for (int i = 0; i != strLen; i++) {
                    int c = chars[i];
                    if (c != 0 && c <= 0x7F) {
                        itsPool[top++] = (byte)c;
                    } else if (c > 0x7FF) {
                        itsPool[top++] = (byte)(0xE0 | (c >> 12));
                        itsPool[top++] = (byte)(0x80 | ((c >> 6) & 0x3F));
                        itsPool[top++] = (byte)(0x80 | (c & 0x3F));
                    } else {
                        itsPool[top++] = (byte)(0xC0 | (c >> 6));
                        itsPool[top++] = (byte)(0x80 | (c & 0x3F));
                    }
                }

                int utfLen = top - (itsTop + 1 + 2);
                if (utfLen > MAX_UTF_ENCODING_SIZE) {
                    tooBigString = true;
                } else {
                    // Write back length
                    itsPool[itsTop + 1] = (byte)(utfLen >>> 8);
                    itsPool[itsTop + 2] = (byte)utfLen;

                    itsTop = top;
                    theIndex = itsTopIndex++;
                    itsUtf8Hash.put(k, theIndex);
                }
            }
            if (tooBigString) {
                throw new IllegalArgumentException("Too big string");
            }
        }
        return (short)theIndex;
    }

    private short addNameAndType(String name, String type)
    {
        short nameIndex = addUtf8(name);
        short typeIndex = addUtf8(type);
        ensure(5);
        itsPool[itsTop++] = CONSTANT_NameAndType;
        itsTop = ClassFileWriter.putInt16(nameIndex, itsPool, itsTop);
        itsTop = ClassFileWriter.putInt16(typeIndex, itsPool, itsTop);
        return (short)(itsTopIndex++);
    }

    short addClass(String className)
    {
        int theIndex = itsClassHash.get(className, -1);
        if (theIndex == -1) {
            String slashed = className;
            if (className.indexOf('.') > 0) {
                slashed = ClassFileWriter.getSlashedForm(className);
                theIndex = itsClassHash.get(slashed, -1);
                if (theIndex != -1) {
                    itsClassHash.put(className, theIndex);
                }
            }
            if (theIndex == -1) {
                int utf8Index = addUtf8(slashed);
                ensure(3);
                itsPool[itsTop++] = CONSTANT_Class;
                itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
                theIndex = itsTopIndex++;
                itsClassHash.put(slashed, theIndex);
                if (className != slashed) {
                    itsClassHash.put(className, theIndex);
                }
            }
        }
        return (short)theIndex;
    }

    short addFieldRef(String className, String fieldName, String fieldType)
    {
        FieldOrMethodRef ref = new FieldOrMethodRef(className, fieldName,
                                                    fieldType);

        int theIndex = itsFieldRefHash.get(ref, -1);
        if (theIndex == -1) {
            short ntIndex = addNameAndType(fieldName, fieldType);
            short classIndex = addClass(className);
            ensure(5);
            itsPool[itsTop++] = CONSTANT_Fieldref;
            itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
            itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
            theIndex = itsTopIndex++;
            itsFieldRefHash.put(ref, theIndex);
        }
        return (short)theIndex;
    }

    short addMethodRef(String className, String methodName,
                       String methodType)
    {
        FieldOrMethodRef ref = new FieldOrMethodRef(className, methodName,
                                                    methodType);

        int theIndex = itsMethodRefHash.get(ref, -1);
        if (theIndex == -1) {
            short ntIndex = addNameAndType(methodName, methodType);
            short classIndex = addClass(className);
            ensure(5);
            itsPool[itsTop++] = CONSTANT_Methodref;
            itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
            itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
            theIndex = itsTopIndex++;
            itsMethodRefHash.put(ref, theIndex);
        }
        return (short)theIndex;
    }

    short addInterfaceMethodRef(String className,
                                String methodName, String methodType)
    {
        short ntIndex = addNameAndType(methodName, methodType);
        short classIndex = addClass(className);
        ensure(5);
        itsPool[itsTop++] = CONSTANT_InterfaceMethodref;
        itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
        itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
        return (short)(itsTopIndex++);
    }

    void ensure(int howMuch)
    {
        if (itsTop + howMuch > itsPool.length) {
            int newCapacity = itsPool.length * 2;
            if (itsTop + howMuch > newCapacity) {
                newCapacity = itsTop + howMuch;
            }
            byte[] tmp = new byte[newCapacity];
            System.arraycopy(itsPool, 0, tmp, 0, itsTop);
            itsPool = tmp;
        }
    }

    private ClassFileWriter cfw;

    private static final int MAX_UTF_ENCODING_SIZE = 65535;

    private UintMap itsStringConstHash = new UintMap();
    private ObjToIntMap itsUtf8Hash = new ObjToIntMap();
    private ObjToIntMap itsFieldRefHash = new ObjToIntMap();
    private ObjToIntMap itsMethodRefHash = new ObjToIntMap();
    private ObjToIntMap itsClassHash = new ObjToIntMap();

    private int itsTop;
    private int itsTopIndex;
    private byte itsPool[];
}

final class FieldOrMethodRef
{
    FieldOrMethodRef(String className, String name, String type)
    {
        this.className = className;
        this.name = name;
        this.type = type;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (!(obj instanceof FieldOrMethodRef)) { return false; }
        FieldOrMethodRef x = (FieldOrMethodRef)obj;
        return className.equals(x.className)
            && name.equals(x.name)
            && type.equals(x.type);
    }

    @Override
    public int hashCode()
    {
        if (hashCode == -1) {
            int h1 = className.hashCode();
            int h2 = name.hashCode();
            int h3 = type.hashCode();
            hashCode = h1 ^ h2 ^ h3;
        }
        return hashCode;
    }

    private String className;
    private String name;
    private String type;
    private int hashCode = -1;
}
