/**
 * @file s3/core/domain/S3ConstantsBuilder.java
 **/
package s3.core.domain;

import ovm.core.OVMBase;
import ovm.core.domain.ConstantResolvedInterfaceMethodref;
import ovm.core.domain.Type;
import ovm.core.repository.Binder;
import ovm.core.repository.ConstantClass;
import ovm.core.repository.ConstantFieldref;
import ovm.core.repository.ConstantMethodref;
import ovm.core.repository.ConstantPool;
import ovm.core.repository.Constants;
import ovm.core.repository.ConstantsEditor;
import ovm.core.repository.RepositoryString;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.repository.UnboundSelector;
import ovm.services.bytecode.JVMConstants;
import ovm.util.HTObject2int;

/**
 * Implementation of a constant pool editor, which creates the linked
 * per-Domain S3Constants representation of a ConstantPool.
 * S3Constants may contain resolved Strings, SharedStates (as Oops or
 * unresolved as TypeName.Gemeinsam) and Binders.
 *
 * @author Christian Grothoff
 * @see ovm.core.repository.RepositoryBuilder
 **/
public class S3ConstantsBuilder extends S3Constants
    implements ConstantsEditor, JVMConstants {

//     /**
//      * For every entry in the constant pool, we keep its tag.  Tags are
//      * constants which indicate the type of constant pool entry that
//      * follows. See JVM Spec 4.4.
//      **/
//     private byte[] tags_;

//     /**
//      * For every entry in the Constant Pool, the result of parsing is
//      * an object stored in this array.
//      **/
//     private Object[] constants;

//     private Type.Compound type_;

    int nextFree = 1;

    /**
     * Create a new ConstantPoolBuilder. 
     **/
    public S3ConstantsBuilder(Type.Compound type) {
	super(type, new byte[1], new Object[1]);
    }

    /**
     * Create a new ConstantPoolBuilder by extending a base constant pool. 
     * @param base the basic constant pool that should be extended with
     *        more constants
     **/
    public S3ConstantsBuilder(Type.Compound type,
			      ConstantPool base) {
	super(type, base.getTags(), base.getValues());
    }

    /**
     * Create a new ConstantPoolBuilder by extending a base constant pool. 
     * @param base the basic constant pool that should be extended with
     *        more constants
     **/
    public S3ConstantsBuilder(Type.Compound type,
			      S3Constants base) {
	super(type, base.getTags(), base.getValues());
    }

    /**
     * Commit all changes made and return the completed constant pool.
     * @return the offset of the new entry
     **/
    public S3Constants build() {
        return this;
    }
    
    /**
     * Throw away existing contents and realloc for size <code>n</code>.
     * @param n the new size of the constant pool in number of
     * constants.
     *
     * FIXME: Why is this public?  Hell, why is it even here?
     **/
    public void realloc(int n) {
	tags_ = new byte[n];
	constants = new Object[n];
    }
    
    /**
     * Clears a builder.
     **/
    public void reset() {
	realloc(1); // constant 0 is reserved!
    }

    /**
     * Build the corresponding constants object.
     * FIXME: Why is this here?
     */
    public Constants unrefinedBuild() {
	return build();
    }
    
    /* **************** build methods with auto-index allocation ********* */

    public int addResolvedConstant(Object o) {
	int n = testPresent(o, CONSTANT_Reference);
	if (-1 != n)
	    return n;
	return set(allocateIndex(), CONSTANT_Reference, o);
    }

    /**
     * Add a Binder constant.
     * This is an OVM speciality and does not correspond to anything in the
     * JVM Spec.
     * @param rb the bunder to be added
     **/
    public int addUnresolvedBinder(Binder rb) {
	int n = testPresent(rb, CONSTANT_Binder);
	if (-1 != n)
	    return n;
	return set(allocateIndex(), CONSTANT_Binder, rb);
    }

    /**
     * Add a double to the constant pool. (JVM Spec 4.4.5)
     * @param value the value to be added to the constant pool
     * @return the offset of the new entry	 
     **/
    public int addConstantDouble(double value) {
	Double d = new Double(value);
	int n = testPresent(d, CONSTANT_Double);
	if (-1 != n)
	    return n;
	n = set(allocateIndex(), CONSTANT_Double, d);
	allocateIndex();
	return n;
    }

    /**
     * Add a float to the constant pool. (JVM Spec 4.4.4)
     * @param value the value to be added to the constant pool
     * @return the offset of the new entry	 
     **/
    public int addConstantFloat(float value) {
	Float f = new Float(value);
	int n = testPresent(f, CONSTANT_Float);
	if (-1 != n)
	    return n;
	return set(allocateIndex(), CONSTANT_Float, f);
    }
    
    /**
     * Add an int to the constant pool. (JVM Spec 4.4.4)
     * @param value the value to be added to the constant pool     
     * @return the offset of the new entry	 
     **/
    public int addConstantInt(int value) {
	Integer val = new Integer(value);
	int n = testPresent(val, CONSTANT_Integer);
	if (-1 != n)
	    return n;
	return set(allocateIndex(), CONSTANT_Integer, val);
    }
    
    /**
     * Add a long to the constant pool. (JVM Spec 4.4.5)
     * @param value the value to be added to the constant pool
     * @return the offset of the new entry	 
     **/
    public int addConstantLong(long value) {
	Long l = new Long(value);
	int n = testPresent(l, CONSTANT_Long);
	if (-1 != n)
	    return n;
	n = set(allocateIndex(), CONSTANT_Long, l);
	allocateIndex(); // allocate 2nd index (wide!)
	return n;
    }

    /**
     * Add a bound field selector to the constant pool.
     * See JVM Spec 4.4.2
     * @param fieldsel the selector to be added to the constant pool
     * @return the offset of the new entry
     **/
    public int addFieldref(ConstantFieldref fieldsel) {
	int n = testPresent(fieldsel, fieldsel.getTag());
	if (-1 != n)
	    return n;
	return set(allocateIndex(), fieldsel.getTag(), fieldsel);
    }
    
    /**
     * Add an interface method selector to the constant pool.
     * See JVM Spec 4.4.2
     * @param methodsel the selector to be added to the constant pool
     * @return the offset of the new entry
     **/
    public int addInterfaceMethodref(ConstantMethodref methodsel) {
	// This wierd code is due to the fact that a Selector.Method
	// can't tell whether it is an interface method or not and
	// hence it always gives CONSTANT_Methodref. -HY
	byte tag;
	if (methodsel instanceof Selector.Method)
	    tag = CONSTANT_InterfaceMethodref;
	else if (methodsel 
		 instanceof ConstantResolvedInterfaceMethodref)
	    tag = CONSTANT_ResolvedInterfaceMethod;
	else
	    throw new Error("unexpected type : " + methodsel);
	int n = testPresent(methodsel, tag);
	if (-1 != n)
	    return n;
	return set(allocateIndex(), tag, methodsel);
    }

    /**
     * Add a method selector to the constant pool.	 
     * See JVM Spec 4.4.2
     * @param methodsel the selector to be added to the constant pool
     * @return the offset of the new entry
     **/
    public int addMethodref(ConstantMethodref methodsel) {
	int n = testPresent(methodsel, methodsel.getTag());
	if (-1 != n)
	    return n;
	return set(allocateIndex(), methodsel.getTag(), methodsel);
    }
    
    /**
     * Add a symbolic shared state constant.
     * This is an OVM speciality and does not correspond to anything in the
     * JVM Spec.
     * @param rss the symbolic shared state constant to be added to the constant pool
     * @return the offset of the new entry
     **/
    public int addUnresolvedSharedState(TypeName.Gemeinsam tn) {
        int n = testPresent(tn, CONSTANT_SharedState);
        if (-1 != n)
            return n;
	return set(allocateIndex(), CONSTANT_SharedState, tn);
    }

    /**
     * Add a string to the constant pool. (JVM Spec 4.4.3)
     * @param str the string to be added
     * @return the offset of the new entry	 
     **/
    public int addUnresolvedString(RepositoryString str) {
        int n = testPresent(str, CONSTANT_String);
        if (-1 != n)
            return n;
	return set(allocateIndex(), CONSTANT_String, str);
    }

    /**
     * Add a <code>TypeName</code> to the constant pool.
     * See JVM Spec 4.4.1.
     * @param name the TypeName object to be added
     * @return the offset of the new entry
     **/
    public int addClass(ConstantClass name) {
        if (name == null)
            throw new NullPointerException();
	// The same TypeName.Gemeinsam object can be stored in the
	// constant pool with different tags CONSTANT_Class or
	// CONSTANT_SharedState. -HY
        int n = testPresent(name, name.getTag());
        if (-1 != n)
            return n;
	return set(allocateIndex(), name.getTag(), name);
    }

    /**
     * Add an <code>UnboundSelector</code> to the constant pool.
     * See JVM Spec 4.4.6
     * @param usel the unbound selector to be added
     * @return the offset of the new entry
     *
     * FIXME: needed?
     **/
    public int addUnboundSelector(UnboundSelector usel) {
        int n = testPresent(usel, CONSTANT_NameAndType);
        if (-1 != n)
            return n;
	return set(allocateIndex(), CONSTANT_NameAndType, usel);
    }

    /**
     * Add a UTF8 string to the constant pool via its UTF8 index
     * See JVM Spec 4.4.7.
     * @param utf_index the index into the UTF8 table of the repository
     * @return the offset of the new entry
     *
     * FIXME: needed?
     **/
    public int addUtf8(int utf_index) {
        ConstantPool.UTF8Index utf = new ConstantPool.UTF8Index(utf_index);
        int n = testPresent(utf, CONSTANT_Utf8);
        if (-1 != n)
            return n;
	return set(allocateIndex(), CONSTANT_Utf8, utf);
    }

 
    /* ************** little convenience method ***************** */
    
    /**
     * Allocate a new index into the constant pool.  The pool may be
     * extremely sparse due to UTF8 elimination.
     *
     * @return the new index.
     **/
    private int allocateIndex() {
	int n = nextFree;
	while (n < tags_.length && tags_[n] != 0)
	    n++;
	nextFree = n + 1;
	reserveTagNumbers(n + 1);
	return n;
    }
   
    /**
     * Make sure there is room for n constants.
     * @param n the new number of constants supported (0...n-1)
     **/
    public void reserveTagNumbers(int n) {
	if (n <= tags_.length)
	    return; // got enough space
	byte[] oldTags = tags_;
	Object[] oldValues = constants;
	realloc(n);
	System.arraycopy(oldTags, 0, tags_, 0, oldTags.length);
	System.arraycopy(oldValues, 0, constants, 0, oldValues.length);
    }
     
    /**
     * Test if a constant equal to "o" with the expected tag is
     * already in the constant pool.  If yes, the respective index is
     * returned.
     * @param o the object whose presence is being tested 
     * @return -1 if o was not found
     */
    protected int testPresent(Object o, byte expectedTag) {
	switch (expectedTag) {
	case CONSTANT_Methodref:
	    for (int i = 1; i < constants.length; i++)
		if (o == constants[i] ||
		    ((tags_[i] == CONSTANT_ResolvedStaticMethod ||
		      tags_[i] == CONSTANT_ResolvedInstanceMethod) &&
		     ((ConstantMethodref) constants[i]).asSelector() == o))
		    return i;
	    return -1;
	case CONSTANT_Fieldref:
	    for (int i = 1; i < constants.length; i++)
		if (o == constants[i] ||
		    ((tags_[i] == CONSTANT_ResolvedStaticField ||
		      tags_[i] == CONSTANT_ResolvedInstanceField) &&
		     ((ConstantFieldref) constants[i]).asSelector() == o))
		    return i;
	    return -1;
	case CONSTANT_Class:
	    for (int i = 1; i < constants.length; i++)
		if (o == constants[i] ||
		    (tags_[i] == CONSTANT_ResolvedClass &&
		     ((S3Blueprint) constants[i]).getName() == o))
		    
		    return i;
	    return -1;
	default:
	    for (int i = 1; i < constants.length; i++)
		if (expectedTag == tags_[i]
		    && o.equals(constants[i]))
		    return i;
	    return -1;
	}
    }

    static class Caching extends S3ConstantsBuilder {
	HTObject2int entries = new HTObject2int();
	HTObject2int shStEntries = new HTObject2int();

	public Caching(Type.Compound t) { super(t); }
    
	protected int set(int index, int tag, Object value) {
	    if (constants[index] != null) {
		HTObject2int cache = (tags_[index] == CONSTANT_SharedState
				      ? shStEntries
				      : entries);
		cache.remove(constants[index]);
	    }
	    super.set(index, tag, value);
	    HTObject2int cache = (tags_[index] == CONSTANT_SharedState
				  ? shStEntries
				  : entries);
	    cache.put(value, tag);
            return index;
	}

	protected int testPresent(Object o, byte expectedTag) {
	    HTObject2int cache = (expectedTag == CONSTANT_SharedState
				  ? shStEntries
				  : entries);
	    int val = cache.get(o);
	    OVMBase.OVMassert(val == -1
			      || (tags_[val] == expectedTag
				  && o.equals(constants[val])));
	    return val;
	}
    }
} // end of S3ConstantsBuilder
