/*
 * ReflectObject.java --
 *
 *	Implements the Tcl internal representation of Java
 *	reflection object.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 *
 * SCCS: @(#) ReflectObject.java 1.37 97/12/11 17:18:19
 *
 */

package tcl.lang;

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

/*
 * A ReflectObject is used to create and access arbitrary Java objects
 * using the Java Reflection API. It wraps around a Java object (i.e.,
 * an instance of any Java class) and expose it to Tcl scripts. The
 * object is registered inside the interpreter and is given a string
 * name. Tcl scripts can manipulate this object as long as the the
 * reference count of the object is greater than zero.
 */

public class ReflectObject extends InternalRep implements CommandWithDispose {

/*
 * The java.lang.Object wrapped by the ReflectObject representation.
 */

Object javaObj;

/*
 * The interpreter in which the java.lang.Object is registered in.
 * ReflectObject's are not shared among interpreters for safety
 * reasons.
 */

Interp ownerInterp;

/*
 * The reference ID of this object.
 */

int refID;

/*
 * This variables records how many number of TclObject's are using
 * this ReflectObject internal rep. In this example:
 *
 *     set x [new java.lang.Integer 1]
 *     set y [format %s $x]
 *     java::info methods $y
 *
 * The two objects $x and $y share the same ReflectObject instance.
 * useCount is 2 when the java::info command has just translated the
 * string $y into a ReflectObject.
 *
 * useCount is initially 1. It will be more than 1 only when the
 * script tries to refer to the object using its string form, or when
 * the same object is returned by the Reflection API more than once.
 *
 * This variable is called useCount rather than refCount to avoid
 * confusion with TclObject.refCount.
 */

private int useCount;

/*
 * This variable marks whether the object is still considered "valid"
 * in Tcl scripts. An object is no longer valid if its object command
 * has been explicitly deleted from the interpreter.
 */

private boolean isValid;

/*
 * Stores the bindings of this ReflectObject. This member variable is used
 * in the BeanEventMgr class.
 */

Hashtable bindings;


/*
 *----------------------------------------------------------------------
 *
 * makeReflectObject --
 *
 *	Wraps an Java Object in a ReflectObject. If the same Java
 *	Object has already been wrapped in a ReflectObject, return
 *	that ReflectObject. Otherwise, create a new ReflectObject to
 *	wrap the Java Object.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The object is unregistered (and thus no longer accessible from
 *	Tcl scripts) if no other if no other TclObjects are
 *	still using this internal rep.
 *
 *----------------------------------------------------------------------
 */

private static ReflectObject
makeReflectObject(
    Interp interp,
    Object object)
{
    ReflectObject roRep = null;

    if (object != null) {
	roRep = (ReflectObject) interp.reflectObjTable.get(object);
    }

    if (roRep != null) {
	roRep.useCount ++;
	return roRep;

    } else {
	if ((object != null) && (object.getClass().isArray())) {
	    roRep = new ArrayObject();
	} else {
	    roRep = new ReflectObject();
	}
	roRep.ownerInterp = interp;
	roRep.javaObj = object;

	if (roRep.javaObj != null) {
	    /*
	     * Register the object in the interp.
	     */

	    interp.reflectObjCount ++;
	    roRep.refID = (int)interp.reflectObjCount;

	    interp.createCommand("java0x" + Integer.toHexString(roRep.refID),
		    roRep);
	    interp.reflectIDTable.put(new Integer(roRep.refID), roRep);
	    interp.reflectObjTable.put(object, roRep);
	} else {
	    /*
	     * The null object is not registered (hence it can't be deleted).
	     */

	    roRep.refID = 0;
	}

	roRep.useCount = 1;
	roRep.isValid = true;

	return roRep;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * dispose --
 *
 *	Called when a TclObject no longers uses this internal rep. We
 *	unregister the java.lang.Object if no other TclObjects are
 *	still using this internal rep.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The object is unregistered (and thus no longer accessible from
 *	Tcl scripts) if no other if no other TclObjects are
 *	still using this internal rep.
 *
 *----------------------------------------------------------------------
 */

protected void
dispose()
{
    -- useCount;
    if ((useCount == 0) && (refID != 0)) {
	/*
	 * No TclObject is using this internal rep anymore. Free it.
	 */

	ownerInterp.deleteCommand("java0x" + Integer.toHexString(refID));
	ownerInterp.reflectIDTable.remove(new Integer(refID));
	ownerInterp.reflectObjTable.remove(javaObj);

	ownerInterp = null;
	javaObj = null;
	refID = 0;
	bindings = null;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * duplicate --
 *
 *	Get a copy of this ReflectObject for copy-on-write
 *	operations. We just increment its useCount and return the same
 *	ReflectObject because ReflectObject's cannot be modified, so
 *	they don't need copy-on-write protections.
 *
 * Results:
 *	The same internal rep.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

protected InternalRep
duplicate()
{
    ++ useCount;
    return this;
}

/*
 *----------------------------------------------------------------------
 *
 * setReflectObjectFromAny --
 *
 *	Called to convert an TclObject's internal rep to ReflectObject.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When successful, the internal representation of tobj is
 *	changed to ReflectObject, if it is not already so.
 *
 *----------------------------------------------------------------------
 */

private static void
setReflectObjectFromAny(
    Interp interp,		// Current interpreter. Must be non-null.
    TclObject tobj)		// The TclObject to convert.
throws
    TclException		// If the object's internal rep is not
				// already ReflectObject, and the string rep
				// is not the name of a java.lang.Object
				// registered in the given interpreter.
				// Error message is left inside interp.
{
    InternalRep rep = tobj.getInternalRep();
    ReflectObject roRep;

    if (rep instanceof ReflectObject) {
	roRep = (ReflectObject)rep;
	if (roRep.isValid && (roRep.ownerInterp == interp)) {
	    return;
	}
    }

    try {
	String s = tobj.toString();
	if (s.startsWith("java")) {
	    int id = Util.getInt(interp, s.substring(4));

	    if (id == 0) {
		tobj.setInternalRep(makeReflectObject(interp,
			null));
		return;
	    } else {
		roRep = (ReflectObject) interp.reflectIDTable.get(
		    new Integer(id));
		if ((roRep != null) && (roRep.isValid)) {
		    roRep.useCount ++;
		    tobj.setInternalRep(roRep);
		    return;
		}
	    }
	}
    } catch (TclException e) {
	/*
	 * tobj.toString() is not a valid object ID. We'll report this
	 * error below.
	 */

    }

    throw new TclException(interp, "unknown java object \"" + 
	    tobj + "\"");
}

/*
 *----------------------------------------------------------------------
 *
 * newInstance --
 *
 *	Creates a new instance of a TclObject that wraps a
 *	java.lang.Object.
 *
 * Results:
 *	The newly created TclObject.
 *
 * Side effects:
 *	The java.lang.Object will be registered in the interpreter.
 *
 *----------------------------------------------------------------------
 */

public static TclObject
newInstance(
    Interp interp,		// Current interpreter.
    Object obj)			// java.lang.Object to wrap.
{
    return new TclObject(makeReflectObject(interp, obj));
}

/*
 *----------------------------------------------------------------------
 *
 * get --
 *
 *	Returns a java.lang.Object represented by tobj. tobj must have a
 *	ReflectObject internal rep, or its string rep must be one of the
 *	currently registered objects.
 *
 * Results:
 *	The Java object represented by tobj.
 *
 * Side effects:
 *	When successful, the internal representation of tobj is
 *	changed to ReflectObject, if it is not already so.
 *
 *----------------------------------------------------------------------
 */

public static Object
get(
    Interp interp,		// Current interpreter. Must be non-null.
    TclObject tobj)		// The TclObject to query.
throws
    TclException		// If the internal rep of tobj cannot
				// be converted to a ReflectObject. 
				// Error message is left inside interp.
{
    setReflectObjectFromAny(interp, tobj);
    ReflectObject rep = (ReflectObject)(tobj.getInternalRep());
    return rep.javaObj;
}

/*
 *----------------------------------------------------------------------
 *
 * getReflectObject --
 *
 *	Returns the InternalRep of a the ReflectObject represented by
 *	tobj. Only the java:: commands should call this
 *	method. (java::bind, java::call, etc).
 *
 * Results:
 *	The Java object represented by tobj.
 *
 * Side effects:
 *	When successful, the internal representation of tobj is
 *	changed to ReflectObject, if it is not already so.
 *
 *----------------------------------------------------------------------
 */

static ReflectObject
getReflectObject(
    Interp interp,		// Current interpreter. Must be non-null
    TclObject tobj)		// The TclObject to query.
throws
    TclException		// If the internal rep of tobj cannot
				// be converted to a ReflectObject.
				// Error message is left inside interp.
{
    setReflectObjectFromAny(interp, tobj);
    return (ReflectObject)(tobj.getInternalRep());
}

/*
 *----------------------------------------------------------------------
 *
 * cmdProc --
 *
 *	This cmdProc implements the Tcl command used to invoke methods
 *	of the java.lang.Object stored in this ReflectObject internal
 *	rep. For example, this method is called to process the "$v"
 *	command at the second line of this script:
 *
 *	    set v [java::new java.util.Vector]
 *	    $v addElement "foo"
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the given method returns a value, it is converted into a
 *	TclObject and stored as the result of the interpreter.
 *
 *----------------------------------------------------------------------
 */

public void
cmdProc(
    Interp interp,			// Current interpreter.
    TclObject argv[])			// Argument list.
throws
    TclException			// Standard Tcl exception;
{
    boolean convert;
    int sigIdx;

    if (argv.length < 2) {
	throw new TclNumArgsException(interp, 1, argv, 
		"?-noconvert? signature ?arg arg ...?");
    }

    String arg1 = argv[1].toString();
    if ((arg1.length() >= 2) && ("-noconvert".startsWith(arg1))) {
	convert = false;
	sigIdx = 2;
    } else {
	convert = true;
	sigIdx = 1;
    }

    if (argv.length < sigIdx + 1) {
	throw new TclNumArgsException(interp, 1, argv, 
		"?-noconvert? signature ?arg arg ...?");
    }

    int startIdx = sigIdx + 1;
    int count = argv.length - startIdx;

    interp.setResult(JavaInvoke.callMethod(interp, argv[0],
	    argv[sigIdx], argv, startIdx, count, convert));
}

/*
 *----------------------------------------------------------------------
 *
 * disposeCmd --
 *
 * 	This method is called when the object command has been deleted
 * 	from an interpreter. It marks the ReflectObject no longer
 * 	accessible from Tcl scripts.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The ReflectObject is no longer accessible from Tcl scripts.
 *
 *----------------------------------------------------------------------
 */

public void
disposeCmd()
{
    isValid = false;
}

/*
 *----------------------------------------------------------------------
 *
 * toString --
 *
 *	Called to query the string representation of the Tcl
 *	object. This method is called only by TclObject.toString()
 *	when TclObject.stringRep is null.
 *
 * Results:
 * 	Returns the string representation of this ReflectObject.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

public String
toString()
{
    return "java0x" + Integer.toHexString(refID);
}

/*
 *----------------------------------------------------------------------
 *
 * init --
 *
 *	Initializes fields inside the Interp that are used by
 *	ReflectObject's.
 *
 * Results:
 * 	None.
 *
 * Side effects:
 *	The fields are initialized.
 *
 *----------------------------------------------------------------------
 */

static void
init(
    Interp interp)
{
    interp.reflectIDTable = new Hashtable();
    interp.reflectObjTable = new Hashtable();
    interp.reflectObjCount = 0;
}

} // end ReflectObject
