/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: TypesStack.java,v 1.1 2000/02/11 22:08:18 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 -- 2000 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.util.Stack;

/**
 * Organizes stack of types, supports type identification and
 * automatically calculates the maximum occupation. Efficiently 
 * supports primitive types identification.
 */
public class TypesStack implements Cloneable {

  // string buffer used temporarily for string concatenation
  protected static java.lang.Class tsb_class=null;

  // string class
  protected static java.lang.Class string_class=null;

  static {
    try {
      string_class=Class.forName("java.lang.String");
      tsb_class=Class.forName("gnu.jel.TempStringBuffer");
    } catch (ClassNotFoundException exc) {
      if (Debug.enabled)
        Debug.reportThrowable(exc);
    };
  };
  
  /**
   *  Classes of the primitive types by ID.
   */
  public final static Class[] 
    primitiveTypes={Boolean.TYPE, Byte.TYPE   , Character.TYPE,
                    Short.TYPE  , Integer.TYPE, Long.TYPE,
                    Float.TYPE  , Double.TYPE, null, Void.TYPE};

  /**
   * Java codes for primitive types.
   */
  public final static char[] primitiveCodes= {'Z','B','C','S',
                                              'I','J','F','D','L','V'};
  
  /**
   * Names of the primitive types by ID in readable form.
   */
  public final static String[] primitiveTypeNames = {
    "boolean","byte","char" ,"short" ,
    "int"    ,"long","float","double","reference","void"
  };


  /**
   * Identifies the primitive type of the given class.
   * @param c class to identify.
   * @return id of the corresponding primitive type.
   */
  public static final int primitiveID(Class c) {
    if ((c==null) || (!c.isPrimitive())) return 8;
    int i;
    for(i=0;(i<primitiveTypes.length) && (primitiveTypes[i]!=c);i++);
    if (Debug.enabled)
      Debug.assert(i<primitiveTypes.length,
                   "You didn't put _ALL_ primitive types"+
                   " into primitiveTypes array.");
    return i;
  };

  /**
   * Identify the primitive type corresponding to the given reflection object.
   * @param o object to identify.
   * @return id of the corresponding primitive type.
   */
  public static final int primitiveID(Object o) {
    if (o instanceof Boolean) return 0;
    if (o instanceof Byte) return 1;
    if (o instanceof Character) return 2;
    if (o instanceof Short) return 3;
    if (o instanceof Integer) return 4;
    if (o instanceof Long) return 5;
    if (o instanceof Float) return 6;
    if (o instanceof Double) return 7;
    return 8;
  };

  // Stack occupation by the given type by ID
  //                                       Z  B  C  S  I  J  F  D  REF VOID
  protected final static byte[] stkoccup={ 1, 1, 1, 1, 1, 2, 1, 2,  1,  0  };


  // holds ids of types if they are primitive
  private IntegerStack primitiveIDs;

  // holds the class objects for REF types (id=8)
  private Stack classes;

  // current number of words in this stack
  protected int currWords;

  // maximum number of words in this stack
  private int maxWords;

  /**
   * Constructs a new empty TypesStack.
   */
  public TypesStack() {
    primitiveIDs=new IntegerStack();
    classes=new Stack();
    currWords=0;
    maxWords=0;
  };

  /**
   * Makes a clone of this object.
   * @return a clone of this object
   */
  public Object clone() {
    TypesStack res=null;
    try {
      res=(TypesStack)super.clone();
      res.primitiveIDs=(IntegerStack)res.primitiveIDs.clone();
      res.classes=(Stack)res.classes.clone();
    } catch (CloneNotSupportedException exc) {
      if (Debug.enabled)
        Debug.reportThrowable(exc);
    };
    return res;
  };

  /**
   * Peeks the class on top of the stack without removing it.
   * @return class on top of the stack.
   */
  public final Class peek() {
    return (Class)classes.peek();
  };

  /**
   * Peeks the ID of the class on top of the stack without removing it.
   * @return ID of the class on top of the stack.
   */
  public final int peekID() {
    return primitiveIDs.peek();
  };

  /**
   * Peeks the class from the body of the stack.
   * @param i number of the class to peek (0 means the top of the stack)
   * @return class number i from the top of the stack.
   */
  public final Class peek(int i) {
    return (Class)classes.elementAt(classes.size()-1-i);
  };

