/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: TypesStack.java,v 1.5 2000/11/12 14:59:34 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 {

  /**
   *  Classes of the special types by ID.
   *  <P> The frequently used types (those on which many operations are
   *  defined) are identified by an integer number. The advantage is 
   *  the possibility to have almost completely table driven code generator.
   *  <P> So, the special types are only special in the fact that except
   *  of the reference to their class object they are also identified by an
   *  integer number.
   */
  public final static Class[] specialTypes; // defined in gnu.jel.TablesKeeper

  /**
   * Translates the type ID into the Java base type ID.
   * <P>That is all special types which are references are translated into 8.
   */
  public final static byte[] baseType;

  /**
   * Translates the type ID into the Java base type ID.
   * <P>That is all special types which are references are translated into 8.
   */
  public final static byte[] unwrapType;

  /**
   * Java codes for base types by base ID.
   */
  public final static char[] primitiveCodes;
  
  static {
    specialTypes=(Class[])TableKeeper.getTable("specialTypes");
    baseType=(byte[]) TableKeeper.getTable("baseType");
    unwrapType=(byte[]) TableKeeper.getTable("unwrapType");
    primitiveCodes=(char[]) TableKeeper.getTable("primitiveCodes");
  };

  /**
   * Identifies the primitive type of the given class.
   * @param c class to identify.
   * @return id of the corresponding primitive type.
   */
  public static final int typeID(Class c) {

    final int NUM_SPECIAL_PRIMITIVE_TYPES=10;
    // ^^^ it is the number of primitive types out of special ones

    if (c==null) return 8;
    if (c.isPrimitive()) {
      int i;
      for(i=0;(i<NUM_SPECIAL_PRIMITIVE_TYPES) && (specialTypes[i]!=c);i++);
      if (Debug.enabled)
        Debug.assert(i<NUM_SPECIAL_PRIMITIVE_TYPES,
                     "You didn't put _ALL_ primitive types"+
                     " into primitiveTypes array.");
      return i;
    };

    int i;
    for(i=NUM_SPECIAL_PRIMITIVE_TYPES+1; // TSB is excluded
        (i<specialTypes.length) && (!specialTypes[i].isAssignableFrom(c));
        i++);
    
    if (i<specialTypes.length) return i; 
    return 8; // just a generic reference
  };


  /**
   * 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 typeIDObject(Object o) {
    if (o instanceof java.lang.Boolean) return 0;
    if (o instanceof java.lang.Byte) return 1;
    if (o instanceof java.lang.Character) return 2;
    if (o instanceof java.lang.Short) return 3;
    if (o instanceof java.lang.Integer) return 4;
    if (o instanceof java.lang.Long) return 5;
    if (o instanceof java.lang.Float) return 6;
    if (o instanceof java.lang.Double) return 7;
    if (o instanceof java.lang.String) return 11;
    return 8;
  };

  // Stack occupation in words by the given base type by ID
  //                                       Z  B  C  S  I  J  F  D  REF VOID
  protected final static byte[] stkoccup;

  static {
    stkoccup=(byte[])TableKeeper.getTable("stkoccup");
  };

  // holds ids of types if they are special
  private IntegerStack typeIDs;

  // holds the class objects for non special 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() {
    typeIDs=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.typeIDs=(IntegerStack)res.typeIDs.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 typeIDs.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 typeIDs.peek(i);
  };


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

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

  /**
   * Pushes the class with given typeID into stack.
   */
  public final void pushID(int id,Class c) {
    Debug.assert(! ((id==8) && (c==null)));
    typeIDs.push(id);
    if (c!=null) 
      classes.push(c); 
    else 
      classes.push(specialTypes[id]);
    currWords+=stkoccup[baseType[id]];
    if (currWords>maxWords) maxWords=currWords;
  };
  
  /**
   * Pushes the class representing the special 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(typeID(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=typeID(cls);
    typeIDs.push(id,i);
    classes.insertElementAt(cls,classes.size()-i);
    currWords+=stkoccup[baseType[id]];
    if (currWords>maxWords) maxWords=currWords;
  };

  /**
   * Adds a special type 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=specialTypes[id];
    typeIDs.push(id ,i);
    classes.insertElementAt(cls,classes.size()-i);
    currWords+=stkoccup[baseType[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 typeIDs.size();
  };

  /**
   * Resets maximum stack occupation statistics.
   * <P>Should be called when this stack is reused for another method.
   */
  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 bitmap. 
  private final static int[] cvt_wide;
  static {
    cvt_wide=(int[])TableKeeper.getTable("cvt_wide");
  };

  /**
   * Used to find out if the conversion t1->t2 is widening.
   * @param id1 type ID to convert from
   * @param c1 class to convert from (used if id1==8)
   * @param id2 type ID to convert to
   * @param c2 class to convert to (used if id2==8)
   * @return true if the given conversion is widening (can be done
   *         automatically)
   */
  public static boolean isWidening(int id1, Class c1, int id2, Class c2) {
    id1=unwrapType[id1];
    id2=unwrapType[id2];

    if (Debug.enabled)
      Debug.assert(id1<cvt_wide.length);

    if ((id1!=8) && (id2!=8)) return (cvt_wide[id2] & (0x800 >> id1)) >0;

    // check references cast
    return c2.isAssignableFrom(c1);
  };

  public static boolean isIntegral(int id1) {
    final int itypes=0x3E;
      //      7 6 5 4 3 2 1 0
      //      0 0 1 1 1 1 1 0 = 0x3E
    return ((itypes>>id1) & 1)>0;
  };

  /**
   * Used to find out if the conversion t1->t2 is widening.
   * @param c1 class to convert from (used if id1==8)
   * @param c2 class to convert to (used if id2==8)
   * @return true if the given conversion is widening (can be done
   *         automatically)
   */
  public static boolean isWidening(Class c1, Class c2) {
    return isWidening(typeID(c1),c1,typeID(c2),c2);
  };

  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(typeID(Boolean.TYPE),typeIDObject(new Boolean(true)));
        t.startTest("ID(Byte.TYPE)==ID(new Byte(0))");
        t.compare(typeID(Byte.TYPE),typeIDObject(new Byte((byte)0)));
        t.startTest("ID(\"string\".getClass())==11");
        t.compare(typeID(("string").getClass()),11);
        t.startTest("ID((new Object()).getClass())==8");
        t.compare(typeID((new Object()).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();
      };


    };
  };

};
