/*
 * AdaptorGen.java --
 *
 *	Generates adaptor class for the java::bind command to handle
 *	JavaBean events.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 *
 * RCS: @(#) $Id: AdaptorGen.java,v 1.3 2000/01/25 03:42:26 mo Exp $
 */

package tcl.lang;

import java.util.*;
import java.lang.reflect.*;
import java.beans.*;
import java.io.*;

/**
 * AdaptorGen is the event adaptor class generator. It can generate an
 * event adaptor class that implements any given JavaBean event
 * interface. The generated class is used to provide a callback
 * mechanism for JavaBeans to invoke Tcl scripts.
 *
 * The program in src/test/AdaptorGenTest.java can be used to test the
 * operation of the AdaptorGen class -- it saves the data generated by
 * the AdaptorGen class into a .class file, which can then be examined
 * using tools such as javap.
 */

class AdaptorGen {

// Constant pool types.

private static final int CONSTANT_Class 		= 7;
private static final int CONSTANT_FieldRef 		= 9;
private static final int CONSTANT_MethodRef		= 10;
private static final int CONSTANT_InterfaceMethodRef	= 11;
private static final int CONSTANT_String		= 8;
private static final int CONSTANT_Integer		= 3;
private static final int CONSTANT_Float			= 4;
private static final int CONSTANT_Long			= 5;
private static final int CONSTANT_Double		= 6;
private static final int CONSTANT_NameAndType		= 12;
private static final int CONSTANT_Utf8			= 1;

// Java op-codes used by the adaptor class.

private static final int ALOAD		= 0x19;
private static final int ALOAD_0 	= 0x2a;
private static final int ALOAD_1 	= 0x2b;
private static final int ICONST_0 	= 0x3;
private static final int ICONST_1 	= 0x4;
private static final int ANEWARRAY 	= 0xbd;
private static final int DUP	 	= 0x59;
private static final int AASTORE	= 0x53;
private static final int RETURN		= 0xb1;
private static final int ARETURN	= 0xb0;
private static final int DRETURN	= 0xaf;
private static final int FRETURN	= 0xae;
private static final int IRETURN	= 0xac;
private static final int LRETURN	= 0xad;
private static final int SIPUSH		= 0x11;
private static final int ASTORE		= 0x3a;
private static final int NEW		= 0xbb;
private static final int ILOAD		= 0x15;
private static final int LLOAD		= 0x16;
private static final int FLOAD		= 0x17;
private static final int DLOAD		= 0x18;
private static final int INVOKESP	= 0xb7;
private static final int INVOKEVT	= 0xb6;
private static final int WIDE		= 0xc4;
private static final int LDC_W		= 0x13;
private static final int INSTNCOF	= 0xc1;
private static final int CHKCAST	= 0xc0;
private static final int IFEQ		= 0x99;
private static final int ATHROW		= 0xbf;
private static final int GOTO_W		= 0xc8;

// Access modifiers.

private static final int ACC_PUBLIC = 0x0001;
private static final int ACC_SUPER  = 0x0020;

// These are internal variable shared among the methods of this
// class. We declare them as member variables so that we don't need to
// pass them explicitly to all methods.

private DataOutputStream ostream;
private Class listenerCls;
private Method methods[];
private String clsName;
private Class superCls;

// The number of items that have been added into the constant pool so
// far. It starts at 1 because there is always an implicit item #0
// in the constant pool.

int cpSize;

// This Vector is used to hold temporarily the constant pool elements
// when we are counting the number of elements in the constant pool.

Vector constPool;

// Stores all the UTF string constants that are currently in the
// constant pool. We use this information to avoid having duplicate
// copies of the same string in the constant pool.

Hashtable utf8Tab;

// The hashtable stores the Class objects of all the Object types
// referenced by the adaptor class, including:
//
//	+ Object types passed in as parameters to the methods of
//	  the adaptor class.
//	+ Object types returned by the methods of the adaptor class.
//	+ Wrapper Object types used to pass event parameters
//	  to _processEvent().
//	+ Exception types thrown by the methods of the adaptor
//	  class.

Hashtable allClasses;

// This hashtable contains all the Class objects of the primitive
// types used in the adaptor class.

Hashtable primClasses;

// This hashtable contains all the primitive types returned by the
// methods of the interface. It will also contain Object.class if
// there is a method that returns an object (of any class).

Hashtable returnTypes;

// This hashtable contains the constant pool IDs for the _return_<type>
// methods.

Hashtable returnMethodRef;

// This hashtable stores the constant pool IDs for the constructors of
// the wrapper classes that are used to pass parameters of primitive
// types to the _processEvent() method.

Hashtable wrapperConsRef;

// This hashtable contains the constant pool IDs for all the classes
// referenced by the adaptor class.

Hashtable clsRef;

// This hashtable contains the constant pool IDs for all the strings
// referenced by the adaptor class.

Hashtable stringRef;

// The constant pool ID of the adaptor class.

short cp_this_class; 

// The constant pool ID of the super class of the adaptor class
// (tcl.lang.EventAdaptor).

short cp_super_class;

// The constant pool ID of the event interface that the adaptor class
// implements.

short cp_listener_interface;

// The constant pool ID of the "Code" string, which is used to
// identify a section of executable code in the class file.

short cp_code;

// The constant pool ID of the constructor of the super class.

short cp_super_cons;

// The constant pool ID of the _processEvent() method in the super
// class.

short cp_processEvent;

// The constant pool ID of the _wrongException() method in the super
// class.

short cp_wrongException;

// Stores information about each method in the adaptor class.
// cp_methodDesc[i] contains info about method[i].

MethodDesc cp_methodDesc[];

// Store information about the constructor of the adaptor class.

MethodDesc cp_consDesc;


/*
 *----------------------------------------------------------------------
 *
 * generate --
 *
 *	Generate the byte code of an adaptor class that implements the
 *	event interface of the given event.
 *
 * Results:
 *	A byte array that contains the byte code of the adaptor class.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

byte[]
generate(
    EventSetDescriptor desc,
    Class superClass,
    String className)
{
    // Copy these arguments into member variables so that they don't need
    // to be passed into the internal methods called by generateByteCode().

    superCls = superClass;
    clsName  = className;
    listenerCls = desc.getListenerType();
    methods = listenerCls.getMethods();

    // Initialize other member variables used to generate the byte code.
    // These variables must be re-initialize each time a new class is to
    // be generated.

    allClasses = new Hashtable();
    primClasses = new Hashtable();
    returnTypes = new Hashtable();

    returnMethodRef = new Hashtable();
    wrapperConsRef = new Hashtable();
    clsRef = new Hashtable();
    stringRef = new Hashtable();
    utf8Tab = new Hashtable();
    cp_methodDesc = new MethodDesc[methods.length];
    analyzeListener();
    cpSize = 1;

    // Generate the data of the adaptor class that implements the
    // event interface given by desc.

    try {
	// Prepare the output streams.

	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	DataOutputStream dos = new DataOutputStream(baos);
	ostream = dos;

	// Generate the data.

	generateByteCode();

	return baos.toByteArray();

    } catch (IOException e) {
	throw new TclRuntimeError("Unexcepted IOException " + e);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * analizeListener --
 *
 *	Find out information about the listener class:
 *
 *	  - How many method are there.
 *	  - What exceptions (if any) are thrown by the methods.
 *	  - The argument types of the methods
 *	  - The return types of the methods.
 *	
 *	From this information, we can determine what to put in the constant
 *	pools:
 *
 *	  - All the classes referred to by the arguments, checked exception
 *	    and return types.
 *	  - All the primitive wrapper classes needed to pass primitive
 *	    arguments to the super.processEvent()
 *	  - All the super.return<type> methods needed by the return types
 *	    of the methods.
 *
 *	One reason for obtaining the information is to reduce the size
 *	of the constant pool. E.g., if we know that none of the methods
 *	returns int values, then we don't need to put the method
 *	_return_int() into the constant pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Infomation about the listener class are recorded in various member
 *	variables.
 *
 *----------------------------------------------------------------------
 */

private void
analyzeListener()
{
    int i, j;
    boolean paramsDefined = false;

    for (i = 0; i < methods.length; i++) {
	Class params[] = methods[i].getParameterTypes();

	// Record all classes (including wrapper classes) that will
	// be used to pass parameters to _processEvent().

	for (j = 0; j < params.length; j++) {
	    if (params[j].isPrimitive()) {
		if (params[j] == Void.TYPE) {
		    // Looks likes the JVM has loaded a bad interface class:
		    // one of the parameter of an interface is of type void.

		    throw new ClassFormatError(
			    "Parameter type cannot be void");
		}
		Class wrapper = getWrapperClass(params[j]);
		allClasses.put(wrapper, wrapper);
		primClasses.put(params[j], params[j]);
	    } else {
		allClasses.put(params[j], params[j]);
	    }
	    paramsDefined = true;
	}

	// Record all exceptions thrown by the methods.

	Class exceptions[] = methods[i].getExceptionTypes();
	for (j = 0; j < exceptions.length; j++) {
	    allClasses.put(exceptions[j], exceptions[j]);
	}

	// Record information about the return types of the methods.

	Class retType = methods[i].getReturnType();
	if (retType != Void.TYPE) {
	    if (!retType.isPrimitive()) {
		allClasses.put(retType, retType);
	    }
	    returnTypes.put(retType, retType);
	}
    }

    if (paramsDefined) {
	allClasses.put(Object.class, Object.class);
    }

    allClasses.put(Throwable.class, Throwable.class);
}

/*
 *----------------------------------------------------------------------
 *
 * generateByteCode --
 *
 *	Writes out the byte code into the ostream.
 *
 * Results:
 *
 *	None.
 *
 * Side effects:
 *	Byte code is written into the ostream.
 *
 *----------------------------------------------------------------------
 */

private void
generateByteCode()
throws
    IOException			// This exception may never happen. We
				// declare it just to avoid putting
				// catch statements everywhere.
{
    // u4 magic.
    // u2 minor_version
    // u2 major_version

    ostream.writeInt(0xCAFEBABE);
    ostream.writeShort(3);
    ostream.writeShort(45);

    // u2 constant_pool_count
    // cp_info constant_pool[constant_pool_count-1]

    generateConstantPool();

    // u2 access_flags
    // u2 this_class
    // u2 super_class

    ostream.writeShort(ACC_SUPER|ACC_PUBLIC);
    ostream.writeShort(cp_this_class);
    ostream.writeShort(cp_super_class);

    // u2 interfaces_count
    // u2 interfaces[interfaces_count]

    ostream.writeShort(1);
    ostream.writeShort(cp_listener_interface);

    // u2 fields_count
    // u2 field_info fields[fields_count]

    ostream.writeShort(0);

    // u2 methods_count
    // u2 method_info methods[methods_count]

    ostream.writeShort(1 + methods.length);
    generateConstructor();

    for (int i=0; i<methods.length; i++) {
	generateMethod(i);
    }

    // u2 attributes_count
    // u2 attribute_info attributes[attributes_count]

    ostream.writeShort(0);
}

/*
 *----------------------------------------------------------------------
 *
 * generateConstantPool --
 *
 *	Generate the constant pool.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The constant pool elements are written into the byte array stream.
 *
 *----------------------------------------------------------------------
 */

private void
generateConstantPool()
throws
    IOException			// This exception may never happen because
				// we are only writing to byte array
				// streams. We declare it just to
				// avoid putting catch statements
				// everywhere.
{
    // We do this in three stages because inside the byte code, the
    // constant_pool_count appears in front of the constant pool
    // elements.
    //
    //	(1) Generate the constant pool elements and store them into a 
    //	    Vector. When we are done we know the total number of
    //	    constants we have.
    //
    //	(2) Find out how many elements we have written, and write
    //      constant_pool_count into the byte array stream.
    //
    //	(3) Write the constant pool elements into the byte array
    //	    stream.
    //

    constPool = new Vector();

    // Names of this class, its super class and the interface that
    // this class implements.

    cp_this_class  = cp_putClass(clsName); 
    cp_super_class = cp_putClass(superCls.getName());

    cp_listener_interface = cp_putClass(listenerCls.getName());

    // The UTF8 string "Code" is used to generate the body of methods.

    cp_code = cp_putUtf8("Code");

    // All the methods that the generated class calls.

    cp_super_cons = cp_putMethodRef(cp_super_class, "<init>", "()V");
    cp_processEvent = cp_putMethodRef(cp_super_class, "_processEvent", 
	    "([Ljava/lang/Object;Ljava/lang/String;)V");
    cp_wrongException = cp_putMethodRef(cp_super_class, "_wrongException",
	    "()V");

    for (Enumeration e = returnTypes.keys(); e.hasMoreElements(); ) {
	Class retType = (Class)e.nextElement();
	short ref;

	if (retType.isPrimitive()) {
	    ref = cp_putMethodRef(cp_super_class,
		    "_return_" + retType.getName(),
		    "()" + getTypeDesc(retType));
	} else {
	    cp_putString(retType.getName());
	    ref = cp_putMethodRef(cp_super_class,
		    "_return_Object",
		    "(Ljava/lang/String;)" + getTypeDesc(retType));
	}

	hashPutShort(returnMethodRef, retType, ref);
    }

    // The constructor and methods that are defined in the generated
    // class.

    cp_consDesc = cp_putMethodDesc("<init>", "()V", false);

    for (int i = 0; i < methods.length; i++) {
	cp_methodDesc[i] = cp_putMethodDesc(methods[i].getName(),
		getMethodDescriptor(methods[i]), true);
    }

    // All the classes referred to by the generated class.

    for (Enumeration e = allClasses.keys(); e.hasMoreElements(); ) {
	Class type = (Class)e.nextElement();

	short ref = cp_putClass(type.getName());
	hashPutShort(clsRef, type, ref);
    }

    // If the methods in the generated class receives parameter of
    // primitive types, they must be wrapped in wrapper classes such
    // as java.lang.Integer before they are passed to
    // super._processEvent().

    for (Enumeration e = primClasses.keys(); e.hasMoreElements(); ) {
	// FIXME : javac compiler bug workaround
	//
	// This loop works around a compiler bug in JAVAC 1.1.4. For
	// For some reasons, if this loop is not here, AdaptorGen.class will
	// contain incorrect byte code and causes a NullPoniterException.
	// 
	// This compiler bug happens only in JAVAC. MS JVC apparently
	// works fine.

	e.nextElement();
    }

    for (Enumeration e = primClasses.keys(); e.hasMoreElements(); ) {
	Class primType = (Class)e.nextElement();
	short class_index = cp_getClass(getWrapperClass(primType));
	short ref = cp_putMethodRef(class_index,	"<init>",
		"(" + getTypeDesc(primType) + ")V");

	hashPutShort(wrapperConsRef, primType, ref);
    }

    // Now we know the count. Let's write into the byte array.

    ostream.writeShort(constPool.size() + 1);
    for (int i=0; i<constPool.size(); i++) {
	Object obj = constPool.elementAt(i);

	if (obj instanceof ConstUtf) {
	    ConstUtf cutf = (ConstUtf)obj;

	    ostream.writeByte(CONSTANT_Utf8);
	    ostream.writeUTF(cutf.string);

	} else if (obj instanceof ConstString) {
	    ConstString cstr = (ConstString)obj;

	    ostream.writeByte(CONSTANT_String);
	    ostream.writeShort(cstr.string_index);

	} else if (obj instanceof ConstClass) {
	    ConstClass ccls = (ConstClass)obj;

	    ostream.writeByte(CONSTANT_Class);
	    ostream.writeShort(ccls.name_index);

	} else if (obj instanceof ConstMethodRef) {
	    ConstMethodRef cmref = (ConstMethodRef)obj;

	    ostream.writeByte(CONSTANT_MethodRef);
	    ostream.writeShort(cmref.class_index);
	    ostream.writeShort(cmref.name_and_type_index);

	} else {
	   ConstNameAndType cnat = (ConstNameAndType)obj;

	   ostream.writeByte(CONSTANT_NameAndType);
	   ostream.writeShort(cnat.name_index);
	   ostream.writeShort(cnat.desc_index);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * generateConstructor --
 *
 *	Generates the constructor method -- it just chains to the
 *	constructor of the super class. E.g.
 *
 *	    public Adaptor0() {
 *     		super();
 * 	    }
 *
 *	Note we need to generate such an "implicit constructor"
 *	anyways. The JVM requires this. We can omit implicit
 *	constructor in Java source code because JAVAC makes one for us
 *	behind the scene.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The byte code of the constructor is written into the ostream.
 *
 *----------------------------------------------------------------------
 */

void
generateConstructor()
throws
    IOException			// This exception may never happen. We
				// declare it just to avoid putting
				// catch statements everywhere.
{
    ostream.writeShort(ACC_PUBLIC);	// access
    ostream.writeShort(cp_consDesc.name_index);
    ostream.writeShort(cp_consDesc.descriptor_index);
    ostream.writeShort(1);		// attr count

    ostream.writeShort(cp_code);	// attr_name_index = "Code"
    ostream.writeInt(17);		// attr_length,excluding first 6 bytes
    ostream.writeShort(2);		// max_stacks
    ostream.writeShort(1);		// max_locals
    ostream.writeInt(5);		// code_length

    ostream.writeByte(ALOAD_0);		// aload_0

    ostream.writeByte(INVOKESP);	// invokespecial super.<init>()
    ostream.writeShort(cp_super_cons);

    ostream.writeByte(RETURN);		// return

    ostream.writeShort(0);		// exception_table_length
    ostream.writeShort(0);		// attribute_count
}

/*
 *----------------------------------------------------------------------
 *
 * generateMethod --
 *
 *	Generates an event handling method. This procedure is more
 *	complex because the method can receive any parameters, throw
 *	any exceptions and return any value. Here is a canonical form
 *	of the kind of method generated:
 *
 *	    public int someEvent(int p0, double p1, byte p2, Object p3)
 *	        throws Exception1, Exception2
 *	    {
 *		Object params[] = new Object[4];
 *		params[0] = new Integer(p0);
 *		params[1] = new Double(p1);
 *		params[2] = new Byte(p2);
 *		params[3] = p3;
 *		
 *		try {
 *		    _processEvent(params, "someEvent");
 *		} catch (Throwable exception) {
 *		    if (exception instanceof Exception1) {
 *		        throw (Exception1)exception;
 *		    } else if (exception instanceof Exception2) {
 *		        throw (Exception2)exception;
 *		    } else {
 *			_wrongException();
 *		    }
 *		}
 *
 *		return _return_int();
 *	    }
 *
 *
 *	If the return type is any Object type, the final statement
 *	will be modified as in the following:
 *
 *	    public xyz.pkg.FooBar someEvent(...)
 *	    {
 *		....
 *		return (FooBar)_return_Object("xyz.pkg.FooBar");
 *	    }
 *	
 *	The actual work of converting interp.getResult() to the
 *	appropriate return type is done in the _return_<type> methods
 *	in the EventAdaptor class.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The byte code of the method is written into the ostream.
 *
 *----------------------------------------------------------------------
 */

void
generateMethod(int methodIdx)	// Generate the method  described by
				// methods[methodIdx].
throws
    IOException
{

    int max_stacks;
    int max_locals;
    int paramVarIdx;		// index of the "param" variable.
    int exceptionVarIdx;	// index of the "exception" variable.
    int exStartPC, exEndPC;	// Exception start and end PC.
    int exHandlerPC = 0;	// Exception handler PC.

    // Calculate the max_stacks and max_locals variables for the
    // method.

    max_stacks = 6;

    Class paramTypes[] = methods[methodIdx].getParameterTypes();
    int numParams = paramTypes.length;

    max_locals = 1;					// "this" pointer
    for (int i = 0; i < numParams; i++) {
	if ((paramTypes[i] == Double.TYPE) || (paramTypes[i] == Long.TYPE)) {
	    max_locals += 2;
	} else {
	    max_locals += 1;
	}
    }

    max_locals += 2;			// param[], exception.
    paramVarIdx = max_locals-2;
    exceptionVarIdx = max_locals-1;

    ostream.writeShort(ACC_PUBLIC);	// access = "public"
    ostream.writeShort(cp_methodDesc[methodIdx].name_index);
    ostream.writeShort(cp_methodDesc[methodIdx].descriptor_index);
    ostream.writeShort(1);		// attr count

    // Generate the body of the code.

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream code = new DataOutputStream(baos);

    // [1] Create an array for passing parameters to super.processEvent():
    //
    //     Object params[] = new Object[numParams];
    //
    // NOTE:
    //
    //   - In all the generated code, it is sufficient to represent
    //	   a local variable using a short because a Java method may
    //	   have no more than 65535 local variables (including paramaters).
    //
    //   - This means the side of the params array is no more than 65535,
    //     so we can specify its size using sipush.


    code.writeByte(SIPUSH);		// sipush	#<numParams>
    code.writeShort(numParams);

    code.writeByte(ANEWARRAY);		// anewarray	Object
    code.writeShort(cp_getClass(Object.class));

    writeLoadStore(code, ASTORE,	// astore	param[]
	    paramVarIdx);

    // [2] Copy the parameters into an object array:
    //
    //	   params[0] = new Integer(p0);
    //	   params[1] = new Double(p0);
    //	   params[2] = (Object)p2;
    //	   // .... etc

    // We start at local variable index 1, which is the first parameter.
    // (index 0 is the "this" pointer).
     
    int paramIdx = 1;

    for (int i = 0; i < numParams; i++) {
	writeLoadStore(code, ALOAD,	// aload	param[]
		paramVarIdx);

	code.writeByte(SIPUSH);		// sipush	#<i>	
	code.writeShort(i);

	if (paramTypes[i].isPrimitive()) {
	    Class prim = paramTypes[i];
	    Class wrapper = getWrapperClass(paramTypes[i]);
	    int loadOpcode, numWords;

	    if (prim == Double.TYPE) {
		loadOpcode = DLOAD;
		numWords = 2;
	    } else if (prim == Float.TYPE) {
		loadOpcode = FLOAD;
		numWords = 1;
	    } else if (prim == Long.TYPE) {
		loadOpcode = LLOAD;
		numWords = 2;
	    } else {
		loadOpcode = ILOAD;
		numWords = 1;
	    }

	    code.writeByte(NEW);	// new		<Wrapper>
	    code.writeShort(cp_getClass(wrapper));

	    code.writeByte(DUP);	// dup

	    writeLoadStore(code, 	// <?>load	p_<i>
		    loadOpcode,
		    paramIdx);

    	    code.writeByte(INVOKESP);	// invokespecial <Wrapper>(<type>);
	    code.writeShort(cp_getWrapperConstructor(prim));

	    paramIdx += numWords;
	} else {
	    writeLoadStore(code, ALOAD,	// aload	p_<i>
		    paramIdx);

	    paramIdx += 1;
	}

	code.writeByte(AASTORE);	// aastore
    }

    // [3] Call super.processEvent():
    //
    //     try {
    //	   	super.processEvent(params, <nameOfMethod>);
    //     }

    exStartPC = code.size();

    code.writeByte(ALOAD_0);		// aload_0	this

    writeLoadStore(code, ALOAD, 	// aload	param[]
	    paramVarIdx);

    code.writeByte(LDC_W);		// ldc_w	<name_of_method>
    code.writeShort(cp_getString(methods[methodIdx].getName()));

    code.writeByte(INVOKEVT);		// invokevirtual processEvent()
    code.writeShort(cp_processEvent);

    exEndPC = code.size();

    // [4] Handle any exceptions thrown by processEvent():
    //
    //     catch (Throwable exception) {
    //	       ....
    //     }
    //
    //	   Note, we use WIDE version of load/store in all subsequent
    //	   byte codes so that it's easy to calculate the offset
    //	   for jumping to the "normal" return statement.)

    Class exceptions[] = methods[methodIdx].getExceptionTypes();

    int offset = 5 + 4 + exceptions.length * 18 + 4 ;
    code.writeByte(GOTO_W);		// goto_w	#<offset>
    code.writeInt(offset);

    exHandlerPC = code.size();

    code.writeByte(WIDE);		// astore	exception
    code.writeByte(ASTORE);
    code.writeShort(exceptionVarIdx);

    for (int i = 0; i < exceptions.length; i++) {
	// Write the exception handler for each of the checked exception
	// types. Each handler is 16 bytes long.

	code.writeByte(WIDE);		// aload	exception
	code.writeByte(ALOAD);
	code.writeShort(exceptionVarIdx);
	
	code.writeByte(INSTNCOF);	// instanceof	<exceptions[i]>
	code.writeShort(cp_getClass(exceptions[i]));

	code.writeByte(IFEQ);		// ifeq		#<nextException>
	code.writeShort(11);

	code.writeByte(WIDE);		// aload	exception
	code.writeByte(ALOAD);
	code.writeShort(exceptionVarIdx);

	code.writeByte(CHKCAST);	// checkcast	<exceptions[i]>
	code.writeShort(cp_getClass(exceptions[i]));
	
	code.writeByte(ATHROW);		// athrow
    }

    code.writeByte(ALOAD_0);		// aload_0	this
    
    code.writeByte(INVOKEVT);		// invokevirtual _wrongExceptionError()
    code.writeShort(cp_wrongException);

    // [5] Normal return from this method.

    Class retType = methods[methodIdx].getReturnType();

    if (retType == Void.TYPE) {
	code.writeByte(RETURN);
    } else if (retType.isPrimitive()) {
	code.writeByte(ALOAD_0);	// aload_0	this

	code.writeByte(INVOKEVT);	// invokevirtual return_<type>
	code.writeShort(cp_getReturnMethodRef(
	        retType));

	if (retType == Double.TYPE) {
	    code.writeByte(DRETURN);	// dreturn
	} else if (retType == Float.TYPE) {
	    code.writeByte(FRETURN);	// freturn
	} else if (retType == Long.TYPE) {
	    code.writeByte(LRETURN);	// lreturn
	} else {
	    // IRETURN is used for boolean,
	    // byte, char, int and short.

	    code.writeByte(IRETURN);	// ireturn
	}
    } else {
	code.writeByte(ALOAD_0);	// aload_0	this

	code.writeByte(LDC_W);		// ldc_w	<retType.getName()>
	code.writeShort(cp_getString(retType.getName()));

	code.writeByte(INVOKEVT);	// invokevirtual return_<type>
	code.writeShort(cp_getReturnMethodRef(
	        retType));

	code.writeByte(CHKCAST);	// checkcast <retType>
	code.writeShort(cp_getClass(retType));
	code.writeByte(ARETURN);	// areturn
    }

    int codeLength = code.size();
    
    // [6] Write the exception table: we catch all Throwable
    //     classes.

    code.writeShort(1);			// exception_table_length

    code.writeShort(exStartPC);		// start_pc
    code.writeShort(exEndPC);		// end_pc
    code.writeShort(exHandlerPC);	// handler_pc
    code.writeShort(			// catch_type
	    cp_getClass(Throwable.class));


    // [7] The attributes table (empty)

    code.writeShort(0);			// attribute_count

    // [8] Now we are done. Emit the code section into the output
    //     stream.

    code.close();
    byte codeBytes[] = baos.toByteArray();

    ostream.writeShort(cp_code);	// attr_name_index = "Code"
    ostream.writeInt(codeBytes.length	// attr_length
	    + 8);

    ostream.writeShort(max_stacks);
    ostream.writeShort(max_locals);

    ostream.writeInt(codeLength);	// code_length
    ostream.write(codeBytes);
}

/*
 *----------------------------------------------------------------------
 *
 * internalClassName --
 *
 *	Returns the "internal" class name of a Java class: E.g. the
 *	internal name for "java.lang.Integer" is "java/lang/Integer".
 *
 * Results:
 *	The "internal" class name.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private final static String
internalClassName(
    String className)		// "Normal" name of the class.
{
    return className.replace('.', '/');
}

/*
 *----------------------------------------------------------------------
 *
 * hashPutShort --
 *
 *	Puts a short value into a hash table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The short value is wrapped in a Short object and stored in the
 *	hashtable.
 *
 *----------------------------------------------------------------------
 */

private final static void
hashPutShort(
    Hashtable hashtable,	// The hashtable.
    Object key,			// The key.
    short num)			// Put this number under the given key
				// in the hashtable.
{
    Short shortObj = new Short(num);
    hashtable.put(key, shortObj);
}

/*
 *----------------------------------------------------------------------
 *
 * hashGetShort --
 *
 *	Gets the short value corresponding to the key from the hash
 *	table.
 *
 * Results:
 *	The short value corresponding to the key.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private final static short
hashGetShort(
    Hashtable hashtable,	// The hashtable.
    Object key)			// The key.
{
    return ((Short) hashtable.get(key)).shortValue();
}

/*
 *----------------------------------------------------------------------
 *
 * getWrapperClass --
 *
 *	Given a primitive type (e.g. int), returns its wrapper class
 *	(e.g., java.lang.Integer).
 *
 * Results:
 *	The wrapper class for the primitive type.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private final static Class
getWrapperClass(
    Class primType)		// The Class object that represents the
				// primitive type.
{
    if (primType == Boolean.TYPE) {
	return Boolean.class;
    } else if (primType == Byte.TYPE) {
	return Byte.class;
    } else if (primType == Character.TYPE) {
	return Character.class;
    } else if (primType == Double.TYPE) {
	return Double.class;
    } else if (primType == Float.TYPE) {
	return Float.class;
    } else if (primType == Integer.TYPE) {
	return Integer.class;
    } else if (primType == Long.TYPE) {
	return Long.class;
    } else {
	return Short.class;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getTypeDesc --
 *
 *	Returns the string that represents a Java type. E.g, "Z" for
 *	boolean, "Lfoo.Bar;" for foo.Bar.
 *
 * Results:
 *	The string that represents a Java type.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private final static String
getTypeDesc(Class cls)
{
    if (cls.isPrimitive()) {
	if (cls == Boolean.TYPE) {
	    return "Z";
	} else if (cls == Byte.TYPE) {
	    return "B";
	} else if (cls == Character.TYPE) {
	    return "C";
	} else if (cls == Double.TYPE) {
	    return "D";
	} else if (cls == Float.TYPE) {
	    return "F";
	} else if (cls == Integer.TYPE) {
	    return "I";
	} else if (cls == Long.TYPE) {
	    return "J";
	} else if (cls == Short.TYPE) {
	    return "S";
	} else {
	    return "V";
	}
    } else {
	if (cls.isArray()) {
	    return "[" + getTypeDesc(cls.getComponentType());
	} else {
	    String s = "L" + cls.getName() + ";";
	    return s.replace('.', '/');
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getMethodDesc --
 *
 *	Returns the string that represents the type of a method. E.g.
 *	"(Lfoo.Bar;DI)V" for void xxx(foo.Bar,double,int)
 *
 * Results:
 *	The string that represents the type of a method.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private final static String
getMethodDescriptor(
    Method method)		// Returns the desc of this method.
{
    StringBuffer sbuf = new StringBuffer();
    sbuf.append('(');

    Class params[] = method.getParameterTypes();

    for (int i = 0; i < params.length; i++) {
	sbuf.append(getTypeDesc(params[i]));
    }
    sbuf.append(')');
    sbuf.append(getTypeDesc(method.getReturnType()));

    return sbuf.toString();
}
/*
 *----------------------------------------------------------------------
 *
 * writeLoadStore --
 *
 *	Writes the byte-code for a load or store operation. To reduce
 *	the size of the byte code, the WIDE prefix is used only when
 *	necessary (i.e., the address is greater than 255)
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	the load/store byte codes are written into the
 *	DataOutputStream.
 *
 *----------------------------------------------------------------------
 */

void
writeLoadStore(
    DataOutputStream code,	// The DataOutputStream to write the byte-code
    				// into.
    int opcode,			// The opcode can be ?LOAD or ?STORE
    int address)		// The target address of the load/store.
throws
    IOException			// This exception may never happen. We
				// declare it just to avoid putting
				// catch statements everywhere.
{
    if (address > 255) {
	code.writeByte(WIDE);
	code.writeByte(opcode);
	code.writeShort(address);
    } else {
	code.writeByte(opcode);
	code.writeByte(address);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putUtf8 --
 *
 *	Puts a UTF8 string into the constant pool.
 *
 * Results:
 *	The index of that UTF8 string in the constant pool.
 *
 * Side effects:
 *	The UTF8 string is put into the constPool vector if the same
 *	string is not already in the constant pool.
 *
 *----------------------------------------------------------------------
 */

short
cp_putUtf8(
    String string)		// The string to put (in UTF8 format) into
				// the constant pool.
{
    Short shortObj;

    shortObj = (Short)utf8Tab.get(string);

    // Check to make sure that the string is not already in the
    // constant pool so that we won't have duplicated entries (which
    // wastes space!).

    if (shortObj != null) {
	return shortObj.shortValue();
    } else {
	ConstUtf cutf = new ConstUtf();
	cutf.string = string;
	constPool.addElement(cutf);

	short id = (short)cpSize++;
	hashPutShort(utf8Tab, string, id);
	return id;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putString --
 *
 *	Puts a string into the constant pool. N.B., this is stored as
 *	a CONSTANT_String element. In contrast, UTF8 strings are
 *	stored as CONSTANT_Utf8. Read "The Java Virtual Machine
 *	Specification" for details.
 *
 * Results:
 *	The index of the string in the constant pool.
 *
 * Side effects:
 *	The string is put into the constPool vector.
 *
 *----------------------------------------------------------------------
 */

private short
cp_putString(
    String string)		// The string to put (in CONSTANT_String
				// format) into the constant pool.
{
    ConstString cstr = new ConstString();
    cstr.string_index = cp_putUtf8(string);
    constPool.addElement(cstr);

    short id = (short) cpSize++;
    hashPutShort(stringRef, string, id);
    return id;
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putClass --
 *
 *	Puts a CONSTANT_Class element into the constant pool.
 *
 * Results:
 *	The index of the class.
 *
 * Side effects:
 *	The class is put into the constPool vector.
 *
 *----------------------------------------------------------------------
 */

private short
cp_putClass(
    String className)		// Fully qualified name of the class.
{
    ConstClass ccls = new ConstClass();
    ccls.name_index = cp_putUtf8(internalClassName(className));
    constPool.addElement(ccls);

    return (short) cpSize++;
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putNameAndType --
 *
 *	Puts a CONSTANT_NameAndType element into the constant pool.
 *
 * Results:
 *	The index of the NameAndType.
 *
 * Side effects:
 *	The NameAndType is put into the constPool vector.
 *
 *----------------------------------------------------------------------
 */

short
cp_putNameAndType(
    String name,		// The name of the method.
    String type)		// The type of the method.
{
    ConstNameAndType cnat = new ConstNameAndType();
    cnat.name_index = cp_putUtf8(name);
    cnat.desc_index = cp_putUtf8(type);
    constPool.addElement(cnat);

    return (short) cpSize++;
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putMethodRef --
 *
 *	Puts a CONSTANT_MethodRef element into the constant pool.
 *
 * Results:
 *	The index of the MethodRef.
 *
 * Side effects:
 *	The MethodRef is put into the constPool vector.
 *
 *----------------------------------------------------------------------
 */

short
cp_putMethodRef(
    short class_index,		// Index of the class.
    String name,		// Name of the method.
    String desc)		// Descriptor of the method.
{
    ConstMethodRef cmref = new ConstMethodRef();
    cmref.class_index = class_index;
    cmref.name_and_type_index = cp_putNameAndType(name, desc);

    constPool.addElement(cmref);

    return (short) cpSize++;
}

/*
 *----------------------------------------------------------------------
 *
 * cp_putMethodDesc --
 *
 *	Puts the UTF8 strings needed to describe a method defined in
 *	the generated class.
 *
 * Results:
 *	A MethodDesc object that contains the index of the name and
 *	descriptor of the method.
 *
 * Side effects:
 *	UTF8 strings may be put into the constPool vector.
 *
 *----------------------------------------------------------------------
 */

MethodDesc
cp_putMethodDesc(
    String name,		// Name of the method.
    String descriptor,		// Descriptor of the method. 
    boolean generateID)		// True if we need to generate a string ID
    				// to pass to _processEvent() for this method.
{
    MethodDesc desc = new MethodDesc();
    desc.name_index = cp_putUtf8(name);
    desc.descriptor_index = cp_putUtf8(descriptor);
    if (generateID) {
	cp_putString(name);
    }

    return desc;
}

/*
 *----------------------------------------------------------------------
 *
 * cp_getClass --
 *
 *	Returns the index of a class definition in the constant pool
 *
 * Results:
 *	The index of the class definition.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

short
cp_getClass(
    Class cls)			// Query the index of this class.
{
    return hashGetShort(clsRef, cls);
}

/*
 *----------------------------------------------------------------------
 *
 * cp_getString --
 *
 *	Returns the index of a CONSTANT_String definition in the
 *	constant pool
 *
 * Results:
 *	The index of the string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

short
cp_getString(
    String string)		// Query the index of this string.
{
    return hashGetShort(stringRef, string);
}

/*
 *----------------------------------------------------------------------
 *
 * cp_getWrapperConstructor --
 *
 *	Returns the constant pool index of a CONSTANT_MethodRef
 *	definition for the constructor of the primitive wrapper class.
 *
 * Results:
 *	The index of the constructor.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

short
cp_getWrapperConstructor(
    Class primType)		// Query the index of the constructor
				// of the wrapper class of this primitive type.
{
    return hashGetShort(wrapperConsRef, primType);
}


/*
 *----------------------------------------------------------------------
 *
 * cp_getReturnMethodRef --
 *
 *	Returns the constant pool index of a CONSTANT_MethodRef
 *	definition of methods such super._return_int(); these methods
 *	are used to return values from the binding script.
 *
 * Results:
 *	The index of the method.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

short
cp_getReturnMethodRef(
    Class retType)		// Query the index of the method that
				// returns this type.
{
    if (retType.isPrimitive()) {
	return hashGetShort(returnMethodRef, retType);
    } else {
	return hashGetShort(returnMethodRef, Object.class);
    }
}


// The following five inner classes are used to store temporary copies
// of constane pool items in a Vector.

class ConstUtf {
    String string;		// The string to put into the constant pool
				// in UTF8 format.
}

class ConstString {
    short string_index;		// Index of the CONSTANR_Utf8 element that
				// defines the string.
}

class ConstClass {
    short name_index;		// Index of the CONSTANR_Utf8 element that
				// defines the internal name of the class.
}

class ConstNameAndType {
    short name_index;		// Index of the CONSTANR_Utf8 element that
				// defines the name of a method.
    short desc_index;		// Index of the CONSTANR_Utf8 element that
				// defines the type of a method.
}

class ConstMethodRef {
    short class_index;		// Index of the CONSTANR_Utf8 element that
				// defines the internal name of the class.
    short name_and_type_index;	// Index of the CONSTANR_NameAndType element
				// that defines the name and type of the
				// method.
}


// This inner class stores the name and descriptor of the method to
// generate.

class MethodDesc {

// Index to the name of the method (a CONSTANT_String).

short name_index;

// Index to the name of the method (a CONSTANT_String).

short descriptor_index;

} // end AdaptorGen.MethodDesc

} // end AdaptorGen

