/* BytecodeInfo Copyright (C) 1999-2002 Jochen Hoenicke.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; see the file COPYING.LESSER.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: BytecodeInfo.java.in,v 4.9.2.8 2002/06/11 08:40:31 hoenicke Exp $
 */

package jode.bytecode;
import jode.GlobalOptions;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.BitSet;
import java.util.Stack;
import java.util.Vector;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import @COLLECTIONS@.List;
import @COLLECTIONS@.AbstractSequentialList;
import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.ListIterator;


/**
 * This class represents the byte code of a method.  Each instruction is
 * stored in an Instruction instance.
 *
 * We canonicalize some opcodes:  wide opcodes are mapped to short ones,
 * opcodes that load a constant are mapped to opc_ldc or opc_ldc2_w, and
 * opc_xload_x / opc_xstore_x opcodes are mapped to opc_xload / opc_xstore.
 */
public class BytecodeInfo extends BinaryInfo implements Opcodes {

    private MethodInfo methodInfo;
    private int maxStack, maxLocals;
    private Handler[] exceptionHandlers;
    private LocalVariableInfo[] lvt;
    private LineNumber[] lnt;
    
    /**
     * A array of instructions, indexed by address.  This array is only
     * valid while reading the code.
     */
    private Instruction[] instrs;

    private InstructionList instructions;

    private class InstructionList extends AbstractSequentialList {
	Instruction borderInstr;
	int instructionCount = 0;

	InstructionList() {
	    // opc_impdep1 is used as border instruction, it may not
	    // occur in bytecode.
	    borderInstr = new Instruction(opc_impdep1);
	    borderInstr.nextByAddr = borderInstr.prevByAddr = borderInstr;
	}

	public int size() {
	    return instructionCount;
	}

	Instruction get0(int index) {
	    Instruction instr = borderInstr;
	    if (index < instructionCount / 2) {
		for (int i=0; i <= index; i++)
		    instr = instr.nextByAddr;
	    } else {
		for (int i=instructionCount; i > index; i--)
		    instr = instr.prevByAddr;
	    }
	    return instr;
	}

	public Object get(int index) {
	    if (index < 0 || index >= instructionCount)
		throw new IllegalArgumentException();
	    return get0(index);
	}

	public boolean add(Object o) {
	    /* optimize add, since it is called many times by read() */
	    instructionCount++;
	    borderInstr.prevByAddr.appendInstruction((Instruction) o,
						     BytecodeInfo.this);
	    return true;
	}

	public ListIterator listIterator(final int startIndex) {
	    if (startIndex < 0 || startIndex > instructionCount)
		throw new IllegalArgumentException();
	    return new ListIterator() {
		Instruction instr = get0(startIndex);
		Instruction toRemove = null;
		int index = startIndex;
		
		public boolean hasNext() {
		    return index < instructionCount;
		}

		public boolean hasPrevious() {
		    return index > 0;
		}
		
		public Object next() {
		    if (index >= instructionCount)
			throw new NoSuchElementException();
		    index++;
		    toRemove = instr;
		    instr = instr.nextByAddr;
//  		    System.err.println("next: "+toRemove.getDescription());
		    return toRemove;
		}

		public Object previous() {
		    if (index == 0)
			throw new NoSuchElementException();
		    index--;
		    instr = instr.prevByAddr;
		    toRemove = instr;
//  		    System.err.println("prev: "+toRemove.getDescription());
		    return toRemove;
		}

		public int nextIndex() {
		    return index;
		}

		public int previousIndex() {
		    return index - 1;
		}
		
		public void remove() {
		    if (toRemove == null)
			throw new IllegalStateException();
//  		    System.err.println("remove: "+toRemove.getDescription());
		    instructionCount--;
		    if (instr == toRemove)
			instr = instr.nextByAddr;
		    else
			index--;
		    toRemove.removeInstruction(BytecodeInfo.this);
		    toRemove = null;
		}

		public void add(Object o) {
		    instructionCount++;
		    index++;
//  		    System.err.println("add: "
//  				       +((Instruction)o).getDescription()
//  				       +" after "+instr.prevByAddr
//  				       .getDescription());
		    instr.prevByAddr.appendInstruction((Instruction) o,
						       BytecodeInfo.this);
		    toRemove = null;
		}

		public void set(Object o) {
		    if (toRemove == null)
			throw new IllegalStateException();
//  		    System.err.println("replace "+toRemove.getDescription()
//  				       +" with "
//  				       +((Instruction)o).getDescription());
		    toRemove.replaceInstruction((Instruction) o, 
						BytecodeInfo.this);
		    if (instr == toRemove)
			instr = (Instruction) o;
		    toRemove = (Instruction) o;
		}
	    };
	}

	void setLastAddr(int addr) {
	    borderInstr.setAddr(addr);
	}

	int getCodeLength() {
	    return borderInstr.getAddr();
	}
    }

    public BytecodeInfo(MethodInfo mi) {
	methodInfo = mi;
    }

    private final static Object[] constants = {
	null, 
	new Integer(-1), new Integer(0), new Integer(1), 
	new Integer(2), new Integer(3), new Integer(4), new Integer(5),
	new Long(0), new Long(1), 
	new Float(0), new Float(1), new Float(2),
	new Double(0), new Double(1)
    };