  /**
   * Peeks the ID of the class from the body of the stack.
   * @param i number of the class to peek (0-peek)
   * @return ID of the class number i from the top of the stack.
   */
  public final int peekID(int i) {
    return primitiveIDs.peek(i);
  };


  /**
   * Pops the top class from the stack.
   * @return class formerly on top of the stack.
   */
  public final Class pop() {
    int id=primitiveIDs.pop();
    currWords-=stkoccup[id];
    
    if (Debug.enabled)
      Debug.assert(currWords>=0);

    return (Class)classes.pop();
  };

  /**
   * Pushes the class representing the primitive type into stack.
   */
  public final void pushID(int id,Class c) {
    primitiveIDs.push(id);
    if (id==8) classes.push(c); else classes.push(primitiveTypes[id]);
    currWords+=stkoccup[id];
    if (currWords>maxWords) maxWords=currWords;
  };
  
  /**
   * Pushes the class representing the primitive type into stack.
   */
  public final void pushID(int id) {
    if (Debug.enabled) 
      Debug.assert(id!=8);
    pushID(id,null);
  };

  /**
   * Pushes a given class into stack.
   */
  public final void push(Class c) {
    pushID(primitiveID(c),c);
  };

  /**
   * Adds a new element to the stack at a given position from top of it.
   * @param cls class to add
   * @param i position of new element from the top of the stack (0 -- push)
   */
  public final void push(Class cls,int i) {
    int id=primitiveID(cls);
    primitiveIDs.push(id,i);
    classes.insertElementAt(cls,classes.size()-i);
    currWords+=stkoccup[id];
    if (currWords>maxWords) maxWords=currWords;
  };

  /**
   * Adds a new element to the stack at a given position from top of it.
   * @param id ID of the class to add
   * @param i position of new element from the top of the stack (0 -- push)
   */
  public final void pushID(int id,int i) {
    Class cls=primitiveTypes[id];
    primitiveIDs.push(id ,i);
    classes.insertElementAt(cls,classes.size()-i);
    currWords+=stkoccup[id];
    if (currWords>maxWords) maxWords=currWords;
  };


  /**
   * Used to determine the number of elements in this stack.
   * @return the number of elements in this stack.
   */
  public final int size() {
    return primitiveIDs.size();
  };

  /**
   * Used to adjust maximum stack occupation by a given amount.
   * <P>This method is called when there were data pushed on top of
   * Java stack bypassing this class.
   */
  public final void resetStats() {
    if (Debug.enabled)
      Debug.assert(currWords==0);
    maxWords=0;
  };

  /**
   * Used to adjust maximum stack occupation by a given amount.
   * <P>This method is called when there were data pushed on top of
   * Java stack bypassing this class.
   */
  public final void tempExcessWords(int nw) {
    nw=currWords+nw;
    if (nw>maxWords) maxWords=nw;
  };

  /**
   * Used to get a maximum number of Java words needed to store the stack. 
   * @return number of words.
   */
  public final int getMaxOccupation() {
    return maxWords;
  };

  // Possible widening conversions
  private final static byte[] cvt_wide= {
    (byte)0x80,0x40,0x60,0x50,0x78,0x5C,0x5E,0x5F
  };

  /**
   * Used to find out if the conversion t1->t2 is widening.
   * @param t1 type to convert from
   * @param t2 type to convert to
   * @return true if the given conversion is widening
   */
  public static boolean isWidening(Class t1, Class t2) {
    boolean pt1=t1.isPrimitive();
    boolean pt2=t2.isPrimitive();
    
    if (pt2 && pt1) return isWidening(primitiveID(t1),primitiveID(t2));
    if (pt2 ^  pt1) return false; // Can't convert yet between PT and OBJ.
    // freely convert between string and tsb
    if (((t1==tsb_class) && (t2==string_class)) ||
        ((t2==tsb_class) && (t1==string_class))) return true;
    return t2.isAssignableFrom(t1);
  };

