/*
 * EventAdaptor.java --
 *
 *	Base class for event adaptors classes generated by Tcl.
 *
 * 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: EventAdaptor.java,v 1.1.1.1 1998/10/14 21:09:14 cvsadmin Exp $
 */

package tcl.lang;

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

/*
 * This class is the base class for all event adaptors used by Tcl.
 * It handles events generated by Java code and passes them to the Tcl
 * interpreter.
 *
 * A subclass of EventAdaptor will inplement a particular event interface
 * and is usually generated on-the-fly by the AdaptorGen class.
 *
 * NOTE:
 *
 *    + THIS CLASS MUST BE PUBLIC, otherwise some JVM may refuse to load
 *      subclasses of this class in the custom AdaptorClassLoader.
 *
 *    + Some methods in this class are called by subclasses and thus must
 *      be public. All public methods are prefixed with "_" in order to
 *	avoid conflicts with method names in event interfaces. These methods
 *	are also declared "final", so that if a conflict does happen, it
 *	will be reported by the JVM as an attempt to redefine a final
 *	methood.
 */

public class EventAdaptor {

/*
 * true if the init() has been called, false otherwise.
 */

private boolean initialized;

/*
 * The event should be fired to this interpreter.
 */

Interp interp;

/*
 * The event source object.
 */

Object source;

/*
 * Stores the callbacks that are currently being handled by the target.
 */

Hashtable callbacks;

/*
 * If an Exception is throws during the execution of the event
 * handler, it is stored in this member variable for later processing
 * by _wrongException(). If no Exception is thrown, the value of this
 * variable is null.
 */

Throwable exception;

/*
 * The event set handled by this adaptor.
 */

EventSetDescriptor eventDesc;

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

public
EventAdaptor()
{
    initialized = false;
}

/*
 *----------------------------------------------------------------------
 *
 * init --
 *
 *	Initialize the event adaptor object and register it as a
 *	listener to the event source.
 *
 *	The initialization is carried out in this method instead of
 *	the constructor. This makes it easy to generate class data for
 *	the event adaptor subclasses.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The adaptor is registered as a listener to the event source.
 *
 *----------------------------------------------------------------------
 */

void
init(
    Interp i,			// Interpreter in which the event should fire.
    Object src,			// Event source.
    EventSetDescriptor desc) 	// Describes the event to listen to.
throws
    TclException		// Standard Tcl exception.
{
    interp = i;
    callbacks = new Hashtable();
    eventDesc = desc;
    source = src;

    Method method = eventDesc.getAddListenerMethod();
    if (method != null) {
	Object args[] = new Object[1];
	args[0] = this;

	try {
	    method.invoke(source, args);
	} catch (Exception e) {
	    throw new ReflectException(i, e);
	}
    }
    initialized = true;
}

/*
 *----------------------------------------------------------------------
 *
 * setCallback --
 *
 *	Set the callback script of the given event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If a callback has already been installed for the given event,
 *	it will be replaced by the new callback.
 *
 *----------------------------------------------------------------------
 */

void
setCallback(
    String eventName,		// Name of the event for which a callback
				// should be created.
    TclObject command)		// Tcl command to invoke when the given event
    				// fires.
{
    check();
    TclObject oldCmd = (TclObject)callbacks.get(eventName);
    if (oldCmd != null) {
	oldCmd.release();
	callbacks.remove(eventName);
    }
    callbacks.put(eventName, command);
    command.preserve();
}

/*
 *----------------------------------------------------------------------
 *
 * deleteCallback --
 *
 *	Deletes the callback script of the given event, if one exists.
 *
 * Results:
 *	The number of events that are still handled after deleting
 *	the script for the given event.
 *
 * Side effects:
 *	If no events are handled after deleting the script for the
 *	given event, this event listener is removed from the object and
 *	the adaptor is uninitialized.
 *
 *----------------------------------------------------------------------
 */

int
deleteCallback(
    String eventName)		// Name of the event for which a callback
				// should be created.
throws
    TclException		// Standard Tcl exception.
{
    check();

    TclObject oldCmd = (TclObject)callbacks.get(eventName);
    if (oldCmd != null) {
	oldCmd.release();
	callbacks.remove(eventName);
    }

    int size = callbacks.size();

    if (size == 0) {
	try {
	    Method method = eventDesc.getRemoveListenerMethod();
	    if (method != null) {
		Object args[] = new Object[1];
		args[0] = this;

		method.invoke(source, args);
	    }
	} catch (Exception e) {
	    throw new ReflectException(interp, e);
	} finally {
	    initialized = false;
	    callbacks = null;
	    eventDesc = null;
	    interp = null;
	    source = null;
	}
    }

    return size;
}

/*
 *----------------------------------------------------------------------
 *
 * getCallback --
 *
 *	Query the callback command for the given event.
 *
 * Results:
 *	The callback command for the given event, if any, or null
 *	if no callback was registered for the event.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

TclObject
getCallback(
    String eventName)		// Name of the event to query.
{
    check();
    return (TclObject)callbacks.get(eventName);
}

/*
 *----------------------------------------------------------------------
 *
 * getHandledEvents --
 *
 *	Query all the events that are currently handled by
 *	this adaptor.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The full names of the handled events are appended to the list.
 *
 *----------------------------------------------------------------------
 */

void
getHandledEvents(
    TclObject list)		// TclList to store the name of the handled
				// events.
{
    check();
    try {
	String interfaceName = eventDesc.getListenerType().getName();

	for (Enumeration e = callbacks.keys(); e.hasMoreElements(); ) {
	    String eventName = (String)e.nextElement();
	    TclList.append(null, list, TclString.newInstance(
		    interfaceName + "." + eventName));
	}
    } catch (TclException e) {
	throw new TclRuntimeError("unexpected TclException: " + e);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * processEvent --
 *
 *	This method is called whenever an event is fired by the event
 *	source.  If a callback has been registered, the Tcl command is
 *	executed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The callback script may have any side effect.
 *
 *----------------------------------------------------------------------
 */

public final void
_processEvent(
    Object params[],		// Event object associated with this event.
    String eventName)		// Name of the event.
throws
    Throwable	
{
    check();
    exception = null;

    TclObject cmd = (TclObject)callbacks.get(eventName);
    if (cmd != null) {
	Class paramTypes[] = null;
	Method methods[] = eventDesc.getListenerType().getMethods();
	for (int i = 0; i < methods.length; i++) {
	    if (methods[i].getName().equals(eventName)) {
		paramTypes = methods[i].getParameterTypes();
		break;
	    }
	}

	BeanEvent evt = new BeanEvent(interp, paramTypes, params, cmd);
	interp.getNotifier().queueEvent(evt, TCL.QUEUE_TAIL);
	evt.sync();

	exception = evt.exception;
	if (exception != null) {
	    throw exception;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * check --
 *
 *	Sanity check. Make sure the init() method has been called on
 *	this object.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	TclRuntimeError is thrown if the init() method has not been caled.
 *
 *----------------------------------------------------------------------
 */

private final void
check()
{
    if (!initialized) {
	throw new TclRuntimeError("EventAdaptor not initialized");
    }
}

/*
 *----------------------------------------------------------------------
 *
 * _wrongException --
 *
 *	This procedure is called if the binding script generates an
 *	exception which is not declared in the method's "throws" clause.
 *	If the exception is an unchecked exception (i.e., a subclass of
 *	java.lang.Error), it gets re-thrown. Otherwise, a Tcl bgerror
 *	is triggered and this procedure returns normally.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	unchecked exceptions will be re-thrown.
 *
 *----------------------------------------------------------------------
 */

public final void
_wrongException()
throws
    Error,			// If a Error was caught during the
				// execution of the binding script, it
				// is re-thrown.
    RuntimeException 		// If a RuntimeException was caught during the
				// execution of the binding script, it
				// is re-thrown.
{
    if (exception instanceof Error) {
	throw (Error)exception;
    }
    if (exception instanceof RuntimeException) {
	throw (RuntimeException)exception;
    }

    if (!(exception instanceof TclException)) {
	interp.setResult("unexpected exception: " + exception);
    } else {
	/*
	 * The error message is already in the interp in the case of
	 * a TclException, so there is no need to set it here.
	 */
    }

    interp.addErrorInfo("\n    (command bound to event)");
    interp.backgroundError();
}

/*
 *----------------------------------------------------------------------
 *
 * _return_boolean --
 *
 *	Converts the interp's result to a boolean value and returns it.
 *
 * Results:
 *	A boolean value from the interp's result; false if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final boolean
_return_boolean()
{
    if (exception != null) {
	/*
	 * An unexpected exception had happen during the execution of
	 * the binding. We return an "undefined" value without looking
	 * at interp.getResult().
	 */
	
	return false;
    }

    TclObject result = interp.getResult();
    try {
	return TclBoolean.get(interp, result);
    } catch (TclException e) {
	interp.addErrorInfo(
		"\n    (attempting to return boolean from binding)");
	interp.backgroundError();
	return false;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * _return_byte --
 *
 *	Converts the interp's result to a byte value and returns it.
 *
 * Results:
 *	A byte value from the interp's result; 0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final byte
_return_byte()
{
    return (byte)_return_int();
}

/*
 *----------------------------------------------------------------------
 *
 * _return_char --
 *
 *	Converts the interp's result to a char value and returns it.
 *
 * Results:
 *	A char value from the interp's result; \0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final char
_return_char()
{
    if (exception != null) {
	/*
	 * An unexpected exception had happen during the execution of
	 * the binding. We return an "undefined" value without looking
	 * at interp.getResult().
	 */
	
	return '\0';
    }

    String s = interp.getResult().toString();

    if (s.length() == 1) {
	return s.charAt(0);
    } else {
	interp.setResult("expecting character but got \"" + s + "\"");
	interp.addErrorInfo(
		"\n    (attempting to return character from binding)");
	interp.backgroundError();
	return '\0';
    }
}

/*
 *----------------------------------------------------------------------
 *
 * _return_double --
 *
 *	Converts the interp's result to a double value and returns it.
 *
 * Results:
 *	A double value from the interp's result; 0.0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final double
_return_double()
{
    if (exception != null) {
	/*
	 * An unexpected exception had happen during the execution of
	 * the binding. We return an "undefined" value without looking
	 * at interp.getResult().
	 */
	
	return 0.0;
    }

    TclObject result = interp.getResult();
    try {
	return TclDouble.get(interp, result);
    } catch (TclException e) {
	interp.addErrorInfo(
	    "\n    (attempting to return floating-point number from binding)");
	interp.backgroundError();
	return 0.0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * _return_float --
 *
 *	Converts the interp's result to a float value and returns it.
 *
 * Results:
 *	A float value from the interp's result; 0.0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final float
_return_float()
{
    return (float)_return_double();
}

/*
 *----------------------------------------------------------------------
 *
 * _return_int --
 *
 *	Converts the interp's result to a int value and returns it.
 *
 * Results:
 *	A int value from the interp's result; 0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final int
_return_int()
{
    if (exception != null) {
	/*
	 * An unexpected exception had happen during the execution of
	 * the binding. We return an "undefined" value without looking
	 * at interp.getResult().
	 */
	
	return 0;
    }
    TclObject result = interp.getResult();
    try {
	return TclInteger.get(interp, result);
    } catch (TclException e) {
	interp.addErrorInfo(
		"\n    (attempting to return integer number from binding)");
	interp.backgroundError();
	return 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * _return_long --
 *
 *	Converts the interp's result to a long value and returns it.
 *
 * Results:
 *	A long value from the interp's result; 0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final long
_return_long()
{
    return (long)_return_int();
}

/*
 *----------------------------------------------------------------------
 *
 * _return_short --
 *
 *	Converts the interp's result to a short value and returns it.
 *
 * Results:
 *	A short value from the interp's result; 0 if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final short
_return_short()
{
    return (short)_return_int();
}

/*
 *----------------------------------------------------------------------
 *
 * _return_Object --
 *
 *	Converts the interp's result to an instance of the given class
 *	and returns it.
 *
 * Results:
 *	A Object value from the interp's result; null if the
 *	conversion fails.
 *
 * Side effects:
 *	A background error is reported if the conversion fails.
 *
 *----------------------------------------------------------------------
 */

public final Object
_return_Object(
    String className)		// The name of the class that the object must
				// belong to.
{
    if (exception != null) {
	/*
	 * An unexpected exception had happen during the execution of
	 * the binding. We return an "undefined" value without looking
	 * at interp.getResult().
	 */
	
	return null;
    }

    if (className.equals("java.lang.String")) {
	return interp.getResult().toString();
    }

    Class cls = null;
    try {
	cls = Class.forName(className);
    } catch (ClassNotFoundException e) {
	/*
	 * This exception should never happen here because the class
	 * of the given name should have already been referenced
	 * before execution comes to here (e.g, when a parameter of
	 * this class is passed to the method).
	 *
	 * If the exception indeed happens, our byte-code generator
	 * AdaptorGen must be at fault.
	 */

	throw new TclRuntimeError("unexpected exception " + e);
    }

    try {
	TclObject result = interp.getResult();
	Object obj = ReflectObject.get(interp, result);
	if (!cls.isInstance(obj)) {
	    throw new TclException(interp, "cannot cast object " +
		    result.toString() + " (" + obj.toString() +
		    ") to required type " +
		    className);
	}
	return obj;
    } catch (TclException e) {
	interp.addErrorInfo(
		"\n    (attempting to return object from binding)");
	interp.backgroundError();
	return null;
    }
}

} // end EventAdaptor

