/*
 * $Id: ExpressionImage.java,v 1.1.1.1 1998/05/11 18:43:02 metlov Exp $
 *
 * This file is part of the Java Expressions Library (JEL).
 *   For more information about JEL visit :
 *    http://galaxy.fzu.cz/JEL/
 *
 * (c) 1998 by Konstantin Metlov(metlov@fzu.cz);
 *
 * JEL is Distributed under the terms of GNU General Public License.
 *    This code comes with ABSOLUTELY NO WARRANTY.
 *  For license details see COPYING file in this directory.
 */

package gnu.jel;

import gnu.jel.debug.Debug;
import gnu.jel.debug.Tester;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

/**
 * A class which is repsonsible for generating valid class files for compiler.
 * <P>It is not general purpose Java assembler, it is specifically JEL
 * oriented.
 * <P>This class is designed to be the part of a bigger package. This means
 * it will silently generate wrong bytecodes. ;) BUT, if You compile it with
 * debuging ON (see <TT>boolean gnu.jel.debug.Debug.enabled</TT>) it should
 * warn You about all  possible wrong things it does. If there are more
 * assertions to be made, please submit Your patches to <TT>metlov@fzu.cz</TT>.
 */
public class ExpressionImage {
  
  /**
   * Denotes the PLUS binary operation.
   */
  public final static int BI_PL=0;

  /**
   * Denotes the MINUS binary operation.
   */
  public final static int BI_MI=1;

  /**
   * Denotes the MULTIPLY binary operation.
   */
  public final static int BI_MU=2;

  /**
   * Denotes the DIVIDE binary operation.
   */
  public final static int BI_DI=3;

  /**
   * Denotes the REMAINDER binary operation.
   */
  public final static int BI_RE=4;

  /**
   * Denotes the AND binary operation.
   */
  public final static int BI_AN=5;

  /**
   * Denotes the OR binary operation.
   */
  public final static int BI_OR=6;

  /**
   * Denotes the XOR binary operation.
   */
  public final static int BI_XO=7;


  public final static String[] binaryNames={"add","substract","multiply",
					    "divide","remainder",
					    "bitwise and","bitwise or",
					    "bitwise xor"};
  
  public final static String[] unaryNames={"negate"};
  /**
   * Denotes the unary NEGATION operation.
   */
  public final static int UN_NE=0;
  
  // NACHALO
  
