/*
 * BeanEventMgr.java --
 *
 *	The Bean Event Manager: This class manages beans event
 *	handlers for a Tcl interpreter.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: BeanEventMgr.java,v 1.1.1.1 1998/10/14 21:09:14 cvsadmin Exp $
 *
 */

package tcl.lang;

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

/*
 * This class manages beans event handlers for a Tcl interpreter:
 * the event object stack, etc.
 */

class BeanEventMgr implements AssocData {

/*
 * Stores all of the available event adaptor classes.
 */

private static Hashtable adaptorClsTab = new Hashtable();

/*
 * The class loader for loading automatically generated event adaptor
 * classes.
 */

private static AdaptorClassLoader adaptorLoader = new AdaptorClassLoader();

/*
 * When a event handler is invoked, it is given a set of parameters
 * (stored in an Object array.) The eventParamSetStack variable is
 * used to store the parameter sets in a LIFO order when nested event
 * handlers are invoked. Event parameters can be queried by the
 * "java::event" command.
 */

Stack eventParamSetStack;


/*
 *----------------------------------------------------------------------
 *
 * BeanEventMgr --
 *
 *	Creates a new BeanEventMgr instance.
 *
 * Side effects:
 *	Member fields are initialized.
 *
 *----------------------------------------------------------------------
 */

private
BeanEventMgr()
{
    eventParamSetStack = new Stack();
}

/*
 *----------------------------------------------------------------------
 *
 * getBeanEventMgr --
 *
 *	Returns the BeanEventMgr instance for the given interp. A new
 *	BeanEventMgr is created if no such BeanEventMgr exists for
 *	the interp.
 *
 * Results:
 *	The BeanEventMgr instance for the given interp.
 *
 * Side effects:
 *	A new BeanEventMgr may be created and registered as an
 *	AssocData in the given interp.
 *
 *----------------------------------------------------------------------
 */

static BeanEventMgr
getBeanEventMgr(
    Interp interp)		// Query the BeanEventMgr of this interp.
{
    BeanEventMgr mgr = (BeanEventMgr)interp.getAssocData("tclBeanEvent");
    if (mgr == null) {
	mgr = new BeanEventMgr();
	interp.setAssocData("tclBeanEvent", mgr);
    }

    return mgr;
}

/*
 *----------------------------------------------------------------------
 *
 * pushEventParamSet --
 *
 *	Pushes a set of event parameters to the top of the stack.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The set of parameters are pushed to the top of the stack.
 *
 *----------------------------------------------------------------------
 */

void
pushEventParamSet(
    BeanEventParamSet p)	// The parameters to push to the top of the
				// stack.
{
    eventParamSetStack.push(p);
}

/*
 *----------------------------------------------------------------------
 *
 * popEventParamSet --
 *
 *	Pops the set of event parameters from the top of the stack.
 *
 * Results:
 *	None.
 *	
 * Side effects:
 *	The size of the event parameter set stack is reduced by one.
 *
 *----------------------------------------------------------------------
 */

void
popEventParamSet()
throws
     EmptyStackException	// If the stack is already empty.
{
    eventParamSetStack.pop();
}

/*
 *----------------------------------------------------------------------
 *
 * peekEventParamSet --
 *
 *	Returns the set	of event parameters at the top of the stack.
 *
 * Results:
 *	If the event parameter stack is not empty, returns the set of
 *	parameters at the top of the stack. Otherwise, returns null.
 *	
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

BeanEventParamSet
peekEventParamSet()
{
    if (eventParamSetStack.size() == 0) {
	return null;
    } else {
	return (BeanEventParamSet)eventParamSetStack.peek();
    }
}

/*
 *----------------------------------------------------------------------
 *
 * disposeAssocData --
 *
 *	This method is called when the interpreter is destroyed or
 *	when Interp.deleteAssocData is called on a registered
 *	AssocData instance.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Removes any bgerror's that haven't been reported.
 *
 *----------------------------------------------------------------------
 */

public void
disposeAssocData(
    Interp interp)		// The interpreter in which this AssocData
				// instance is registered in.
{
    eventParamSetStack = null;
}

/*
 *----------------------------------------------------------------------
 *
 * setBinding --
 *
 *	Sets the Tcl command to be executed when the given event fires
 *	in the reflectObj. A event adaptor is created when necessary.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the reflectObj doesn't yet have an EventAdaptor to handle
 *	the event, we will attempt to create it. This may cause an
 *	appropriate event adaptor class to be generated and loaded
 *	into the JVM; the EventAdaptor will be instantiated and
 *	registered as a listener of the given EventSet on the javaObj.
 *
 *----------------------------------------------------------------------
 */

void
setBinding(
    Interp interp,			// Current interpreter.
    ReflectObject reflectObj,		// The reflection object to create
					// event binding for.
    EventSetDescriptor eventSet,	// The EventSet to bind to.
    Method event,			// Identifies a specific event in
					// the EventSet to create a
					// binding for.
    TclObject command)			// The command to execute when the
					// given event fires.
throws
    TclException			// If the adaptor class cannot be
					// generated, or if the adaptor
					// cannot be instantiated.
{
    EventAdaptor adaptor = null;

    if (reflectObj.bindings == null) {
	reflectObj.bindings = new Hashtable();
    } else {
	adaptor = (EventAdaptor)reflectObj.bindings.get(eventSet);
    }

    if (adaptor == null) {
	Class lsnType = eventSet.getListenerType();
	Class adaptorCls = (Class)adaptorClsTab.get(lsnType);

	if (adaptorCls == null) {
	    /*
	     * We have never processed this type of EventSet yet. Generate
	     * an appropriate event adaptor class and load it
	     * into the JVM.
	     */

	    adaptorCls = adaptorLoader.loadEventAdaptor(interp, eventSet);
	    adaptorClsTab.put(lsnType, adaptorCls);
	}

	try {
	    adaptor = (EventAdaptor)adaptorCls.newInstance();
	} catch (InstantiationException e1) {
	    /*
	     * adaptor will remain null. This will trigger the
	     * exception later on.
	     */
	} catch (IllegalAccessException e2) {
	    /*
	     * adaptor will remain null. This will trigger the
	     * exception later on.
	     */
	}

	if (adaptor == null) {
	    throw new TclException(interp, 
		    "couldn't instantiate adaptor class for eventset \"" +
		    eventSet + "\"");
	}

	adaptor.init(interp, reflectObj.javaObj, eventSet);

	/*
	 * Save the adaptor -- we only need a single adaptor for each
	 * EventSet to handle all possible events in this set.
	 */

	reflectObj.bindings.put(eventSet, adaptor);
    }

    if (command.toString().length() > 0) {
	adaptor.setCallback(event.getName(), command);
    } else {
	/*
	 * The callback command is the empty string. This means remove
	 * any existing callback scripts. If no more callback scripts
	 * are registered in the adaptor, we'll remove it from the
	 * hashtable.
	 */

	if (adaptor.deleteCallback(event.getName()) == 0) {
	    reflectObj.bindings.remove(eventSet);
	    if (reflectObj.bindings.size() == 0) {
		reflectObj.bindings = null;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getBinding --
 *
 *	Queries the command to be executed when the given event fires
 *	in this object.
 *
 * Results:
 *	The command to execute when the event fires. null if no such
 *	command has be registered with the setBinding() method.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TclObject
getBinding(
    Interp interp,			// Current interpreter.
    ReflectObject reflectObj,		// The reflection object to query.
    EventSetDescriptor eventSet,	// The EventSet to bind to.
    Method event)			// Identifies a specific event in
					// the EventSet to query the
					// binding for.
{
    EventAdaptor adaptor = null;

    if (reflectObj.bindings != null) {
	adaptor = (EventAdaptor)reflectObj.bindings.get(eventSet);
    }

    if (adaptor == null) {
	return null;
    } else {
	return adaptor.getCallback(event.getName());
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getHandledEvents --
 *
 *	Queries all the events that are currently handled by for this
 *	object.
 *
 * Results:
 *	A Tcl list of the events that are currently handled by for this
 *	object. The list is a valid empty Tcl list if this object
 *	handles no event.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TclObject
getHandledEvents(
    ReflectObject reflectObj)		// The reflection object to query.
{
    TclObject list = TclList.newInstance();

    if (reflectObj.bindings != null) {
	for (Enumeration e = reflectObj.bindings.elements();
		e.hasMoreElements(); ) {
	    EventAdaptor adaptor = (EventAdaptor)e.nextElement();
	    adaptor.getHandledEvents(list);
	}
    }

    return list;
}

} // end BeanEventMgr