    protected void readAttribute(String name, int length, ConstantPool cp,
				 DataInputStream input, 
				 int howMuch) throws IOException {
	if ((howMuch & KNOWNATTRIBS) != 0 
	    && name.equals("LocalVariableTable")) {
	    if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0) 
		GlobalOptions.err.println("LocalVariableTable of "+methodInfo);
            int count = input.readUnsignedShort();
	    if (length != 2 + count * 10) {
		if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0) 
		    GlobalOptions.err.println("Illegal LVT length, ignoring it");
		return;
	    }
	    lvt = new LocalVariableInfo[count];
            for (int i=0; i < count; i++) {
		lvt[i] = new LocalVariableInfo();
                int start  = input.readUnsignedShort();
                int end    = start + input.readUnsignedShort();
		int nameIndex = input.readUnsignedShort();
		int typeIndex = input.readUnsignedShort();
		int slot = input.readUnsignedShort();
		Instruction startInstr = 
		    start >= 0 && start < instrs.length ? instrs[start] : null;
		Instruction endInstr;
		if (end >=0 && end < instrs.length)
		    endInstr = instrs[end] == null ? null
			: instrs[end].getPrevByAddr();
		else {
		    endInstr = null;
		    for (int nr = instrs.length - 1; nr >= 0; nr--) {
			if (instrs[nr] != null) {
			    if (instrs[nr].getNextAddr() == end)
				endInstr = instrs[nr];
			    break;
			}
		    }
		}

		if (startInstr == null
		    || endInstr == null
		    || nameIndex == 0 || typeIndex == 0
		    || slot >= maxLocals
		    || cp.getTag(nameIndex) != cp.UTF8
		    || cp.getTag(typeIndex) != cp.UTF8) {

		    // This is probably an evil lvt as created by HashJava
		    // simply ignore it.
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_LVT) != 0) 
			GlobalOptions.err.println
			    ("Illegal entry, ignoring LVT");
		    lvt = null;
		    return;
		}
		lvt[i].start = startInstr;
		lvt[i].end = endInstr;
		lvt[i].name = cp.getUTF8(nameIndex);
                lvt[i].type = cp.getUTF8(typeIndex);
                lvt[i].slot = slot;
                if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_LVT) != 0)
                    GlobalOptions.err.println("\t" + lvt[i].name + ": "
					   + lvt[i].type
					   +" range "+start+" - "+end
					   +" slot "+slot);
            }
	} else if ((howMuch & KNOWNATTRIBS) != 0 
		   && name.equals("LineNumberTable")) {
	    int count = input.readUnsignedShort();
	    if (length != 2 + count * 4) {
		GlobalOptions.err.println
		    ("Illegal LineNumberTable, ignoring it");
		return;
	    }
	    lnt = new LineNumber[count];
	    for (int i = 0; i < count; i++) {
		lnt[i] = new LineNumber();
		int start = input.readUnsignedShort();
		Instruction startInstr = instrs[start];
		if (startInstr == null) {
		    GlobalOptions.err.println
			("Illegal entry, ignoring LineNumberTable table");
		    lnt = null;
		    return;
		}
		lnt[i].start = startInstr;
		lnt[i].linenr = input.readUnsignedShort();
	    }
	} else
	    super.readAttribute(name, length, cp, input, howMuch);
    }

    public void read(ConstantPool cp, 
		     DataInputStream input) throws IOException {
        maxStack = input.readUnsignedShort();
        maxLocals = input.readUnsignedShort();
	instructions = new InstructionList();
        int codeLength = input.readInt();
	instrs = new Instruction[codeLength];
	int[][] succAddrs = new int[codeLength][];
	{
	    int addr = 0;
	    while (addr < codeLength) {
		Instruction instr;
		int length;
		int opcode = input.readUnsignedByte();
		if ((GlobalOptions.debuggingFlags
		     & GlobalOptions.DEBUG_BYTECODE) != 0) 
		if ((GlobalOptions.debuggingFlags
		     & GlobalOptions.DEBUG_BYTECODE) != 0) 
		    GlobalOptions.err.print(addr+": "+opcodeString[opcode]);

		switch (opcode) {
		case opc_wide: {
		    int wideopcode = input.readUnsignedByte();
		    switch (wideopcode) {
		    case opc_iload: case opc_fload: case opc_aload:
		    case opc_istore: case opc_fstore: case opc_astore: {
			int slot = input.readUnsignedShort();
			if (slot >= maxLocals)
			    throw new ClassFormatError
				("Invalid local slot "+slot);
			instr = new Instruction(wideopcode);
			instr.setLocalSlot(slot);
			length = 4;
			if ((GlobalOptions.debuggingFlags
			     & GlobalOptions.DEBUG_BYTECODE) != 0) 
			    GlobalOptions.err.print
				(" " + opcodeString[wideopcode] + " " + slot);
			break;
		    }
		    case opc_lload: case opc_dload:
		    case opc_lstore: case opc_dstore: {
			int slot = input.readUnsignedShort();
			if (slot >= maxLocals-1)
			    throw new ClassFormatError
				("Invalid local slot "+slot);
			instr = new Instruction(wideopcode);
			instr.setLocalSlot(slot);
			length = 4;
			if ((GlobalOptions.debuggingFlags
			     & GlobalOptions.DEBUG_BYTECODE) != 0) 
			    GlobalOptions.err.print
				(" " + opcodeString[wideopcode] + " " + slot);
			break;
		    }
		    case opc_ret: {
			int slot = input.readUnsignedShort();
			if (slot >= maxLocals)
			    throw new ClassFormatError
				("Invalid local slot "+slot);
			instr = new Instruction(wideopcode);
			instr.setLocalSlot(slot);
			length = 4;
			if ((GlobalOptions.debuggingFlags
			     & GlobalOptions.DEBUG_BYTECODE) != 0) 
			    GlobalOptions.err.print(" ret "+slot);
			break;
		    }
		    case opc_iinc: {
			int slot = input.readUnsignedShort();
			if (slot >= maxLocals)
			    throw new ClassFormatError
				("Invalid local slot "+slot);
			instr = new Instruction(wideopcode);
			instr.setLocalSlot(slot);
			instr.setIncrement(input.readShort());
			length = 6;
			if ((GlobalOptions.debuggingFlags
			     & GlobalOptions.DEBUG_BYTECODE) != 0) 
			    GlobalOptions.err.print
				(" iinc " + slot + " " + instr.getIncrement());
			break;
		    }
		    default:
			throw new ClassFormatError("Invalid wide opcode "
						   +wideopcode);
		    }
		    break;
		}
		case opc_iload_0: case opc_iload_1:
		case opc_iload_2: case opc_iload_3:
		case opc_lload_0: case opc_lload_1:
		case opc_lload_2: case opc_lload_3:
		case opc_fload_0: case opc_fload_1:
		case opc_fload_2: case opc_fload_3:
		case opc_dload_0: case opc_dload_1:
		case opc_dload_2: case opc_dload_3:
		case opc_aload_0: case opc_aload_1:
		case opc_aload_2: case opc_aload_3: {
		    int slot = (opcode-opc_iload_0) & 3;
		    if (slot >= maxLocals)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opc_iload + 
					    (opcode-opc_iload_0)/4);
		    instr.setLocalSlot(slot);
		    length = 1;
		    break;
		}
		case opc_istore_0: case opc_istore_1: 
		case opc_istore_2: case opc_istore_3:
		case opc_fstore_0: case opc_fstore_1:
		case opc_fstore_2: case opc_fstore_3:
		case opc_astore_0: case opc_astore_1:
		case opc_astore_2: case opc_astore_3: {
		    int slot = (opcode-opc_istore_0) & 3;
		    if (slot >= maxLocals)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opc_istore + 
					     (opcode-opc_istore_0)/4);
		    instr.setLocalSlot(slot);
		    length = 1;
		    break;
		}
		case opc_lstore_0: case opc_lstore_1: 
		case opc_lstore_2: case opc_lstore_3:
		case opc_dstore_0: case opc_dstore_1:
		case opc_dstore_2: case opc_dstore_3: {
		    int slot = (opcode-opc_istore_0) & 3;
		    if (slot >= maxLocals-1)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opc_istore
					     + (opcode-opc_istore_0)/4);
		    instr.setLocalSlot(slot);
		    length = 1;
		    break;
		}
		case opc_iload: case opc_fload: case opc_aload:
		case opc_istore: case opc_fstore: case opc_astore: {
		    int slot = input.readUnsignedByte();
		    if (slot >= maxLocals)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opcode);
		    instr.setLocalSlot(slot);
		    length = 2;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+slot);
		    break;
		}
		case opc_lstore: case opc_dstore:
		case opc_lload: case opc_dload: {
		    int slot = input.readUnsignedByte();
		    if (slot >= maxLocals - 1)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opcode);
		    instr.setLocalSlot(slot);
		    length = 2;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+slot);
		    break;
		}
		case opc_ret: {
		    int slot = input.readUnsignedByte();
		    if (slot >= maxLocals)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opcode);
		    instr.setLocalSlot(slot);
		    length = 2;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+slot);
		    break;
		}
		case opc_aconst_null:
		case opc_iconst_m1: 
		case opc_iconst_0: case opc_iconst_1: case opc_iconst_2:
		case opc_iconst_3: case opc_iconst_4: case opc_iconst_5:
		case opc_fconst_0: case opc_fconst_1: case opc_fconst_2:
		    instr = new Instruction(opc_ldc);
		    instr.setConstant
			(constants[opcode - opc_aconst_null]);
		    length = 1;
		    break;
		case opc_lconst_0: case opc_lconst_1:
		case opc_dconst_0: case opc_dconst_1:
		    instr = new Instruction(opc_ldc2_w);
		    instr.setConstant
			(constants[opcode - opc_aconst_null]);
		    length = 1;
		    break;
		case opc_bipush:
		    instr = new Instruction(opc_ldc);
		    instr.setConstant(new Integer(input.readByte()));
		    length = 2;
		    break;
		case opc_sipush:
		    instr = new Instruction(opc_ldc);
		    instr.setConstant(new Integer(input.readShort()));
		    length = 3;
		    break;
		case opc_ldc: {
		    int index = input.readUnsignedByte();
		    int tag = cp.getTag(index);
		    if (tag != cp.STRING
			 && tag != cp.INTEGER && tag != cp.FLOAT)
			throw new ClassFormatException
			    ("wrong constant tag: "+tag);
		    instr = new Instruction(opcode);
		    instr.setConstant(cp.getConstant(index));
		    length = 2;
		    break;
		}
		case opc_ldc_w: {
		    int index = input.readUnsignedShort();
		    int tag = cp.getTag(index);
		    if (tag != cp.STRING
			 && tag != cp.INTEGER && tag != cp.FLOAT)
			throw new ClassFormatException
			    ("wrong constant tag: "+tag);
		    instr = new Instruction(opc_ldc);
		    instr.setConstant(cp.getConstant(index));
		    length = 3;
		    break;
		}
		case opc_ldc2_w: {
		    int index = input.readUnsignedShort();
		    int tag = cp.getTag(index);
		    if (tag != cp.LONG && tag != cp.DOUBLE)
			throw new ClassFormatException
			    ("wrong constant tag: "+tag);
		    instr = new Instruction(opcode);
		    instr.setConstant(cp.getConstant(index));
		    length = 3;
		    break;
		}
		case opc_iinc: {
		    int slot = input.readUnsignedByte();
		    if (slot >= maxLocals)
			throw new ClassFormatError
			    ("Invalid local slot "+slot);
		    instr = new Instruction(opcode);
		    instr.setLocalSlot(slot);
		    instr.setIncrement(input.readByte());
		    length = 3;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print
			    (" " + slot + " " + instr.getIncrement());
		    break;
		}
		case opc_goto:
		case opc_jsr:
		case opc_ifeq: case opc_ifne: 
		case opc_iflt: case opc_ifge: 
		case opc_ifgt: case opc_ifle:
		case opc_if_icmpeq: case opc_if_icmpne:
		case opc_if_icmplt: case opc_if_icmpge: 
		case opc_if_icmpgt: case opc_if_icmple:
		case opc_if_acmpeq: case opc_if_acmpne:
		case opc_ifnull: case opc_ifnonnull:
		    instr = new Instruction(opcode);
		    length = 3;
		    succAddrs[addr] = new int[] { addr+input.readShort() };
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+succAddrs[addr][0]);
		    break;

		case opc_goto_w:
		case opc_jsr_w:
		    instr = new Instruction(opcode - (opc_goto_w - opc_goto));
		    length = 5;
		    succAddrs[addr] = new int[] { addr+input.readInt() };
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+succAddrs[addr][0]);
		    break;

		case opc_tableswitch: {
		    length = 3 - (addr % 4);
		    input.readFully(new byte[length]);
		    int def  = input.readInt();
		    int low  = input.readInt();
		    int high = input.readInt();
		    int[] dests = new int[high-low+1];
		    int npairs = 0;
		    for (int i=0; i < dests.length; i++) {
			dests[i] = input.readInt();
			if (dests[i] != def)
			    npairs++;
		    }
		    instr = new Instruction(opc_lookupswitch);
		    succAddrs[addr] = new int[npairs + 1];
		    int[] values = new int[npairs];
		    int pos = 0;
		    for (int i=0; i < dests.length; i++) {
			if (dests[i] != def) {
			    values[pos] = i+low;
			    succAddrs[addr][pos] = addr + dests[i];
			    pos++;
			}
		    }
		    succAddrs[addr][npairs] = addr + def;
		    instr.setValues(values);
		    length += 13 + 4 * (high-low+1);
		    break;
		}
		case opc_lookupswitch: {
		    length = 3 - (addr % 4);
		    input.readFully(new byte[length]);
		    int def = input.readInt();
		    int npairs = input.readInt();
		    instr = new Instruction(opcode);
		    succAddrs[addr] = new int[npairs + 1];
		    int[] values = new int[npairs];
		    for (int i=0; i < npairs; i++) {
			values[i] = input.readInt();
			if (i > 0 && values[i-1] >= values[i])
			    throw new ClassFormatException
				("lookupswitch not sorted");
			succAddrs[addr][i] = addr + input.readInt();
		    }
		    succAddrs[addr][npairs] = addr + def;
		    instr.setValues(values);
		    length += 9 + 8 * npairs;
		    break;
		}
			    
		case opc_getstatic:
		case opc_getfield:
		case opc_putstatic:
		case opc_putfield:
		case opc_invokespecial:
		case opc_invokestatic:
		case opc_invokevirtual: {
		    int index = input.readUnsignedShort();
		    int tag = cp.getTag(index);
		    if (opcode < opc_invokevirtual) {
			if (tag != cp.FIELDREF)
			    throw new ClassFormatException
				("field tag mismatch: "+tag);
		    } else {
			if (tag != cp.METHODREF)
			    throw new ClassFormatException
				("method tag mismatch: "+tag);
		    }
		    Reference ref = cp.getRef(index);
		    if (ref.getName().charAt(0) == '<'
			&& (!ref.getName().equals("<init>")
			    || opcode != opc_invokespecial))
			throw new ClassFormatException
			    ("Illegal call of special method/field "+ref);
		    instr = new Instruction(opcode);
		    instr.setReference(ref);
		    length = 3;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+ref);
		    break;
		}
		case opc_invokeinterface: {
		    int index = input.readUnsignedShort();
		    int tag = cp.getTag(index);
		    if (tag != cp.INTERFACEMETHODREF)
			throw new ClassFormatException
			    ("interface tag mismatch: "+tag);
		    Reference ref = cp.getRef(index);
		    if (ref.getName().charAt(0) == '<')
			throw new ClassFormatException
			    ("Illegal call of special method "+ref);
		    int nargs = input.readUnsignedByte();
		    if (TypeSignature.getArgumentSize(ref.getType())
			!= nargs - 1)
			throw new ClassFormatException
			    ("Interface nargs mismatch: "+ref+" vs. "+nargs);
		    if (input.readUnsignedByte() != 0)
			throw new ClassFormatException
			    ("Interface reserved param not zero");

		    instr = new Instruction(opcode);
		    instr.setReference(ref);
		    length = 5;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+ref);
		    break;
		}

		case opc_new:
		case opc_checkcast:
		case opc_instanceof: {
		    String type = cp.getClassType(input.readUnsignedShort());
		    if (opcode == opc_new && type.charAt(0) == '[')
			throw new ClassFormatException
			    ("Can't create array with opc_new");
		    instr = new Instruction(opcode);
		    instr.setClazzType(type);
		    length = 3;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+type);
		    break;
		}
		case opc_multianewarray: {
		    String type = cp.getClassType(input.readUnsignedShort());
		    int dims = input.readUnsignedByte();
		    if (dims == 0)
			throw new ClassFormatException
			    ("multianewarray dimension is 0.");
		    for (int i=0; i < dims; i++) {
			/* Note that since type is a valid type
			 * signature, there must be a non bracket
			 * character, before the string is over.  
			 * So there is no StringIndexOutOfBoundsException.
			 */
			if (type.charAt(i) != '[')
			    throw new ClassFormatException
				("multianewarray called for non array:"+ type);
		    }
		    instr = new Instruction(opcode);
		    instr.setClazzType(type);
		    instr.setDimensions(dims);
		    length = 4;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" " + type + " " + dims);
		    break;
		}
		case opc_anewarray: {
		    String type = cp.getClassType(input.readUnsignedShort());
		    instr = new Instruction(opc_multianewarray);
		    instr.setClazzType(("["+type).intern());
		    instr.setDimensions(1);
		    length = 3;
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+type);
		    break;
		}
		case opc_newarray: {
		    char sig = newArrayTypes.charAt
			(input.readUnsignedByte()-4);
		    String type = new String (new char[] { '[', sig });
		    if ((GlobalOptions.debuggingFlags
			 & GlobalOptions.DEBUG_BYTECODE) != 0) 
			GlobalOptions.err.print(" "+type);
		    instr = new Instruction(opc_multianewarray);
		    instr.setClazzType(type);
		    instr.setDimensions(1);
		    length = 2;
		    break;
		}
		
		case opc_nop:
		case opc_iaload: case opc_laload: case opc_faload:
		case opc_daload: case opc_aaload:
		case opc_baload: case opc_caload: case opc_saload:
		case opc_iastore: case opc_lastore: case opc_fastore:
		case opc_dastore: case opc_aastore:
		case opc_bastore: case opc_castore: case opc_sastore:
		case opc_pop: case opc_pop2:
		case opc_dup: case opc_dup_x1: case opc_dup_x2:
		case opc_dup2: case opc_dup2_x1: case opc_dup2_x2:
		case opc_swap:
		case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd:
		case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub:
		case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul:
		case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv:
		case opc_irem: case opc_lrem: case opc_frem: case opc_drem:
		case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg:
		case opc_ishl: case opc_lshl:
		case opc_ishr: case opc_lshr:
		case opc_iushr: case opc_lushr: 
		case opc_iand: case opc_land:
		case opc_ior: case opc_lor: 
		case opc_ixor: case opc_lxor:
		case opc_i2l: case opc_i2f: case opc_i2d:
		case opc_l2i: case opc_l2f: case opc_l2d:
		case opc_f2i: case opc_f2l: case opc_f2d:
		case opc_d2i: case opc_d2l: case opc_d2f:
		case opc_i2b: case opc_i2c: case opc_i2s:
		case opc_lcmp: case opc_fcmpl: case opc_fcmpg:
		case opc_dcmpl: case opc_dcmpg:
		case opc_ireturn: case opc_lreturn: 
		case opc_freturn: case opc_dreturn: case opc_areturn:
		case opc_return: 
		case opc_athrow:
		case opc_arraylength:
		case opc_monitorenter: case opc_monitorexit:
		    instr = new Instruction(opcode);
		    length = 1;
		    break;
		default:
		    throw new ClassFormatError("Invalid opcode "+opcode);
		}
		if ((GlobalOptions.debuggingFlags
		     & GlobalOptions.DEBUG_BYTECODE) != 0) 
		    GlobalOptions.err.println();

		instrs[addr] = instr;
		instructions.add(instr);

		addr += length;
		instructions.setLastAddr(addr);
	    }
	    if (addr != codeLength)
		throw new ClassFormatError("last instruction too long");
	}
	for (Iterator iter = instructions.iterator(); iter.hasNext(); ) {
	    Instruction instr = (Instruction) iter.next();
	    int addr = instr.getAddr();
	    if (succAddrs[addr] != null) {
		int length = succAddrs[addr].length;
		Instruction[] succs = new Instruction[length];
		for (int i=0; i < length; i++) {
		    int succAddr = succAddrs[addr][i];
		    if (succAddr < 0 || succAddr > codeLength
			|| instrs[succAddr] == null)
			throw new ClassFormatException
			    ("Illegal jump target at "
			     +this+"@"+addr);
		    succs[i] = instrs[succAddr];
		}
		instr.setSuccs(succs);
	    }
	}
	succAddrs = null;

	{
	    int handlersLength = input.readUnsignedShort();
	    exceptionHandlers = new Handler[handlersLength];
	    for (int i=0; i< handlersLength; i ++) {
		exceptionHandlers[i] = new Handler();
		exceptionHandlers[i].start
		    = instrs[input.readUnsignedShort()];
		exceptionHandlers[i].end
		    = instrs[input.readUnsignedShort()].getPrevByAddr();
		exceptionHandlers[i].catcher
		    = instrs[input.readUnsignedShort()];
		int index = input.readUnsignedShort();
		exceptionHandlers[i].type = (index == 0) ? null
		    : cp.getClassName(index);

		if (exceptionHandlers[i].catcher.getOpcode() == opc_athrow) {
		    /* There is an obfuscator, which inserts bogus
		     * exception entries jumping directly to a throw
		     * instruction.  Remove those handlers.
		     */
		    handlersLength--;
		    i--;
		    continue;
		}

		if (exceptionHandlers[i].start.getAddr()
		       <= exceptionHandlers[i].catcher.getAddr()
		    && exceptionHandlers[i].end.getAddr()
		       >= exceptionHandlers[i].catcher.getAddr())
		{
		    /* Javac 1.4 is a bit paranoid with finally and
		     * synchronize blocks and even breaks the JLS.
		     * We fix it here.  Hopefully this won't produce
		     * any other problems.
		     */
		    if (exceptionHandlers[i].start
			== exceptionHandlers[i].catcher) {
			handlersLength--;
			i--;
		    } else {
			exceptionHandlers[i].end = 
			    exceptionHandlers[i].catcher.getPrevByAddr();
		    }
		}
	    }
	    if (handlersLength < exceptionHandlers.length) {
		Handler[] newHandlers = new Handler[handlersLength];
		System.arraycopy(exceptionHandlers, 0, newHandlers, 0,
				 handlersLength);
		exceptionHandlers = newHandlers;
	    }
	}
	readAttributes(cp, input, FULLINFO);
	instrs = null;
    }

    public void dumpCode(java.io.PrintWriter output) {
	for (Iterator iter = instructions.iterator(); iter.hasNext(); ) {
	    Instruction instr = (Instruction) iter.next();
	    output.println(instr.getDescription() + " "
			   + Integer.toHexString(hashCode()));
	    Instruction[] succs = instr.getSuccs();
	    if (succs != null) {
		output.print("\tsuccs: "+succs[0]);
		for (int i = 1; i < succs.length; i++)
		    output.print(", "+succs[i]);
		output.println();
	    }
	    if (instr.getPreds() != null) {
		output.print("\tpreds: " + instr.getPreds()[0]);
		for (int i=1; i < instr.getPreds().length; i++)
		    output.print(", " + instr.getPreds()[i]);
		output.println();
	    }
	}
	for (int i=0; i< exceptionHandlers.length; i++) {
	    output.println("catch " + exceptionHandlers[i].type 
			   + " from " + exceptionHandlers[i].start 
			   + " to " + exceptionHandlers[i].end
			   + " catcher " + exceptionHandlers[i].catcher);
	}
    }

    public void reserveSmallConstants(GrowableConstantPool gcp) {
    next_instr:
	for (Iterator iter = instructions.iterator(); iter.hasNext(); ) {
	    Instruction instr = (Instruction) iter.next();
	    if (instr.getOpcode() == opc_ldc) {
		Object constant = instr.getConstant();
		if (constant == null)
		    continue next_instr;
		for (int i=1; i < constants.length; i++) {
		    if (constant.equals(constants[i]))
			continue next_instr;
		}
		if (constant instanceof Integer) {
		    int value = ((Integer) constant).intValue();
		    if (value >= Short.MIN_VALUE
			&& value <= Short.MAX_VALUE)
			continue next_instr;
		}
		gcp.reserveConstant(constant);
	    }
	}
    }

    private void calculateMaxStack() {
	maxStack = 0;
	int[] stackHeights = new int[instructions.getCodeLength()];
	int[] poppush = new int[2];
	Stack todo = new Stack();

	for (int i=0; i < stackHeights.length; i++)
	    stackHeights[i] = -1;

	stackHeights[0] = 0;
	todo.push(instructions.get(0));
	while (!todo.isEmpty()) {
	    Instruction instr = (Instruction) todo.pop();
	    Instruction next = instr.getNextByAddr();
	    Instruction[] succs = instr.getSuccs();
	    int addr = instr.getAddr();
	    instr.getStackPopPush(poppush);
	    int sh = stackHeights[addr] - poppush[0] + poppush[1];
// 	    System.err.println("Instr: "+instr.getDescription()+
// 			       "; before: "+stackHeights[addr]+" after: "+sh);
	    if (maxStack < sh)
		maxStack = sh;
	    if (instr.getOpcode() == opc_jsr) {
		if (stackHeights[next.getAddr()] == -1) {
		    stackHeights[next.getAddr()] = sh - 1;
		    todo.push(next);
		}
		if (stackHeights[succs[0].getAddr()] == -1) {
		    stackHeights[succs[0].getAddr()] = sh;
		    todo.push(succs[0]);
		}
	    } else {
		if (succs != null) {
		    for (int i=0; i < succs.length; i++) {
			if (stackHeights[succs[i].getAddr()] == -1) {
			    stackHeights[succs[i].getAddr()] = sh;
			    todo.push(succs[i]);
			}
		    }
		}
		if (!instr.doesAlwaysJump()
		    && stackHeights[next.getAddr()] == -1) {
		    stackHeights[next.getAddr()] = sh;
		    todo.push(next);
		}
	    }
	    for (int i=0; i< exceptionHandlers.length; i++) {
		if (exceptionHandlers[i].start.compareTo(instr) <= 0
		    && exceptionHandlers[i].end.compareTo(instr) >= 0) {
		    int catcher = exceptionHandlers[i].catcher.getAddr();
		    if (stackHeights[catcher] == -1) {
			stackHeights[catcher] = 1;
			todo.push(exceptionHandlers[i].catcher);
		    }
		}
	    }
	}
// 	System.err.println("New maxStack: "+maxStack+" Locals: "+maxLocals);
    }

    public void prepareWriting(GrowableConstantPool gcp) {
	/* Recalculate addr, length, maxStack, maxLocals and add all
	 * constants to gcp */
	int addr = 0;
	maxLocals = (methodInfo.isStatic() ? 0 : 1) + 
	    TypeSignature.getArgumentSize(methodInfo.getType());
	
	for (Iterator iter = instructions.iterator(); iter.hasNext(); ) {
	    Instruction instr = (Instruction) iter.next();
	    int opcode = instr.getOpcode();
	    instr.setAddr(addr);
	    int length;
	switch_opc:
	    switch (opcode) {
	    case opc_ldc:
	    case opc_ldc2_w: {
		Object constant = instr.getConstant();
		if (constant == null) {
		    length = 1;
		    break switch_opc;
		}
		for (int i=1; i < constants.length; i++) {
		    if (constant.equals(constants[i])) {
			length = 1;
			break switch_opc;
		    }
		}
		if (opcode == opc_ldc2_w) {
		    gcp.putLongConstant(constant);
		    length = 3;
		    break switch_opc;
		}
		if (constant instanceof Integer) {
		    int value = ((Integer) constant).intValue();
		    if (value >= Byte.MIN_VALUE
			&& value <= Byte.MAX_VALUE) {
			length = 2;
			break switch_opc;
		    } else if (value >= Short.MIN_VALUE
			       && value <= Short.MAX_VALUE) {
			length = 3;
			break switch_opc;
		    }
		}
		if (gcp.putConstant(constant) < 256) {
		    length = 2;
		} else {
		    length = 3;
		}
		break;
	    } 
	    case opc_iinc: {
		int slot = instr.getLocalSlot();
		int increment = instr.getIncrement();
		if (slot < 256 
		    && increment >= Byte.MIN_VALUE 
		    && increment <= Byte.MAX_VALUE)
		    length = 3;
		else
		    length = 6;
		if (slot >= maxLocals)
		    maxLocals = slot + 1;
		break;
	    }
	    case opc_iload: case opc_fload: case opc_aload:
	    case opc_istore: case opc_fstore: case opc_astore: {
		int slot = instr.getLocalSlot();
		if (slot < 4)
		    length = 1;
		else if (slot < 256)
		    length = 2;
		else 
		    length = 4;
		if (slot >= maxLocals)
		    maxLocals = slot + 1;
		break;
	    }
	    case opc_lload: case opc_dload:
	    case opc_lstore: case opc_dstore: {
		int slot = instr.getLocalSlot();
		if (slot < 4)
		    length = 1;
		else if (slot < 256)
		    length = 2;
		else 
		    length = 4;
		if (slot+1 >= maxLocals)
		    maxLocals = slot + 2;
		break;
	    }
	    case opc_ret: {
		int slot = instr.getLocalSlot();
		if (slot < 256)
		    length = 2;
		else 
		    length = 4;
		if (slot >= maxLocals)
		    maxLocals = slot + 1;
		break;
	    } 
	    case opc_lookupswitch: {
		length = 3-(addr % 4);
		int[] values = instr.getValues();
		int   npairs = values.length;
		if (npairs > 0) {
		    int tablesize = values[npairs-1] - values[0] + 1;
		    if (4 + tablesize * 4 < 8 * npairs) {
			// Use a table switch
			length += 13 + 4 * tablesize;
			break;
		    }
		}
		// Use a lookup switch
		length += 9 + 8 * npairs;
		break;
	    }
	    case opc_goto: case opc_jsr: {
		int dist = instr.getSingleSucc().getAddr() - instr.getAddr();
		if (dist < Short.MIN_VALUE || dist > Short.MAX_VALUE) {
		    /* wide goto / jsr */
		    length = 5;
		    break;
		} 
		/* fall through */
	    }
	    case opc_ifeq: case opc_ifne: 
	    case opc_iflt: case opc_ifge: 
	    case opc_ifgt: case opc_ifle:
	    case opc_if_icmpeq: case opc_if_icmpne:
	    case opc_if_icmplt: case opc_if_icmpge: 
	    case opc_if_icmpgt: case opc_if_icmple:
	    case opc_if_acmpeq: case opc_if_acmpne:
	    case opc_ifnull: case opc_ifnonnull:
		length = 3;
		break;
	    case opc_multianewarray: {
		if (instr.getDimensions() == 1) {
		    String clazz = instr.getClazzType().substring(1);
		    if (newArrayTypes.indexOf(clazz.charAt(0)) != -1) {
			length = 2;
		    } else {
			gcp.putClassType(clazz);
			length = 3;
		    }
		} else {
		    gcp.putClassType(instr.getClazzType());
		    length = 4;
		}
		break;
	    }
	    case opc_getstatic:
	    case opc_getfield:
	    case opc_putstatic:
	    case opc_putfield:
		gcp.putRef(gcp.FIELDREF, instr.getReference());
		length = 3;
		break;
	    case opc_invokespecial:
	    case opc_invokestatic:
	    case opc_invokevirtual:
		gcp.putRef(gcp.METHODREF, instr.getReference());
		length = 3;
		break;
	    case opc_invokeinterface:
		gcp.putRef(gcp.INTERFACEMETHODREF, instr.getReference());
		length = 5;
		break;
	    case opc_new:
	    case opc_checkcast:
	    case opc_instanceof:
		gcp.putClassType(instr.getClazzType());
		length = 3;
		break;
	    case opc_nop:
	    case opc_iaload: case opc_laload: case opc_faload:
	    case opc_daload: case opc_aaload:
	    case opc_baload: case opc_caload: case opc_saload:
	    case opc_iastore: case opc_lastore: case opc_fastore:
	    case opc_dastore: case opc_aastore:
	    case opc_bastore: case opc_castore: case opc_sastore:
	    case opc_pop: case opc_pop2:
	    case opc_dup: case opc_dup_x1: case opc_dup_x2:
	    case opc_dup2: case opc_dup2_x1: case opc_dup2_x2:
	    case opc_swap:
	    case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd:
	    case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub:
	    case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul:
	    case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv:
	    case opc_irem: case opc_lrem: case opc_frem: case opc_drem:
	    case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg:
	    case opc_ishl: case opc_lshl:
	    case opc_ishr: case opc_lshr:
	    case opc_iushr: case opc_lushr: 
	    case opc_iand: case opc_land:
	    case opc_ior: case opc_lor: 
	    case opc_ixor: case opc_lxor:
	    case opc_i2l: case opc_i2f: case opc_i2d:
	    case opc_l2i: case opc_l2f: case opc_l2d:
	    case opc_f2i: case opc_f2l: case opc_f2d:
	    case opc_d2i: case opc_d2l: case opc_d2f:
	    case opc_i2b: case opc_i2c: case opc_i2s:
	    case opc_lcmp: case opc_fcmpl: case opc_fcmpg:
	    case opc_dcmpl: case opc_dcmpg:
	    case opc_ireturn: case opc_lreturn: 
	    case opc_freturn: case opc_dreturn: case opc_areturn:
	    case opc_return: 
	    case opc_athrow:
	    case opc_arraylength:
	    case opc_monitorenter: case opc_monitorexit:
		length = 1;
		break;
	    default:
		throw new ClassFormatError("Invalid opcode "+opcode);
	    }
	    addr += length;
	}
	instructions.setLastAddr(addr);
	try {
	    calculateMaxStack();
	} catch (RuntimeException ex) {
	    ex.printStackTrace();
	    dumpCode(GlobalOptions.err);
	}
	for (int i=0; i< exceptionHandlers.length; i++)
	    if (exceptionHandlers[i].type != null)
		gcp.putClassName(exceptionHandlers[i].type);
	if (lvt != null) {
	    gcp.putUTF8("LocalVariableTable");
            for (int i=0; i < lvt.length; i++) {
		gcp.putUTF8(lvt[i].name);
		gcp.putUTF8(lvt[i].type);
            }
	}
	if (lnt != null)
	    gcp.putUTF8("LineNumberTable");
	prepareAttributes(gcp);
    }

    protected int getKnownAttributeCount() {
	int count = 0;
	if (lvt != null)
	    count++;
	if (lnt != null)
	    count++;
	return count;
    }

    public void writeKnownAttributes(GrowableConstantPool gcp,
				     DataOutputStream output) 
	throws IOException {
	if (lvt != null) {
	    output.writeShort(gcp.putUTF8("LocalVariableTable"));
            int count = lvt.length;
	    int length = 2 + 10 * count;
	    output.writeInt(length);
	    output.writeShort(count);
            for (int i=0; i < count; i++) {
		output.writeShort(lvt[i].start.getAddr());
		output.writeShort(lvt[i].end.getAddr() + lvt[i].end.getLength()
				  - lvt[i].start.getAddr());
		output.writeShort(gcp.putUTF8(lvt[i].name));
		output.writeShort(gcp.putUTF8(lvt[i].type));
		output.writeShort(lvt[i].slot);
            }
	}
	if (lnt != null) {
	    output.writeShort(gcp.putUTF8("LineNumberTable"));
            int count = lnt.length;
	    int length = 2 + 4 * count;
	    output.writeInt(length);
	    output.writeShort(count);
            for (int i=0; i < count; i++) {
		output.writeShort(lnt[i].start.getAddr());
		output.writeShort(lnt[i].linenr);
            }
	}
    }

    public void write(GrowableConstantPool gcp, 
		      DataOutputStream output) throws IOException {
	output.writeShort(maxStack);
	output.writeShort(maxLocals);
	output.writeInt(instructions.getCodeLength());
	for (Iterator iter = instructions.iterator(); iter.hasNext(); ) {
	    Instruction instr = (Instruction) iter.next();
	    int opcode = instr.getOpcode();
        switch_opc:
	    switch (opcode) {
	    case opc_iload: case opc_lload: 
	    case opc_fload: case opc_dload: case opc_aload:
	    case opc_istore: case opc_lstore: 
	    case opc_fstore: case opc_dstore: case opc_astore: {
		int slot = instr.getLocalSlot();
		if (slot < 4) {
		    if (opcode < opc_istore)
			output.writeByte(opc_iload_0
					 + 4*(opcode-opc_iload)
					 + slot);
		    else
			output.writeByte(opc_istore_0
					 + 4*(opcode-opc_istore)
					 + slot);
		} else if (slot < 256) {
		    output.writeByte(opcode);
		    output.writeByte(slot);
		} else {
		    output.writeByte(opc_wide);
		    output.writeByte(opcode);
		    output.writeShort(slot);
		}
		break;
	    }		
	    case opc_ret: {
		int slot = instr.getLocalSlot();
		if (slot < 256) {
		    output.writeByte(opcode);
		    output.writeByte(slot);
		} else {
		    output.writeByte(opc_wide);
		    output.writeByte(opcode);
		    output.writeShort(slot);
		}
		break;
	    }
	    case opc_ldc:
	    case opc_ldc2_w: {
		Object constant = instr.getConstant();
		if (constant == null) {
		    output.writeByte(opc_aconst_null);
		    break switch_opc;
		}
		for (int i=1; i < constants.length; i++) {
		    if (constant.equals(constants[i])) {
			output.writeByte(opc_aconst_null + i);
			break switch_opc;
		    }
		}
		if (opcode == opc_ldc2_w) {
		    output.writeByte(opcode);
		    output.writeShort(gcp.putLongConstant(constant));
		} else {
		    if (constant instanceof Integer) {
			int value = ((Integer) constant).intValue();
			if (value >= Byte.MIN_VALUE
			    && value <= Byte.MAX_VALUE) {
			    
			    output.writeByte(opc_bipush);
			    output.writeByte(((Integer)constant)
					     .intValue());
			    break switch_opc;
			} else if (value >= Short.MIN_VALUE
				   && value <= Short.MAX_VALUE) {
			    output.writeByte(opc_sipush);
			    output.writeShort(((Integer)constant)
					      .intValue());
			    break switch_opc;
			}
		    }
		    if (instr.getLength() == 2) {
			output.writeByte(opc_ldc);
			output.writeByte(gcp.putConstant(constant));
		    } else {
			output.writeByte(opc_ldc_w);
			output.writeShort(gcp.putConstant(constant));
		    }
		}
		break;
	    }
	    case opc_iinc: {
		int slot = instr.getLocalSlot();
		int incr = instr.getIncrement();
		if (instr.getLength() == 3) {
		    output.writeByte(opcode);
		    output.writeByte(slot);
		    output.writeByte(incr);
		} else {
		    output.writeByte(opc_wide);
		    output.writeByte(opcode);
		    output.writeShort(slot);
		    output.writeShort(incr);
		}
		break;
	    }
	    case opc_goto:
	    case opc_jsr:
		if (instr.getLength() == 5) {
		    /* wide goto or jsr */
		    output.writeByte(opcode + (opc_goto_w - opc_goto));
		    output.writeInt(instr.getSingleSucc().getAddr()
				    - instr.getAddr());
		    break;
		}
		/* fall through */
	    case opc_ifeq: case opc_ifne: 
	    case opc_iflt: case opc_ifge: 
	    case opc_ifgt: case opc_ifle:
	    case opc_if_icmpeq: case opc_if_icmpne:
	    case opc_if_icmplt: case opc_if_icmpge: 
	    case opc_if_icmpgt: case opc_if_icmple:
	    case opc_if_acmpeq: case opc_if_acmpne:
	    case opc_ifnull: case opc_ifnonnull:
		output.writeByte(opcode);
		output.writeShort(instr.getSingleSucc().getAddr() 
				  - instr.getAddr());
		break;

	    case opc_lookupswitch: {
		int align = 3-(instr.getAddr() % 4);
		int[] values = instr.getValues();
		int npairs = values.length;
		int defAddr = instr.getSuccs()[npairs].getAddr()
		    - instr.getAddr();

		if (npairs > 0) {
		    int tablesize = values[npairs-1] - values[0] + 1;
		    if (4 + tablesize * 4 < 8 * npairs) {
			// Use a table switch
			output.writeByte(opc_tableswitch);
			output.write(new byte[align]);
			/* def */
			output.writeInt(defAddr);
			/* low */
			output.writeInt(values[0]); 
			/* high */
			output.writeInt(values[npairs-1]);
			int pos = values[0];
			for (int i = 0; i < npairs; i++) {
			    while (pos++ < values[i])
				output.writeInt(defAddr);
			    output.writeInt(instr.getSuccs()[i].getAddr()
					    - instr.getAddr());
			}
			break;
		    }
		}
		// Use a lookup switch
		output.writeByte(opc_lookupswitch);
		output.write(new byte[align]);
		/* def */
		output.writeInt(defAddr);
		output.writeInt(npairs);
		for (int i=0; i < npairs; i++) {
		    output.writeInt(values[i]);
		    output.writeInt(instr.getSuccs()[i].getAddr()
				    -instr.getAddr());
		}
		break;
	    }

	    case opc_getstatic:
	    case opc_getfield:
	    case opc_putstatic:
	    case opc_putfield:
		output.writeByte(opcode);
		output.writeShort(gcp.putRef(gcp.FIELDREF, 
					     instr.getReference()));
		break;

	    case opc_invokespecial:
	    case opc_invokestatic:
	    case opc_invokeinterface:
	    case opc_invokevirtual: {
		Reference ref = instr.getReference();
		output.writeByte(opcode);
		if (opcode == opc_invokeinterface) {
		    output.writeShort(gcp.putRef(gcp.INTERFACEMETHODREF, ref));
		    output.writeByte
			(TypeSignature.getArgumentSize(ref.getType()) + 1);
		    output.writeByte(0);
		} else 
		    output.writeShort(gcp.putRef(gcp.METHODREF, ref));
		break;
	    }
	    case opc_new:
	    case opc_checkcast:
	    case opc_instanceof:
		output.writeByte(opcode);
		output.writeShort(gcp.putClassType(instr.getClazzType()));
		break;
	    case opc_multianewarray:
		if (instr.getDimensions() == 1) {
		    String clazz = instr.getClazzType().substring(1);
		    int index = newArrayTypes.indexOf(clazz.charAt(0));
		    if (index != -1) {
			output.writeByte(opc_newarray);
			output.writeByte(index + 4);
		    } else {
			output.writeByte(opc_anewarray);
			output.writeShort(gcp.putClassType(clazz));
		    }
		} else {
		    output.writeByte(opcode);
		    output.writeShort(gcp.putClassType(instr.getClazzType()));
		    output.writeByte(instr.getDimensions());
		}
		break;

	    case opc_nop:
	    case opc_iaload: case opc_laload: case opc_faload:
	    case opc_daload: case opc_aaload:
	    case opc_baload: case opc_caload: case opc_saload:
	    case opc_iastore: case opc_lastore: case opc_fastore:
	    case opc_dastore: case opc_aastore:
	    case opc_bastore: case opc_castore: case opc_sastore:
	    case opc_pop: case opc_pop2:
	    case opc_dup: case opc_dup_x1: case opc_dup_x2:
	    case opc_dup2: case opc_dup2_x1: case opc_dup2_x2:
	    case opc_swap:
	    case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd:
	    case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub:
	    case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul:
	    case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv:
	    case opc_irem: case opc_lrem: case opc_frem: case opc_drem:
	    case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg:
	    case opc_ishl: case opc_lshl:
	    case opc_ishr: case opc_lshr:
	    case opc_iushr: case opc_lushr: 
	    case opc_iand: case opc_land:
	    case opc_ior: case opc_lor: 
	    case opc_ixor: case opc_lxor:
	    case opc_i2l: case opc_i2f: case opc_i2d:
	    case opc_l2i: case opc_l2f: case opc_l2d:
	    case opc_f2i: case opc_f2l: case opc_f2d:
	    case opc_d2i: case opc_d2l: case opc_d2f:
	    case opc_i2b: case opc_i2c: case opc_i2s:
	    case opc_lcmp: case opc_fcmpl: case opc_fcmpg:
	    case opc_dcmpl: case opc_dcmpg:
	    case opc_ireturn: case opc_lreturn: 
	    case opc_freturn: case opc_dreturn: case opc_areturn:
	    case opc_return: 
	    case opc_athrow:
	    case opc_arraylength:
	    case opc_monitorenter: case opc_monitorexit:
		output.writeByte(opcode);
		break;
	    default:
		throw new ClassFormatError("Invalid opcode "+opcode);
	    }
	}

	output.writeShort(exceptionHandlers.length);
	for (int i=0; i< exceptionHandlers.length; i++) {
	    output.writeShort(exceptionHandlers[i].start.getAddr());
	    output.writeShort(exceptionHandlers[i].end.getNextByAddr().getAddr());
	    output.writeShort(exceptionHandlers[i].catcher.getAddr());
	    output.writeShort((exceptionHandlers[i].type == null) ? 0
			      : gcp.putClassName(exceptionHandlers[i].type));
	}
	writeAttributes(gcp, output);
    }

    public void dropInfo(int howMuch) {
	if ((howMuch & KNOWNATTRIBS) != 0) {
	    lvt = null;
	    lnt = null;
	}
	super.dropInfo(howMuch);
    }

    public int getSize() {
	/* maxStack:    2
	 * maxLocals:   2
	 * code:        4 + codeLength
	 * exc count:   2
	 * exceptions:  n * 8
	 * attributes:
	 *  lvt_name:    2
	 *  lvt_length:  4
	 *  lvt_count:   2
	 *  lvt_entries: n * 10
	 * attributes:
	 *  lnt_name:    2
	 *  lnt_length:  4
	 *  lnt_count:   2
	 *  lnt_entries: n * 4
	 */
	int size = 0;
	if (lvt != null)
	    size += 8 + lvt.length * 10;
	if (lnt != null)
	    size += 8 + lnt.length * 4;
	return 10 + instructions.getCodeLength()
	    + exceptionHandlers.length * 8
	    + getAttributeSize() + size;
    }

    public int getMaxStack() {
        return maxStack;
    }

    public int getMaxLocals() {
        return maxLocals;
    }

    public MethodInfo getMethodInfo() {
	return methodInfo;
    }

    public List getInstructions() {
	return instructions;
    }

    public Handler[] getExceptionHandlers() {
	return exceptionHandlers;
    }

    public LocalVariableInfo[] getLocalVariableTable() {
	return lvt;
    }

    public LineNumber[] getLineNumberTable() {
	return lnt;
    }

    public void setExceptionHandlers(Handler[] handlers) {
        exceptionHandlers = handlers;
    }

    public void setLocalVariableTable(LocalVariableInfo[] newLvt) {
	lvt = newLvt;
    }

    public void setLineNumberTable(LineNumber[] newLnt) {
	lnt = newLnt;
    }

    public String toString() {
        return "Bytecode "+methodInfo;
    }
}