  private static final byte[] prologue={
    (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE, // magic CAFE BABE !
    0x00, 0x03,             // minor
    0x00, 0x2D              // major
  };
  
  private int poolEntries=1; // Number of entries in the constant pool
  // Starts from 1  
  
  // <<Here the first CP entry of type UTF8, containing the name of the class,
  // should be written out.
  
  private static final byte[] cp_middle={ // initial entries in CP
    /*02*/ 0x07, 0x00, 0x01, // Class name is in CP1 (name of generated class)
    /*03*/ 0x01, 0x00, 0x1A, // UTF 26 bytes "gnu/jel/CompiledExpression"
	   0x67, 0x6E, 0x75, 0x2F, 0x6A, 0x65, 0x6C, 0x2F, 0x43, 0x6F, 0x6D,
	   0x70, 0x69, 0x6C, 0x65, 0x64, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73,
	   0x73, 0x69, 0x6F, 0x6E,
    /*04*/ 0x07, 0x00, 0x03, // Class name is in CP3 (superclass)
    /*05*/ 0x01, 0x00, 0x01, 0x65, // UTF 1 byte "e"
    /*06*/ 0x01, 0x00, 0x13, // UTF 19 bytes "[Ljava/lang/Object;"
	   0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67,
	   0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B,
    /*07*/ 0x01, 0x00, 0x06, // UTF 6 bytes "<init>"
	   0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E,
    /*08*/ 0x01, 0x00, 0x16, // UTF 22 bytes "([Ljava/lang/Object;)V"
	   0x28, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E,
	   0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x29, 0x56,
    /*09*/ 0x01, 0x00, 0x0A, // UTF 10 bytes "Exceptions"
	   0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x73,
    /*10*/ 0x01, 0x00, 0x04, // UTF 4 bytes "Code"
	   0x43, 0x6F, 0x64, 0x65,
    /*11*/ 0x01, 0x00, 0x03, // UTF 3 bytes "()V"
	   0x28, 0x29, 0x56,
    /*12*/ 0x0C, 0x00, 0x07, 0x00, 0x0B, // N&T, N CP7, T CP11   (<init>)
    /*13*/ 0x0A, 0x00, 0x04, 0x00, 0x0C, // Mref, C CP4,N&T CP12 (super.<init>)
    /*14*/ 0x0C, 0x00, 0x05, 0x00, 0x06, // N&T, N CP5, T CP6    (Object[] e;)
    /*15*/ 0x09, 0x00, 0x02, 0x00, 0x0E, // Fref, C CP2, N&T CP14(this.e)
    /*16*/ 0x01, 0x00, 0x08, // UTF 8 bytes "evaluate"
	   0x65, 0x76, 0x61, 0x6C, 0x75, 0x61, 0x74, 0x65,
    /*17*/ 0x01, 0x00, 0x27, // UTF 39 bytes "([Ljava/lang/Object;)Ljava/lang/Object;"
	   0x28, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61,
	   0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B,
	   0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E,
	   0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B,
    /*18*/ 0x01, 0x00, 0x13, // UTF 19 bytes "java/lang/Throwable"
	   0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
	   0x54, 0x68, 0x72, 0x6F, 0x77, 0x61, 0x62, 0x6C, 0x65,
    /*19*/ 0x07, 0x00, 0x12,  // Class name in CP19 ("java/lang/Throwable");

	   // method references for constructors of primitive types
    /*20*/ 0x01, 0x00, 0x04,  // UTF 4 bytes "(D)V" ,constructor of Double
	   0x28, 0x44, 0x29, 0x56,
    /*21*/ 0x01, 0x00, 0x04,  // UTF 4 bytes "(J)V" ,constructor of Long
	   0x28, 0x4A, 0x29, 0x56,
    /*22*/ 0x0C, 0x00, 0x07, 0x00, 0x14, // N&T, N CP7, T CP21 <init>(double)
    /*23*/ 0x0C, 0x00, 0x07, 0x00, 0x15, // N&T, N CP7, T CP20 <init>(long)
    /*24*/ 0x01, 0x00, 0x10, // UTF 16 bytes "java/lang/Double"
	   0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
	   0x44, 0x6F, 0x75, 0x62, 0x6C, 0x65,
    /*25*/ 0x01, 0x00, 0x0E, // UTF 14 bytes "java/lang/Long"
	   0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
	   0x4C, 0x6F, 0x6E, 0x67,
    /*26*/ 0x07, 0x00, 0x18, // CLASS name is in CP24 , java.lang.Double
    /*27*/ 0x07, 0x00, 0x19, // CLASS name is in CP25 , java.lang.Long
    /*28*/ 0x0A, 0x00, 0x1a, 0x00, 0x16, //Mref,C CP26,N&T CP22 (Double.<init>)
    /*29*/ 0x0A, 0x00, 0x1b, 0x00, 0x17, //Mref,C CP27,N&T CP23 (Long.<init>)
    /*30*/ // DON'T FORGET TO CHANGE CONSTRUCTOR IF YOU ADD SOMETHING !!!
	  
  };
  
  private static final byte[] intermezzo={ 
    0x00, 0x21,             // Access: PUBLIC, AC_SUPER
    0x00, 0x02,             // This class info is CP2
    0x00, 0x04,             // Super class info is CP4
    0x00, 0x00,             // no interfaces at all
    0x00, 0x01,             // have one field
    /* */  0x00, 0x02,        // PRIVATE
    /* */  0x00, 0x05,        // Name UTF is CP5, (non descriptive? who cares?)
    /* */  0x00, 0x06,        // Descriptor UTF is CP6
    /* */  0x00, 0x00,        // No attributes ( just a field)
    0x00, 0x02,             // have two methods
    /*1*/  0x00, 0x01,        // PUBLIC
    /* */  0x00, 0x07,        // Name UTF is CP7, ("<init>")
    /* */  0x00, 0x08,        // Descriptor UTF is CP8
    /* */  0x00, 0x02,        // two attributes, CODE and exceptions
    /*   */  0x00, 0x09,         //-- attribute name is CP9 ("Exceptions")
    /*     */  0x00, 0x00, 0x00, 0x02, // length is 2 bytes (following)
    /*     */  0x00, 0x00,             // No Checked Exceptions Thrown
    /*   */  0x00, 0x0A,         //-- attribute name is CP10 ("Code")
    /*     */  0x00, 0x00, 0x00, 0x16, // length is 22 bytes
    /*     */  0x00, 0x02,             // stack size is 2
    /*     */  0x00, 0x02,             // locals number is 2
    /*         */  0x00, 0x00, 0x00, 0x0A, // the code is 10 bytes long
    /*       00*/  0x2a,                  // aload_0 
    /*       01*/  (byte)0xB7,0x00,0x0D,  // invokespecial super.<init>
    /*       04*/  0x2a,                  // aload_0 
    /*       05*/  0x2b,                  // aload_1
    /*       06*/  (byte)0xB5,0x00,0x0F,  // putfield this.e
    /*       09*/  (byte)0xB1,            // return void
    /*    */   0x00, 0x00,             // no exception table
    /*    */   0x00, 0x00,             // no code attributes (don't DEBUG ;)
    /*2*/  0x00, 0x01,        // PUBLIC
    /* */  0x00, 0x10,        // Name UTF is CP16 ("evaluate")
    /* */  0x00, 0x11,        // Descriptor UTF is CP17(Object evaluate(Object[])
    /* */ 0x00, 0x02,        // two attributes, Exceptions and Code
    /*   */ 0x00, 0x09,         // -- attribute name in CP9 ("Exceptions")
    /*     */ 0x00, 0x00, 0x00, 0x04,   // length is 4 bytes (following)
    /*     */ 0x00, 0x01,               // 1 checked exception thrown
    /*     */ 0x00, 0x13,               // Class in CP19, (java.lang.Throwable)
    /*   */  0x00, 0x0A         // -- attribute name is CP10 ("Code")
  };

                    // SHOULD FOLLOW :
                    // u4 length_of_attribute = length_of_code+12
  int max_stack=2;  // u2 stack size
  int max_locals=4; // u2 num locals    //1 this,1 param,2(index 2) for buffer
                                        // I J J F D D L = +7 = 11;
                    // u4 length_of_code
                    // u1[length_of_code]={THE CODE ITSELF}
  
  private static final byte[] konetc={
    /*    */   0x00, 0x00,             // no exception table
    /*    */   0x00, 0x00,             // no code attributes (don't DEBUG ;)
    /* */ 0x00, 0x00         // No Class File attributes
  };
  
  // FINITA
  
  private static Class[] 
  primitiveIndices={Boolean.TYPE, Byte.TYPE   , Character.TYPE,
		    Short.TYPE  , Integer.TYPE, Long.TYPE,
		    Float.TYPE  , Double.TYPE};
  
  // Primitive type conversions
  
  // Widening conversions
    private final static  boolean[][] 
  //           Z     B     C     S     I     J     F     D   <-- OTKUDA
  cvt_wide={{ true,false,false,false,false,false,false,false}, // Z
	    { true, true,false,false,false,false,false,false}, // B
	    { true, true, true,false,false,false,false,false}, // C
	    { true, true, true, true,false,false,false,false}, // S
	    { true, true, true, true, true,false,false,false}, // I
	    { true, true, true, true, true, true,false,false}, // J
	    { true, true, true, true, true, true, true,false}, // F
	    { true, true, true, true, true, true, true, true}};// D


  //  OTKUDA                            Z   B   C   S   I   J   F   D 
  private final static  int[][] cvt1={{000,255,255,255,255,255,255,255}, // Z
				      {145,000,145,145,145,136,139,142}, // B
				      {146,146,000,146,146,136,139,142}, // C
				      {147,147,147,000,147,136,139,142}, // S
				      {000,000,000,000,000,136,139,142}, // I
				      {133,133,133,133,133,000,140,143}, // J
				      {134,134,134,134,134,137,000,144}, // F
				      {135,135,135,135,135,138,141,000}};// D

  //  OTKUDA                            Z   B   C   S   I   J   F   D 
  private final static  int[][] cvt2={{000,000,000,000,000,000,000,000}, // Z
				      {000,000,000,000,000,145,145,145}, // B
				      {000,000,000,000,000,146,146,146}, // C
				      {000,000,000,000,000,147,147,147}, // S
				      {000,000,000,000,000,000,000,000}, // I
				      {000,000,000,000,000,000,000,000}, // J
				      {000,000,000,000,000,000,000,000}, // F
				      {000,000,000,000,000,000,000,000}};// D
  
  private final static byte[] stkoccup={ 1,  1,  1,  1,  1,  2,  1,  2};

  //                                    Z   B   C   S   I   J   F   D 
  private final static  int[][] cvts={{000,255,255,255,255,255,255,255}, // Z
				      {255,001,255,003,004,005,006,007}, // B
				      {255,255,002,255,255,255,255,255}, // C
				      {255,003,255,003,004,005,006,007}, // S
				      {255,004,255,004,004,005,006,007}, // I
				      {255,005,255,005,005,005,006,007}, // J
				      {255,006,255,006,006,006,006,007}, // F
				      {255,007,255,007,007,007,007,007}};// D

  //                                   Z   B   C   S   I   J   F   D 
  private final static  int[][] ops={{255, 96,255, 96, 96, 97, 98, 99}, // PL
				     {255,100,255,100,100,101,102,103}, // MI
				     {255,104,255,104,104,105,106,107}, // MU
				     {255,108,255,108,108,109,110,111}, // DI
				     {255,112,255,112,112,113,114,115}, // RE
				     {126,126,255,126,126,127,255,255}, // AN
				     {128,128,255,128,128,129,255,255}, // OR
				     {130,130,255,130,130,131,255,255}};// XO

  //                                   Z   B   C   S   I   J   F   D 
  private final static  int[] norm = {000,145,146,147,000,000,000,000};

  //                                   Z   B   C   S   I   J   F   D 
  private final static int[][] una ={{116,255,255,116,116,117,118,119}};// NE

  //                                   Z   B   C   S   I   J   F   D   L
  private final static byte[] stor = { 54, 54, 54, 54, 54, 55, 56, 57, 58};
  
  private final static byte[] load = { 21, 21, 21, 21, 21, 22, 23, 24, 25};

  private final static byte[] sladdr={  4,  4,  4,  4,  4,  5,  7,  8, 10};
    
  private final static byte CONSTANT_Class = 7;
  private final static byte CONSTANT_Fieldref = 9;
  private final static byte CONSTANT_Methodref = 10;
  private final static byte CONSTANT_InterfaceMethodref = 11;
  private final static byte CONSTANT_String = 8;
  private final static byte CONSTANT_Integer = 3;
  private final static byte CONSTANT_Float = 4;
  private final static byte CONSTANT_Long = 5;
  private final static byte CONSTANT_Double = 6;
  private final static byte CONSTANT_NameAndType = 12;
  private final static byte CONSTANT_Utf8 = 1;
  
  private ByteArrayOutputStream constPool;
  private DataOutputStream constPoolData;


  private Hashtable Classes=new Hashtable(); // keys names, values CP indices
  private Hashtable Methods=new Hashtable(); // keys FQMN, values CP indices
  private Hashtable Longs=new Hashtable();   // keys Longs, values CP indices
  private Hashtable Doubles=new Hashtable(); // keys Doubles, values CP indices
  private Hashtable UTFs=new Hashtable();    // keys UTFs, values CP indices
  private Hashtable Strings=new Hashtable(); // keys Strings, values CP indices
  private Hashtable Integers=new Hashtable();// keys Integers,values CP indices
  private Hashtable Floats=new Hashtable();  // keys Floats, values CP indices
  
  private ByteArrayOutputStream methodText;
  private DataOutputStream methodTextData;

  private final static String classNamePrefix="gnu.jel.generated.E_";
  private volatile static long numberGen=0;
  private String className;
    
  private boolean classFinished=false;
  private int currSSW=0; // Current stack size in words
  private Vector objectPool=new Vector(); // pool of constants of type Object
  private Stack typesStk = new Stack(); // Types in Java stack

  // Arrays of Params types for functions
  private Stack functionParams = new Stack();
  
  // integer arrays describing the progress of accumulation.
  // {methodref_CP, numparams, currparam, lastparam_stkpos, isstatic}
  private Stack functionINTS = new Stack();

  // Return types of functions
  private Stack functionRet = new Stack();
  
  /**
   * Constructs and initializes empty expression image.
   * <P> The class name of the expression is generated automatically by
   * appending successive integer numbers (incremented for each instantiated
   *  ExpressionImage) to the "gnu.jel.generated.E_". This should ensure the
   * names of expressions are unique. The number is represented by long integer
   * allowing to instantiate close to 10^19 expressions during the program
   * lifetime. This means program, instantiating a new expression each second,
   * will fail with numeric overflow in about 300000000000 years, which is 
   * big enough for most applications.
   * <P> Immediately after the construction of the expression a number of
   * code generating methods can be issued:
   * <PRE>
   * asm_load_primitive(...)
   * asm_Sum(..)
   * ...
   * </PRE>
   * <P> The code generation should be finished by one of the return methods:
   * <PRE>
   * asm_return();
   * asm_ThrowReturn();
   * </PRE>
   * <P>Before any return method is called, the code generation is assumed 
   * to be in progress and methods, attempting to get class representation 
   * (getImage) or instantiate the class (getExpression) will fail assertion.
   * <P>After the code generation is finished and one of return methods is 
   * called methods, attempting to modify the class (i.e. 
   * <TT>asm_load_primitive(...)</TT>...) will fail. This is done to ensure 
   * integrity of generated classes.
   * <P> Constants of the type object are allowed in the generated expressions.
   * (If You don't know in java the constants are residing in the constant
   * pool and it is impossible to store constants of Object type other than 
   * java.lang.String there. This assembler overcomes this limitation by 
   * allowing to use other _NON-MUTABLE_ objects as constants. More
   * information is provided in the description of <TT>asm_load_object</TT>
   * method.
   */
  public ExpressionImage() {
    constPool=new ByteArrayOutputStream();
    constPoolData=new DataOutputStream(constPool);
    methodText=new ByteArrayOutputStream();
    methodTextData=new DataOutputStream(methodText);
    
    // generate unique classname
    className=classNamePrefix+Long.toString(numberGen++);
    
    classFinished=false;
    
    int cnIndex=getUTFIndex(toHistoricalForm(className));//write the class Name
    if (Debug.enabled) Debug.assert(cnIndex==1,
				    "Class name Should be the first in CP");
    try {    
      constPoolData.write(cp_middle);  // The rest of CP is already prepared
      poolEntries+=28; // Number of entries in the rest
    } catch (java.io.IOException e) {
      if (Debug.enabled) Debug.reportThrowable(e);
    };
    
  };

  private final void checkAlter() {
    if (Debug.enabled) 
      Debug.assert(!classFinished,"Attempt to modify finished class.");
  };

  /**
   * Used to get the index of the given UTF8 string in the Constant Pool.
   * <P> If the specified string was not found in the pool -- it is added.
   * @param str the string to look for ( to add )
   * @return index of the string in the Constant Pool.
   */
  private int getUTFIndex(String str) {
    // Check if it is in the pool already
    Integer index=(Integer)UTFs.get(str);
    if (index==null) {    // add UTF to the pool
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_Utf8);
	constPoolData.writeUTF(str);
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      UTFs.put(str,index);
    };
    return index.intValue();
  };
  
