/* * 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