  /**
   * Used to find out if the conversion t1->t2 is widening.
   * @param it1 type ID to convert from
   * @param it2 type ID to convert to
   * @return true if the given conversion is widening
   */
  public static boolean isWidening(int it1, int it2) {
    if ((it1<8) && (it2<8))
      return (cvt_wide[it2] & (0x80 >> it1)) >0 ;
    return false;
  };

  /**
   * Used to find out if the given class represents a Java string.
   * @param c class to check
   * @return true if this class is a string
   */
  public static boolean isString(Class c) {
    return c==string_class;
  };

  /**
   * Used to find out if the given class represents a temporary sttring buffer.
   * @param c class to check
   * @return true if this class is a temporary string buffer.
   */
  public static boolean isTSB(Class c) {
    return c==tsb_class;
  };

  protected static Number widen(Object o, int clsID) {
    switch (clsID) {
    case 0: // Z
      if (((Boolean)o).booleanValue()) 
        return new Long(1L); 
      else 
        return new Long(0L);
    case 1: // B
      return (Number)o;
    case 2: // C
      return new Long((long)((Character)o).charValue());
    case 3: // S
    case 4: // I
    case 5: // J
    case 6: // F
    case 7: // D
      return (Number)o;
    default:
      if (Debug.enabled)
        Debug.println("Attempt to widen wrong primitive ("+clsID+").");
      return new Long(0L);
    };
  };

  protected static Object narrow(Number val, int clsID) {
    switch (clsID) {
    case 0: // Z
      if (val.longValue()!=0) return Boolean.TRUE; else return Boolean.FALSE;
    case 1: // B
      return new Byte(val.byteValue());
    case 2: // C
      return new Character((char)val.longValue());
    case 3: // S
      return new Short(val.shortValue());
    case 4: // I
      return new Integer(val.intValue());
    case 5: // J
      return new Long(val.longValue());
    case 6:
      return new Float(val.floatValue());
    case 7:
      return new Double(val.doubleValue());
    default:
      if (Debug.enabled)
        Debug.println("Attempt to narrow wrong primitive ("+clsID+").");
      return null;
    };
  };

  
  //================================================================
  //======================== UNITARY TESTS =========================
  //================================================================

  /**
   * Performs unitary test of this class.
   * @param args ignored.
   */
  public static void main(String[] args) {
    if (Debug.enabled) {
      Tester t=new Tester(System.out);
      test(t);
      t.summarize();
    };
  };

  /**
   * Performs unitary test of this class.
   * <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) {
      TypesStack ts=null;
      try {
        t.startTest("ID(Boolean.TYPE)==ID(new Boolean(true))");
        t.compare(primitiveID(Boolean.TYPE),primitiveID(new Boolean(true)));
        t.startTest("ID(Byte.TYPE)==ID(new Byte(0))");
        t.compare(primitiveID(Byte.TYPE),primitiveID(new Byte((byte)0)));
        t.startTest("ID(\"string\".getClass())==8");
        t.compare(primitiveID(("string").getClass()),8);
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };
      
      try {
        t.startTest("Make a new types stack");
        ts=new TypesStack();
        t.testOK();
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("push(Integer.TYPE);pushID(4); pop()==pop()");
        ts.push(Integer.TYPE);
        ts.pushID(4);
        t.compare(ts.pop(),ts.pop());
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("push(Integer.TYPE,0);pushID(4,0); pop()==pop()");
        ts.push(Integer.TYPE,0);
        ts.pushID(4,0);
        t.compare(ts.pop(),ts.pop());
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("push(Long.TYPE);pushID(6);pushID(4,1);"+
                    "peekID()==peekID(1)+2");
        ts.push(Long.TYPE);
        ts.pushID(6);
        ts.pushID(4,1);
        t.compare(ts.peekID(),ts.peekID(1)+2);
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("pop();peekID()==peekID(1)-1");
        ts.pop();
        t.compare(ts.peekID(),ts.peekID(1)-1);
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("getMaxOccupation()==4");
        t.compare(ts.getMaxOccupation(),4);
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };

      try {
        t.startTest("pop();pop(); size()==0");
        ts.pop();
        ts.pop();
        t.compare(ts.size(),0);
      } catch (Throwable exc) {
        Debug.reportThrowable(exc);
        t.testFail();
      };


    };
  };

};

// This is to denote StringBuffer used by string concatenation operation
class TempStringBuffer {
};