  /**
   * Used to get the index of the given Long constant in the Constant Pool.
   * <P> If the specified Long was not found in the pool it is added.
   *
   * @param val the Long to look for ( to add )
   * @return index of the Long in the Constant Pool.
   */
  private int getLongIndex(Long val) {
    // Check if it is in the pool already
    Integer index=(Integer)Longs.get(val);
    if (index==null) {    // add Long to the pool
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_Long);
	constPoolData.writeLong(val.longValue());
	poolEntries++; // Long occupies two entries in CP, weird !!!
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      Longs.put(val,index);
    };
    return index.intValue();
  };

  /**
   * Used to get the index of the given Integer constant in the Constant Pool.
   * <P> If the specified Integer was not found in the pool it is added.
   *
   * @param val the Integer to look for ( to add )
   * @return index of the Integer in the Constant Pool.
   */
  private int getIntIndex(Integer val) {
    // Check if it is in the pool already
    Integer index=(Integer)Integers.get(val);
    if (index==null) {    // add Integer to the pool
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_Integer);
	constPoolData.writeInt(val.intValue());
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      Integers.put(val,index);
    };
    return index.intValue();
  };

  private int getFloatIndex(Float val) {
    // Check if it is in the pool already
    Integer index=(Integer)Floats.get(val);
    if (index==null) {    // add Float to the pool
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_Float);
	constPoolData.writeFloat(val.floatValue());
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      Floats.put(val,index);
    };
    return index.intValue();
  };


  /**
   * Used to get the index of the given Double constant in the Constant Pool.
   * <P> If the specified Double was not found in the pool it is added.
   *
   * @param val the Double to look for ( to add )
   * @return index of the Double in the Constant Pool.
   */
  private int getDoubleIndex(Double val) {
    // Check if it is in the pool already
    Integer index=(Integer)Doubles.get(val);
    if (index==null) {    // add Double to the pool
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_Double);
	constPoolData.writeDouble(val.doubleValue());
	poolEntries++; // Double occupies two entries in CP, weird !!!
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      Doubles.put(val,index);
    };
    return index.intValue();
  };

  /**
   * Used to get the index of the given String constant in the Constant Pool.
   * <P> If the specified String was not found in the pool it is added.
   *
   * @param str the String to look for ( to add )
   * @return index of the String in the Constant Pool.
   */
  private int getStringIndex(String str) {
    // Check if it is in the pool already
    Integer index=(Integer)Strings.get(str);
    if (index==null) {    // add String to the pool
      int UTFIndex=getUTFIndex(str); // write string , if needed
      
      index=new Integer(poolEntries++);
      try {
	checkAlter();
	constPoolData.write(CONSTANT_String);
	constPoolData.writeShort(UTFIndex);
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      
      Strings.put(str,index);
    };
    return index.intValue();
  };

  /**
   * Used to get the index of the given Class info in the Constant Pool.
   * <P> If the specified Class info was not found in the pool it is added.
   *
   * @param the_class the Class object of the class to look for ( to add )
   * @return index of the Class info in the Constant Pool.
   */
  private int getClassIndex(Class the_class) {
    // Check if it is in the pool already
    Integer index=(Integer)Classes.get(the_class);
    if (index==null) {    // add Class to the pool
      String fqcn=the_class.getName();
      String histNameStr=toHistoricalForm(fqcn);
      
      int UTFIndex=getUTFIndex(histNameStr); // write string , if needed
      
      index=new Integer(poolEntries++);
      writeClassInfo(UTFIndex);
      Classes.put(the_class,index);
    };
    return index.intValue();
  };

  private void writeClassInfo(int nameInd) {
    try {
      checkAlter();
      constPoolData.write(CONSTANT_Class);
      constPoolData.writeShort(nameInd);
    } catch (java.io.IOException e) {
      if (Debug.enabled) Debug.reportThrowable(e);
    };
  };

  /*
   * Used to get the index of the given Method reference in the Constant Pool.
   * <P> If the specified Method reference was not found in the pool it is 
   * added (together with corresponding NameAndType record).
   *
   * @param the_method Method object to look for ( to add )
   * @return index of the Method Reference in the Constant Pool.
   */
  private int getMethodIndex(java.lang.reflect.Method the_method) {
    // Check if it is in the pool already
    Integer index=(Integer)Methods.get(the_method);
    if (index==null) {    // add Method to the pool
      
      int name_ind=getUTFIndex(the_method.getName());
      int sign_ind=getUTFIndex(getSignature(the_method));

      Class declaringClass=the_method.getDeclaringClass();
      int cls_ind=getClassIndex(declaringClass);
      
      // Create Name and Type record
      int nat_ind=poolEntries++;
      try {
	checkAlter();
	constPoolData.write(CONSTANT_NameAndType);
	constPoolData.writeShort(name_ind);
	constPoolData.writeShort(sign_ind);
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };

      index=new Integer(poolEntries++);
      
      // Create Methodref or InterfaceMethodref

      int mtd_type=CONSTANT_Methodref;
      if (declaringClass.isInterface()) 
	mtd_type=CONSTANT_InterfaceMethodref;
      try {
	checkAlter();
	constPoolData.write(mtd_type);
	constPoolData.writeShort(cls_ind);
	constPoolData.writeShort(nat_ind);
      } catch (java.io.IOException e) {
	if (Debug.enabled) Debug.reportThrowable(e);
      };
      Methods.put(the_method,index);
    };
    return index.intValue();
  };

  /**
   * Computes signature of the given method.
   * <P> The signature of the method(Method descriptor) is the string and 
   * it's format is described in the paragraph 4.3.3 of the Java VM
   * specification (ISBN 0-201-63451-1).
   * <P> This utility method can be used outside of the JEL package
   * it does not involve any JEL specific assumptions and should follow
   * JVM Specification precisely.
   * @param m is the method to compute the sugnature of.
   * @return the method sugnature.
   */
  public static String getSignature(java.lang.reflect.Method m) {
    StringBuffer signature=new StringBuffer();
    signature.append('(');
    Class[] parameters=m.getParameterTypes();
    for(int i=0;i<parameters.length;i++) 
      signature.append(getSignature(parameters[i]));
    signature.append(')');
    signature.append(getSignature(m.getReturnType()));
    return signature.toString();
  };
  
  /**
   * Computes the signature of the given class.
   * <P> The signature of the class (Field descriptor) is the string and 
   * it's format is described in the paragraph 4.3.2 of the Java VM 
   * specification (ISBN 0-201-63451-1).
   * <P>The same can be done utilizing <TT>java.lang.Class.getName()</TT>,
   *  however that Java method do not always follow Java VM spec.... 
   * To be on the safe (controllable) side I dicided to implement this method
   * myself.
   * <P> This utility method can be used outside of the JEL package
   * it does not involve any JEL specific assumptions and should follow
   * JVM Specification precisely.
   * @param cls is the class to compute the sgnature of. Can be primitive or
   *            array type.
   * @return the class signature.
   */
  public static String getSignature(Class cls) {
    if (cls==java.lang.Byte.TYPE) return "B";
    if (cls==java.lang.Character.TYPE) return "C";
    if (cls==java.lang.Double.TYPE) return "D";
    if (cls==java.lang.Float.TYPE) return "F";
    if (cls==java.lang.Integer.TYPE) return "I";
    if (cls==java.lang.Long.TYPE) return "J";
    if (cls==java.lang.Short.TYPE) return "S";
    if (cls==java.lang.Boolean.TYPE) return "Z";
    if (cls==java.lang.Void.TYPE) return "V";
    if (cls.isArray()) return '['+getSignature(cls.getComponentType());
    // Just a class
    return 'L'+toHistoricalForm(cls.getName())+';';
  };

  private static String toHistoricalForm(String className) {
    StringBuffer histName=new StringBuffer(className);
    int namelen=className.length();
    for(int i=className.indexOf('.');(i>0) && (i<namelen);
	i=className.indexOf('.',i+1)) histName.setCharAt(i,'/');
    return histName.toString();
  };

  /**
   * Used to get the binary image of the class.
   * <P>If You save this array into the file with name to be the first
   * ASCII sequence in it You should get a valid java class file. 
   * @return a binary class representation.
   */
  byte[] getImage() {
    if (Debug.enabled)
      Debug.assert(classFinished,"Attempt to get the code of unfinished"+
		   " class.");
    ByteArrayOutputStream image=new ByteArrayOutputStream();
    DataOutputStream imageData=new DataOutputStream(image);
    try {
      imageData.write(prologue);
      imageData.writeShort(poolEntries);
      constPool.writeTo(imageData);
      imageData.write(intermezzo);
      int code_len=methodText.size();
      imageData.writeInt(code_len+12);
      imageData.writeShort(max_stack);
      imageData.writeShort(max_locals);
      imageData.writeInt(code_len);
      methodText.writeTo(imageData);
      imageData.write(konetc);
    } catch (java.io.IOException e) {
      if (Debug.enabled) Debug.reportThrowable(e);
    };
           
    return image.toByteArray();
  };

  
  /**
   * Constructs a new instance of the CompiledExpression with this image.
   * @return a CompiledExpression instance or null if there was a error.
   */
  public CompiledExpression getExpression() {
    if (Debug.enabled)
      Debug.assert(classFinished,"Attempt to instantiate unfinished"+
		   " class.");

    byte[] image=getImage();
    try {
      
      // Loading
      ExpressionLoader el=new ExpressionLoader(className,image);
      Class cls=el.loadClass(className);
      
      java.lang.reflect.Constructor[] ca=cls.getConstructors();
      if (Debug.enabled)
	Debug.assert(ca.length==1,"There should be only one constructor of "+
		     "the compiled expression.");
      
      // Passing object type constants
      Object[][] constrParams=new Object[1][objectPool.size()];
      if (objectPool.size()>0) {
	objectPool.copyInto(constrParams[0]);
      };
      
      // Instantiating
      return (CompiledExpression)ca[0].newInstance(constrParams);
      
    } catch(Exception e) {
      if (Debug.enabled)
	Debug.reportThrowable(e);
      return null;
    };
  };

  private final void ensureStack() {
    if (currSSW>max_stack) max_stack=currSSW;
  };

  private final void codeOP(int op) {
    try {
      methodTextData.write(op);
    } catch (java.io.IOException e) {
      // Can't be
    };
  };

  private final void codeINDEX(int ind) {
    try {
      methodTextData.writeShort(ind);
    } catch (java.io.IOException e) {
      // Can't be
    };
  };
  
  static final int primitiveID(Class c) {
    if (Debug.enabled) 
      Debug.assert(c.isPrimitive(),
		   "PrimitiveID should be used for primitive types only.");
    int i;
    for(i=0;(i<primitiveIndices.length) && (primitiveIndices[i]!=c);i++);
    if (Debug.enabled)
      Debug.assert(i<primitiveIndices.length,
		   "You IDIOT !!! You didn't put _ALL_ primitive types"+
		   " into primitiveIndices array.");
    return i;
  };

  private static final int stackSpace(Class c) {
    if (c.isPrimitive()) return stkoccup[primitiveID(c)];
    return 1;
  };

  // ------------------------- ASSEMBLER ------------------------------
  /**
   * Loads a constant into the Java stack.
   * <P> Currently only java.lang.Long and java.lang.Double can be loaded
   * as primitive types. Other classes will load as <TT>reference</TT>s.
   * <P>Internally, this code generator supports operations with all Java
   * primitive types. Now, there is only single way how these other
   *  primitive types can come into the stack, namely as function return 
   * values. This will be extended in the subsequent versions allowing to
   * load them directly using this method. 
   * @param o is the object to load.
   */
  public void asm_load_object(Object o) {
    if (o==null) {
      typesStk.push(null);
      codeOP(0x01);    //|   aconst_null
      currSSW+=1; ensureStack();
    } else {
      // Compute the index into the object pool
      int opi=objectPool.indexOf(o);
      if (opi==-1) {
	objectPool.addElement(o);
	opi=objectPool.size()-1;
      };
      typesStk.push(o.getClass());
      
      currSSW+=2; ensureStack();
      // Generate loading from OP array
      codeOP(0x2a);        //|   aload_0         ; this
      codeOP(0xb4);        //|   getfield
      codeINDEX(15);       //|     00, 0F        ; CP15 FieldRef this.e.
      
      if (opi<255) { // if index is small
	codeOP(0x10);      //|   bipush
	codeOP(opi);       //|     <opi>
      } else {       // if index is large
	int opii=getIntIndex(new Integer(opi));
      codeOP(0x13);      //|   ldc_w
      codeINDEX(opii);   //|     <opii_1><opii_2>
      };
      
      codeOP(0x32);        //|   aaload          ; Object constant loaded
    
      int clsind=getClassIndex(o.getClass());
      codeOP(0xc0);       //|   checkcast          ; runtime cast
      codeINDEX(clsind);  //|     <ind_1><ind_2>  
      
      currSSW--; // temporary stack word is out
    };
    
  };

  
  public void asm_load_primitive(Object o) {
    checkAlter();

    int opcode=-1;
    int CP_index=-1;
    int stack_occupation=0;
    Class type_loaded=null;
    Class cvt_to_type=null;
    boolean attempt_short=false;
    
    if (o instanceof Double) {
      stack_occupation=2;
      opcode=0x14;                        //|   ldc2_w
      CP_index=getDoubleIndex((Double)o); 
      type_loaded=Double.TYPE;
    } else if (o instanceof Long) {
      stack_occupation=2;
      opcode=0x14;                        //|   ldc2_w
      CP_index=getLongIndex((Long)o);
      type_loaded=Long.TYPE;
    } else if (o instanceof Float) { 
      stack_occupation=1;
      opcode=0x13;
      CP_index=getFloatIndex((Float)o);
      type_loaded=Float.TYPE;
      attempt_short=true;
    } else if (o instanceof Integer) { 
      stack_occupation=1;
      opcode=0x13;
      CP_index=getIntIndex((Integer)o);
      type_loaded=Integer.TYPE;
      attempt_short=true;
    } else if (o instanceof Byte) { 
      stack_occupation=1;
      opcode=0x13;
      CP_index=getIntIndex(new Integer(((Byte)o).intValue()));
      type_loaded=Integer.TYPE;
      cvt_to_type=Byte.TYPE;
      attempt_short=true;
    } else if (o instanceof Short) { 
      stack_occupation=1;
      opcode=0x13;
      CP_index=getIntIndex(new Integer(((Short)o).intValue()));
      type_loaded=Integer.TYPE;
      cvt_to_type=Short.TYPE;
      attempt_short=true;
    } else if (o instanceof Character) { 
      stack_occupation=1;
      opcode=0x13;
      CP_index=getIntIndex(new Integer(((Character)o).charValue()));
      type_loaded=Integer.TYPE;
      cvt_to_type=Character.TYPE;
      attempt_short=true;
    } else if (o instanceof Boolean) { 
      stack_occupation=1;
      opcode=0x13;
      int ivalue=0;
      if (((Boolean) o).booleanValue()) ivalue=1;
      CP_index=getIntIndex(new Integer(ivalue));
      type_loaded=Boolean.TYPE;
      attempt_short=true;
    } else { // Object
      if (Debug.enabled) 
	Debug.println("Attempt to load object as a primitive type detected.");
    };

    // general purpose code generation
    if (type_loaded!=null) { // generate code
      if (attempt_short && (CP_index<255)) {  // Generate short ref to CP
	codeOP(opcode-1);
	codeOP(CP_index);
      } else {
	codeOP(opcode);
	codeINDEX(CP_index);
      };
      currSSW+=stack_occupation; ensureStack();
      typesStk.push(type_loaded);
      if (cvt_to_type!=null)
	asm_convert(cvt_to_type);
    };
  };

  /**
   * Tests is this codegen can generate code to convert from from one type to another even with possible loss of the information.
   * <P> Both widening and narrowing conversions are supported by the 
   * assembler, this function reports all supported conversions.
   * <P> There are thoughts about support wrapping/unwrapping conversions 
   * (i.e. double <--> java.lang.Double) as described in the Java Reflection 
   * Specification. Currently these are not supported.
   * 
   * @param t1 is the type You want to convert from.
   * @param t2 is the type You want to convert to.
   * @return true is the corresponding conversion is supported.
   */
  public static boolean canConvert(Class t1,Class t2) {
    boolean pt1=t1.isPrimitive();
    boolean pt2=t2.isPrimitive();
    
    if (pt2 && pt1) {
      int it1=primitiveID(t1);
      int it2=primitiveID(t2);
      return (cvt1[it2][it1]!=255);
    };
    
    if (pt2 ^  pt1) return false; // Can't convert yet between PT and OBJ.
    
    return t2.isAssignableFrom(t1);
  };

  /**
   * Tests is this codegen can generate code to convert from from one type to another without loss of the information.
   * <P> Both widening and narrowing conversions are supported by the 
   * assembler itself, this function reports widening conversions only.
   * 
   * @param t1 is the type You want to convert from.
   * @param t2 is the type You want to convert to.
   * @return true is the corresponding widening conversion is supported.
   */
  public static boolean canConvertByWidening(Class t1,Class t2) {
    boolean pt1=t1.isPrimitive();
    boolean pt2=t2.isPrimitive();
    
    if (pt2 && pt1) {
      int it1=primitiveID(t1);
      int it2=primitiveID(t2);
      return cvt_wide[it2][it1];
    };
    if (pt2 ^  pt1) return false; // Can't convert yet between PT and OBJ.
    return t2.isAssignableFrom(t1);
  };

  /**
   * Converts current top of the java stack to the given class type.
   * @param type is the type to convert to, can be primitive.
   * @return true if conversion was made, false if types incompatible.
   */
  public boolean asm_convert(Class type) {

    Class typeNOW=(Class) typesStk.peek();
    
    if (type==typeNOW) return true;

    boolean ptn=typeNOW.isPrimitive();
    boolean pt=type.isPrimitive();
    
    if (ptn && pt) {
      int tni=primitiveID(typeNOW);
      int ti=primitiveID(type);
      int op1= cvt1[ti][tni]; 
      int op2= cvt2[ti][tni];
      if (op1==255) {
	if (Debug.enabled) 
	  Debug.assert(false,"Incompatible types in asm_convert");
	return false;  
      };
      
      currSSW=currSSW+stkoccup[ti]-stkoccup[tni]; ensureStack();
      if (op1!=0) codeOP(op1);
      if (op2!=0) codeOP(op2);
      typesStk.pop();
      typesStk.push(type);
      return true;
    };
    
    if (ptn ^ pt) {
      if (Debug.enabled)
	Debug.println("Can't convert between pimitive and object types in"+
		      " this version.");
      return false;
    };

    typesStk.pop();
    typesStk.push(type);
   
    return true; // classes can only be downcasted ( no (Class)var syntax yet)
    
  };

  /**
   * Used to get the most general type of the two given.
   * <P> Both operands for the binary OP type should be converted to the
   * most general of them, returned by this function.
   * <P> Both parameters should be Java primitive types.
   * @param c1  first operand of the binary OP
   * @param c2  second operand of the binary OP
   * @return Class, representing the most general primitive type of two.
   */
  public static Class getMostGeneralType(Class c1, Class c2) {
    if (Debug.enabled)
      Debug.assert(c1.isPrimitive() && c2.isPrimitive(),
		   "Both types for the binary OP should be primitive.");
    int ic1=primitiveID(c1);
    int ic2=primitiveID(c2);
    int idx=cvts[ic1][ic2];
    if (idx==255) return null;
    return primitiveIndices[idx];
  };

  /**
   * Used to test if this codegen. can generate requested unary operation.
   * @param is the code of operation (UN_NE).
   * @param type is the type of the operand.
   * @return if the code for such operation can be generated.
   */
  public static boolean canGenerateUnary(int op, Class type) {
    if (!type.isPrimitive()) return false;
    return (una[op][primitiveID(type)]!=255);
  };
  


  /**
   * Generates code to perform given unary operation.
   * <P> Unary operations can be performed on the primitive
   * Java types only.
   * <P> If operation can not be supported for the given type
   * this method returns false and generates nothing.
   * @param o type of operation to perform, one of NE
   * @return true if the code was successfully generated.
   */
  public boolean asm_unary(int o) {
    Class a=(Class) typesStk.peek();

    if (!a.isPrimitive()) {
      if (Debug.enabled)
	Debug.println("Unary OPs are supported on primitive types only");
      return false;
    };
    
    int ia=primitiveID(a);

    int opc=una[o][ia];

    if (opc==255) {
      if (Debug.enabled)
	Debug.println("The unary operation requested ("+o+") is not supported"+
		      " for type in stack ("+ia+").");
      return false;
    };
    
    codeOP(opc);
    return true;
  };

  /**
   * Used to test if this codegen. can generate requested binary operation.
   * @param is the code of operation (BI_PL,BI_MI,BI_MU ...).
   * @param type is the type of both operands.
   * @return if the code for such operation can be generated.
   */
  public static boolean canGenerateBinary(int op, Class type) {
    if (!type.isPrimitive()) return false;
    return (ops[op][primitiveID(type)]!=255);
  };

  /**
   * Used to test if this codegen. can generate requested binary operation.
   * @param is the code of operation (BI_PL,BI_MI,BI_MU ...).
   * @param t1 is the type of the first operand.
   * @param t2 is the type of the second operand.
   * @return if the code for such operation can be generated.
   */
  public static boolean canGenerateBinary(int op, Class t1, Class t2) {
    if (!(t1.isPrimitive() && t2.isPrimitive())) return false;
    Class ct=getMostGeneralType(t1,t2);
    if (ct==null) return false;
    return canGenerateBinary(op,ct);
  };



  /**
   * Generates code to perform given binary operation.
   * <P> Binary operations can be performed on the primitive
   * Java types only. The two numbers in Java stack have to have the
   * same type, in order for binary operation to succeed.
   * <P> If operation can not be supported due to incompatible types of
   * operands this method returns false and generates nothing.
   * @param o type of operation to perform, one of PL,MI,MU,DI,AN,OR,XO
   * @return true if the code was successfully generated.
   */
  public boolean asm_binary(int o) {
    // peek two types on stack
    Class a2=(Class)typesStk.peek();
    Object tmp=typesStk.pop();
    Class a1=(Class)typesStk.peek();
    typesStk.push(tmp);
    
    if ((!a1.isPrimitive()) || (!a2.isPrimitive())) {
      if (Debug.enabled)
	Debug.println("Binary OPs are supported on primitive types only");
      return false;
    };

    int ia1=primitiveID(a1);
    int ia2=primitiveID(a2);
    
    if (ia1 != ia2) {
      if (Debug.enabled)
	Debug.println("Types are not the same ("+ia1+"!="+ia2+
		      "), conversion should be done prior to OP.");
      return false;
    };
    
    int opc=ops[o][ia1];

    if (opc==255) {
      if (Debug.enabled)
	Debug.println("The binary operation requested ("+o+") is not "+
		      "supported for types in stack ("+ia1+") .");
      return false;
    };
    
    codeOP(opc);
    typesStk.pop();  // throw one type;
    currSSW-=stkoccup[ia1];
    return true;
  };

  /**
   * Prepare stack for the method call.
   * <P> Method can be static or virtual.
   * <P> Call to this method should be followed zero or more asm_func_param()
   * calls which would transform parameters on top of the stack to be 
   * compatible with the function input (each additioal call will process
   * the next formal parameter).
   * <P> The actual code for the call is generated by calling asm_func_call()
   * method.
   * <P> Function calls can be nested.
   * <P> Correctness of <TT>id</TT> parameter can not be checked by the code 
   * generator !!! Be careful, runtime error will be generated if it's
   * incorrect.
   * @param f is the method to call.
   * @param id is the number of the function in the array of the 
   *    instance of the object, implementing the function (for virtual
   *    methods only).
   */
  public void asm_func_start(java.lang.reflect.Method f,int id) {
    Class[] params=f.getParameterTypes();
    functionParams.push(params);
    boolean isStatic=java.lang.reflect.Modifier.isStatic(f.getModifiers());
    int[] descr={getMethodIndex(f),(isStatic?1:0),params.length,0,currSSW};
    functionINTS.push(descr);
    functionRet.push(f.getReturnType());
    if (!isStatic) { // load Object reference to stack
      checkAlter();
      currSSW+=2; ensureStack();
      codeOP(0x2b);     //|  aload_1   ; load dynamic lib reference

      if (id<255) { // if index is small
	codeOP(0x10);     //|   bipush
	codeOP(id);       //|     <opi>
      } else {       // if index is large
	int idi=getIntIndex(new Integer(id));
	codeOP(0x13);     //|   ldc_w
	codeINDEX(idi);  //|     <opii_1><opii_2>
      };
      
      codeOP(0x32);      //|  aaload    ; load dynamic module reference

      int cls_CP=getClassIndex(f.getDeclaringClass());
      codeOP(0xc0);      //|  checkcast  ; cast to the right class
      codeINDEX(cls_CP); //|   <ind_1><ind_2>
      currSSW--;
      descr[4]=currSSW;
    };
  };

  /**
   * Converts the current top of Java stack to the next formal parameter type.
   * <P>The type of the quantity on top of the Java stack should be
   * convertible to the function required input.
   * <P> Use can convert function to test for convertibility.
   * @return false if types are incompatible.
   */
  public boolean asm_func_param() {
    if (Debug.enabled)
      Debug.assert(!functionParams.empty(),
		   "No function call being generated.");
    Class[] params= (Class[])functionParams.peek();
    int[] descr=(int[])functionINTS.peek();
    int currpar=descr[3];
    if (Debug.enabled)
      Debug.assert(currpar<descr[2],
		   "Too many parameters for function");
    
    boolean converted=asm_convert(params[currpar]);
    
    if (!converted) {
      if (Debug.enabled)
	Debug.println("Can't convert to the required formal parameter type.");
      return false;
    };
    
    if (Debug.enabled)
      Debug.assert(currSSW==descr[4]+stackSpace(params[currpar]),
		   "Stack mismatch. Something was left in the stack in "+
		   "between of function formal parameters.");
    descr[4]=currSSW;
    descr[3]++; // next parameter.
    return true;
  };
  
  /**
   * Generates the actual call to the current function.
   * <P>The stack structure should be already prepared by 
   * <TT>asm_func_start(...)</TT> and <TT>asm_func_param</TT> methods.
   * @return false if the number of formal parameters is mismatched.
   */
  public boolean asm_func_call() {
    if (Debug.enabled)
      Debug.assert(!functionParams.empty(),
		   "No function call being generated.");
    Class[] params= (Class[])functionParams.pop();
    int[] descr=(int[])functionINTS.pop();
    Class retType=(Class)functionRet.pop();
    int currpar=descr[3];

    if (currpar!=descr[2]) {
      if (Debug.enabled)
	Debug.println("Not all parameters were supplied for function.");
      return false;  // fail on error
    };

    boolean isStatic=(descr[1]==1);
    int totstk=(isStatic?0:1);
    
    checkAlter();
    if (isStatic) {
      codeOP(0xb8);       //| invokestatic
    } else {
      codeOP(0xb6);       //| invokevirtual
    };
    codeINDEX(descr[0]);  //|   <ind_1><ind_2>
    for(int i=0;i<params.length;i++) {
      totstk+=stackSpace(params[i]);
      typesStk.pop();
    };
    currSSW-=totstk;
          
    typesStk.push(retType);
    currSSW+=stackSpace(retType); ensureStack();
    return true;
  };

  /**
   * Finishes construction of the expression normally.
   * <P> Number on top of the Java stack converted is returned. If it is
   * of the primitive type it is converted to the Reflection object (i.e.
   * <TT>double</TT> to <TT>java.lang.Double</TT>. Currently such conversion
   * is supported for doubles and longs only.
   */
  public void asm_return() {
    checkAlter();
    Class typeout=null;
    try { typeout=(Class) typesStk.peek(); } 
    catch (java.util.EmptyStackException e) {
      // Nothing in stack is the same as void in stack and is returned as null
      asm_load_object(null);
      //if (Debug.enabled) Debug.reportThrowable(e);
    };
    
    // Convert type to object
    if (typeout!=null) {
      if        ((typeout==Double.TYPE) || (typeout==Float.TYPE)) {
	asm_convert(Double.TYPE); // These two go as java.lang.Double
	currSSW+=2; ensureStack();
	// Let's create return(new java.lang.Double(what_is_now_in_stack));
	codeOP(0x49);        //| dstore_2
	codeOP(0xbb);        //| new
	codeINDEX(26);       //|  <ind_1><ind_2> java.lang.Double
	codeOP(0x59);        //| dup
	codeOP(0x28);        //| dload_2
	codeOP(0xb7);        //| invokespecial
	codeINDEX(28);       //| <ind_1><ind_2> java.lang.Double.<init>(double)
	//	codeOP(0xb0);        //|  areturn
	currSSW-=3;
	classFinished=true;      
      } else if (typeout.isPrimitive()) { // long, int, char, byte, short, bool
	asm_convert(Long.TYPE); // all these are returned as java.lang.Long
	currSSW+=2; ensureStack();

	// Let's create return(new java.lang.Long(what_is_now_in_stack));
	codeOP(0x41);        //| lstore_2
	codeOP(0xbb);        //| new
	codeINDEX(27);       //|  <ind_1><ind_2> java.lang.Long
	codeOP(0x59);        //| dup
	codeOP(0x20);        //| lload_2
	codeOP(0xb7);        //| invokespecial
	codeINDEX(29);       //|  <ind_1><ind_2> java.lang.Long.<init>(long)
	//	codeOP(0xb0);        //|  areturn
	currSSW-=3;
	classFinished=true;      
      }; /* other primitive types should go here */ 
      
    };

    typesStk.pop();

    if (Debug.enabled) 
      Debug.assert(typesStk.size()==0,"Stack is not empty when returning.");

    if (Debug.enabled)
      Debug.assert(currSSW==1,"Words left in stack = "+currSSW+"should be 1.");

    // Now we certainly have the object in stack (can be null)
    codeOP(0xb0);              //|  areturn
    classFinished=true;
  };
  
  /**
   * Finishes construction if the method abnormally.
   * <P> Object on top of the Java stack (which should be instance of
   * <TT>java.lang.Throwable</TT>) is thrown.
   */
  public void asm_ThrowReturn() {
    checkAlter();
    Class typeout=null;
    try { typeout=(Class) typesStk.pop(); } 
    catch (java.util.EmptyStackException e) {
      if (Debug.enabled) Debug.reportThrowable(e);
    };
    if (Debug.enabled) { // Check cast
      try { 
	Class thr=Class.forName("java.lang.Throwable");
	Debug.assert(thr.isAssignableFrom(typeout),
	       "Attempt to throw non throwable.");
      } catch (ClassNotFoundException e) {
      };
    };
    codeOP(0xbf);               //|  athrow
    classFinished=true;
  };

  // ------------------------- TESTSUITE -------------------------------

  /**
   * Performs unitary test of the code generator.
   * @param args ignored.
   */
  public static void main(String[] args) {
    Tester t=new Tester(System.out);
    test(t);
    t.summarize();
  };

  static void dumpImage(ExpressionImage ei) {
    if (Debug.enabled) {
      try {
	java.io.FileOutputStream fos=
	  new java.io.FileOutputStream("dump.class");
	fos.write(ei.getImage());
	fos.close();
      } catch (Exception e) {
	Debug.println("Can't dump generated class file.");
      };
    };
  };

  private static boolean transmitPrimitive(Object obj,Object tobe) 
       throws Throwable {
    if (!Debug.enabled) return true;
    ExpressionImage ei=null;
    Object res=null;
    try {
      ei=new ExpressionImage();
      ei.asm_load_primitive(obj);
      ei.asm_return();
      CompiledExpression cex=ei.getExpression();
      res=cex.evaluate(null);
    } catch (Throwable e) {
      dumpImage(ei);
      throw e;
    };
    boolean success=res.equals(tobe);
    if (!success) {
      Debug.println("Transmit of \""+obj.getClass().getName()+"\" failed.");
      Debug.println("Expected \""+tobe.toString()+"\",  encountered \""+
		    res.toString()+"\".");
      dumpImage(ei);
    };
    return success;
  };

  /**
   * Performs unitary test of the code generator.
   * <p> Used if all package is being tested and not just codegen.
   * @param t Tester to report test results.
   */
  public static void test(Tester t) {
    if (Debug.enabled) {
      // Utility classes tests
      {
	t.startTest("toHistoricalForm(\"java.lang.String\")");
	t.compare(toHistoricalForm("java.lang.String"),
		  "java/lang/String");

	t.startTest("getSignature((\"a string\").getClass())");
	t.compare(getSignature(("a string").getClass()),
		  "Ljava/lang/String;");
	
	t.startTest("getSignature((new int[10]).getClass())");
	t.compare(getSignature((new int[10]).getClass()),
		  "[I");
	
	t.startTest("getSignature((new Object[10]).getClass())");
	t.compare(getSignature((new Object[10]).getClass()),
		  "[Ljava/lang/Object;");

      };

      // Constant pool filling test
      {
	ExpressionImage ei=new ExpressionImage();
	
	t.startTest("Add UTF twice");
	String s1="some string";
	int si=ei.getUTFIndex(s1);
	int sii=ei.getUTFIndex(s1);
	t.compare(sii,si);

	t.startTest("Checking the index of added UTF");
	t.compare(si,30);
	
	t.startTest("Add Long twice");
	int li=ei.getLongIndex(new Long(10));
	int lii=ei.getLongIndex(new Long(10));
	t.compare(lii,li);

	t.startTest("Add Integer twice");
	int ii=ei.getIntIndex(new Integer(15));
	int iii=ei.getIntIndex(new Integer(15));
	t.compare(iii,ii);

	t.startTest("Add Float twice");
	int ff=ei.getFloatIndex(new Float(15.0f));
	int fff=ei.getFloatIndex(new Float(15.0f));
	t.compare(fff,ff);

	t.startTest("Add Double twice");
	int di=ei.getDoubleIndex(new Double(10.0));
	int dii=ei.getDoubleIndex(new Double(10.0));
	t.compare(dii,di);

	t.startTest("Add a new String twice");
	String s2="some other string";
	int s2i=ei.getStringIndex(s2);
	int s2ii=ei.getStringIndex(s2);
	t.compare(s2ii,s2i);

	t.startTest("Add a string with existing UTF twice");
	int s3i=ei.getStringIndex(s1);
	int s3ii=ei.getStringIndex(s1);
	t.compare(s3ii,s3i);
	
	t.startTest("Add a class twice.");
	int ci=ei.getClassIndex(ei.getClass());
	int cii=ei.getClassIndex(ei.getClass());
	t.compare(cii,ci);
	
	t.startTest("Add a method twice.");
	Class[] params=new Class[1];
	java.lang.reflect.Method a_method;
	try {
	  params[0]=Class.forName("gnu.jel.debug.Tester");
	  a_method=ei.getClass().getMethod("test",params);
	  int mi=ei.getMethodIndex(a_method);
	  int mii=ei.getMethodIndex(a_method);
	  t.compare(mii,mi);
	} catch (Exception e) {
	  t.testFail();
	  Debug.reportThrowable(e);
	};

	t.startTest("Counting added entries ");
	t.compare(ei.poolEntries,14+2+29+1);
        // 14(entries added), 2(longs/doubles), 29(in cp_start), 1(zeroth) 

	t.startTest("Loading and executing via getExpression");
	try {
	  boolean ok=true;
	  ei.asm_load_object(null);
	  ei.asm_return();
	  CompiledExpression cell=ei.getExpression();
	  ok = ok && (cell.evaluate(null)==null);
	  if (!ok) dumpImage(ei);
	  if (ok) t.testOK(); else t.testFail();
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};

	t.startTest("Check transmission of Object constants");
	try {
	  ExpressionImage e=new ExpressionImage();
	  e.asm_load_object(e);
	  e.asm_return();
	  CompiledExpression cell=e.getExpression();
	  boolean ok = (cell.evaluate(null)==e);
	  if (!ok) dumpImage(e);
	  if (ok) t.testOK(); else t.testFail();
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};

	t.startTest("Check load/return of primitives (Z,B,C,S,I,J,F,D)");
	try {
 if      (!transmitPrimitive(new Long(1L),new Long(1L))) t.testFail();
 else if (!transmitPrimitive(new Double(1.0d),new Double(1.0d))) t.testFail();
 else if (!transmitPrimitive(new Integer(1),new Long(1L))) t.testFail();
 else if (!transmitPrimitive(new Byte((byte)1),new Long(1L))) t.testFail();
 else if (!transmitPrimitive(new Character(' '),new Long(32L))) t.testFail();
 else if (!transmitPrimitive(new Short((short)-3),new Long(-3L))) t.testFail();
 else if (!transmitPrimitive(new Boolean(true),new Long(1))) t.testFail();
 else if (!transmitPrimitive(new Float(1.0f),new Double(1.0d))) t.testFail();
 else t.testOK();
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};
	
	
	t.startTest("Evaluating 2L*2L=4L");
	try {
	  ExpressionImage e=new ExpressionImage();
	  e.asm_load_primitive(new Long(2L));
	  e.asm_load_primitive(new Long(2L));
	  e.asm_binary(BI_MU);
	  e.asm_return();
	  CompiledExpression cell=e.getExpression();
	  if (((Long)cell.evaluate(null)).longValue()==4) 
	    t.testOK();
	  else
	    t.testFail();
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};

	t.startTest("Evaluating 2D*2D=4D");
	try {
	  ExpressionImage e=new ExpressionImage();
	  e.asm_load_primitive(new Double(2D));
	  e.asm_load_primitive(new Double(2D));
	  e.asm_binary(BI_MU);
	  e.asm_return();
	  CompiledExpression cell=e.getExpression();
	  if (((Double)cell.evaluate(null)).doubleValue()==4.0) 
	    t.testOK();
	  else
	    t.testFail();
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};

	CompiledExpression cell_store=null;
	
	t.startTest("Evaluating Math.sin(2L+2L-4L+Math.round(1.2F))="+
		    "Math.sin(1.0)");
	try {
	  ExpressionImage e=new ExpressionImage();
	  Class math=Class.forName("java.lang.Math");
	  Class[] prs=new Class[1];
	  prs[0]=Double.TYPE;
	  java.lang.reflect.Method sinus=math.getDeclaredMethod("sin",prs);
	  prs[0]=Float.TYPE;
	  java.lang.reflect.Method round=math.getDeclaredMethod("round",prs);

	  e.asm_func_start(sinus,0);
	  e.asm_load_primitive(new Long(2L));
	  e.asm_load_primitive(new Long(2L));
	  e.asm_binary(BI_PL);
	  e.asm_load_primitive(new Long(4L));
	  e.asm_binary(BI_MI);
	  e.asm_func_start(round,0);
	  e.asm_load_primitive(new Double(1.2D));
	  e.asm_func_param();
	  e.asm_func_call();
	  e.asm_convert(Long.TYPE);
	  e.asm_binary(BI_PL);
	  e.asm_func_param();
	  e.asm_func_call();
	  e.asm_return();
	  CompiledExpression cell=e.getExpression();
	  cell_store=cell;  // save for next test
	  double result=((Double)cell.evaluate(null)).doubleValue();
	  //	  System.out.println(result);
	  if (Math.abs(result-Math.sin(1.0))<0.001) 
	    t.testOK();
	  else
	    t.testFail();
	 
	} catch (java.lang.Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};
	
	t.startTest("Evaluating last expression 100000 times");
	try {
	  for(int i=0;i<100000;i++) cell_store.evaluate(null);
	  
	  cell_store=null; // free memory
	  
	  t.testOK();
	} catch (Throwable e) {
	  Debug.reportThrowable(e);
	  t.testFail();
	};
	  

      };
    };
  };
};


// --------------------- ExpressionLoader ----------------------------
class ExpressionLoader extends ClassLoader {
  String name;
  byte[] bytes;
  Class c;
  ClassLoader parent;
  
  ExpressionLoader(String name, byte[] bytes) {
    this.name=name;
    this.bytes=bytes;
    this.c=null;
    this.parent=this.getClass().getClassLoader();
  };
  
  protected synchronized Class loadClass(String name, boolean resolve) throws
  java.lang.ClassNotFoundException
  {
    //   Debug.println("Asked to load "+name+" ("+resolve+")");
    if (!name.equals(this.name)) {
      if (parent!=null) 
	return parent.loadClass(name);
      else
	return findSystemClass(name);
    } else {
      //      Debug.println("Defining "+this.name);
      if(c==null) {
	c = defineClass(name,bytes, 0, bytes.length);    
	if (resolve) resolveClass(c);
      }
      return c;
    };
  };
  
};


