001: /*
002: * EventAdaptor.java --
003: *
004: * Base class for event adaptors classes generated by Tcl.
005: *
006: * Copyright (c) 1997 Sun Microsystems, Inc.
007: *
008: * See the file "license.terms" for information on usage and
009: * redistribution of this file, and for a DISCLAIMER OF ALL
010: * WARRANTIES.
011: *
012: * RCS: @(#) $Id: EventAdaptor.java,v 1.3 2002/12/21 04:04:04 mdejong Exp $
013: */
014:
015: package tcl.lang;
016:
017: import java.util.*;
018: import java.lang.reflect.*;
019: import java.beans.*;
020:
021: /*
022: * This class is the base class for all event adaptors used by Tcl.
023: * It handles events generated by Java code and passes them to the Tcl
024: * interpreter.
025: *
026: * A subclass of EventAdaptor will implement a particular event interface
027: * and is usually generated on-the-fly by the AdaptorGen class.
028: *
029: * NOTE:
030: *
031: * + THIS CLASS MUST BE PUBLIC, otherwise some JVM may refuse to load
032: * subclasses of this class in the custom AdaptorClassLoader.
033: *
034: * + Some methods in this class are called by subclasses and thus must
035: * be public. All public methods are prefixed with "_" in order to
036: * avoid conflicts with method names in event interfaces. These methods
037: * are also declared "final", so that if a conflict does happen, it
038: * will be reported by the JVM as an attempt to redefine a final
039: * methood.
040: */
041:
042: public class EventAdaptor {
043:
044: // true if the init() has been called, false otherwise.
045:
046: private boolean initialized;
047:
048: // The event should be fired to this interpreter.
049:
050: Interp interp;
051:
052: // The event source object.
053:
054: Object source;
055:
056: // Stores the callbacks that are currently being handled by the target.
057:
058: Hashtable callbacks;
059:
060: // If an Exception is throws during the execution of the event
061: // handler, it is stored in this member variable for later processing
062: // by _wrongException(). If no Exception is thrown, the value of this
063: // variable is null.
064:
065: Throwable exception;
066:
067: // The event set handled by this adaptor.
068:
069: EventSetDescriptor eventDesc;
070:
071: /*
072: *----------------------------------------------------------------------
073: *
074: * EventAdaptor --
075: *
076: * Creates a new EventAdaptor instance.
077: *
078: * Side effects:
079: * Member fields are initialized.
080: *
081: *----------------------------------------------------------------------
082: */
083:
084: public EventAdaptor() {
085: initialized = false;
086: }
087:
088: /*
089: *----------------------------------------------------------------------
090: *
091: * init --
092: *
093: * Initialize the event adaptor object and register it as a
094: * listener to the event source.
095: *
096: * The initialization is carried out in this method instead of
097: * the constructor. This makes it easy to generate class data for
098: * the event adaptor subclasses.
099: *
100: * Results:
101: * None.
102: *
103: * Side effects:
104: * The adaptor is registered as a listener to the event source.
105: *
106: *----------------------------------------------------------------------
107: */
108:
109: void init(Interp i, // Interpreter in which the event should fire.
110: Object src, // Event source.
111: EventSetDescriptor desc) // Describes the event to listen to.
112: throws TclException // Standard Tcl exception.
113: {
114: interp = i;
115: callbacks = new Hashtable();
116: eventDesc = desc;
117: source = src;
118:
119: Method method = eventDesc.getAddListenerMethod();
120: if (method != null) {
121: Object args[] = new Object[1];
122: args[0] = this ;
123:
124: try {
125: method.invoke(source, args);
126: } catch (Exception e) {
127: throw new ReflectException(i, e);
128: }
129: }
130: initialized = true;
131: }
132:
133: /*
134: *----------------------------------------------------------------------
135: *
136: * setCallback --
137: *
138: * Set the callback script of the given event.
139: *
140: * Results:
141: * None.
142: *
143: * Side effects:
144: * If a callback has already been installed for the given event,
145: * it will be replaced by the new callback.
146: *
147: *----------------------------------------------------------------------
148: */
149:
150: void setCallback(String eventName, // Name of the event for which a callback
151: // should be created.
152: TclObject command) // Tcl command to invoke when the given event
153: // fires.
154: {
155: check();
156: TclObject oldCmd = (TclObject) callbacks.get(eventName);
157: if (oldCmd != null) {
158: callbacks.remove(eventName);
159: }
160: // We use to preserve the passed in TclObject, but that leads
161: // to a memory leak in Tcl Blend because release was not called.
162: // Instead just use a TclString which is managed by Java.
163: TclObject str = TclString.newInstance(command.toString());
164: callbacks.put(eventName, str);
165: }
166:
167: /*
168: *----------------------------------------------------------------------
169: *
170: * deleteCallback --
171: *
172: * Deletes the callback script of the given event, if one exists.
173: *
174: * Results:
175: * The number of events that are still handled after deleting
176: * the script for the given event.
177: *
178: * Side effects:
179: * If no events are handled after deleting the script for the
180: * given event, this event listener is removed from the object and
181: * the adaptor is uninitialized.
182: *
183: *----------------------------------------------------------------------
184: */
185:
186: int deleteCallback(String eventName) // Name of the event for which a callback
187: // should be created.
188: throws TclException // Standard Tcl exception.
189: {
190: check();
191:
192: TclObject oldCmd = (TclObject) callbacks.get(eventName);
193: if (oldCmd != null) {
194: callbacks.remove(eventName);
195: }
196:
197: int size = callbacks.size();
198:
199: if (size == 0) {
200: try {
201: Method method = eventDesc.getRemoveListenerMethod();
202: if (method != null) {
203: Object args[] = new Object[1];
204: args[0] = this ;
205:
206: method.invoke(source, args);
207: }
208: } catch (Exception e) {
209: throw new ReflectException(interp, e);
210: } finally {
211: initialized = false;
212: callbacks = null;
213: eventDesc = null;
214: interp = null;
215: source = null;
216: }
217: }
218:
219: return size;
220: }
221:
222: /*
223: *----------------------------------------------------------------------
224: *
225: * getCallback --
226: *
227: * Query the callback command for the given event.
228: *
229: * Results:
230: * The callback command for the given event, if any, or null
231: * if no callback was registered for the event.
232: *
233: * Side effects:
234: * None.
235: *
236: *----------------------------------------------------------------------
237: */
238:
239: TclObject getCallback(String eventName) // Name of the event to query.
240: {
241: check();
242: return (TclObject) callbacks.get(eventName);
243: }
244:
245: /*
246: *----------------------------------------------------------------------
247: *
248: * getHandledEvents --
249: *
250: * Query all the events that are currently handled by
251: * this adaptor.
252: *
253: * Results:
254: * None.
255: *
256: * Side effects:
257: * The full names of the handled events are appended to the list.
258: *
259: *----------------------------------------------------------------------
260: */
261:
262: void getHandledEvents(TclObject list) // TclList to store the name of the handled
263: // events.
264: {
265: check();
266: try {
267: String interfaceName = eventDesc.getListenerType()
268: .getName();
269:
270: for (Enumeration e = callbacks.keys(); e.hasMoreElements();) {
271: String eventName = (String) e.nextElement();
272: TclList.append(null, list, TclString
273: .newInstance(interfaceName + "." + eventName));
274: }
275: } catch (TclException e) {
276: throw new TclRuntimeError("unexpected TclException: " + e);
277: }
278: }
279:
280: /*
281: *----------------------------------------------------------------------
282: *
283: * processEvent --
284: *
285: * This method is called whenever an event is fired by the event
286: * source. If a callback has been registered, the Tcl command is
287: * executed.
288: *
289: * Results:
290: * None.
291: *
292: * Side effects:
293: * The callback script may have any side effect.
294: *
295: *----------------------------------------------------------------------
296: */
297:
298: public final void _processEvent(Object params[], // Event object associated with this event.
299: String eventName) // Name of the event.
300: throws Throwable {
301: check();
302: exception = null;
303:
304: TclObject cmd = (TclObject) callbacks.get(eventName);
305: if (cmd != null) {
306: Class paramTypes[] = null;
307: Method methods[] = eventDesc.getListenerType().getMethods();
308: for (int i = 0; i < methods.length; i++) {
309: if (methods[i].getName().equals(eventName)) {
310: paramTypes = methods[i].getParameterTypes();
311: break;
312: }
313: }
314:
315: BeanEvent evt = new BeanEvent(interp, paramTypes, params,
316: cmd);
317: interp.getNotifier().queueEvent(evt, TCL.QUEUE_TAIL);
318: evt.sync();
319:
320: exception = evt.exception;
321: if (exception != null) {
322: throw exception;
323: }
324: }
325: }
326:
327: /*
328: *----------------------------------------------------------------------
329: *
330: * check --
331: *
332: * Sanity check. Make sure the init() method has been called on
333: * this object.
334: *
335: * Results:
336: * None.
337: *
338: * Side effects:
339: * TclRuntimeError is thrown if the init() method has not been caled.
340: *
341: *----------------------------------------------------------------------
342: */
343:
344: private final void check() {
345: if (!initialized) {
346: throw new TclRuntimeError("EventAdaptor not initialized");
347: }
348: }
349:
350: /*
351: *----------------------------------------------------------------------
352: *
353: * _wrongException --
354: *
355: * This procedure is called if the binding script generates an
356: * exception which is not declared in the method's "throws" clause.
357: * If the exception is an unchecked exception (i.e., a subclass of
358: * java.lang.Error), it gets re-thrown. Otherwise, a Tcl bgerror
359: * is triggered and this procedure returns normally.
360: *
361: * Results:
362: * None.
363: *
364: * Side effects:
365: * unchecked exceptions will be re-thrown.
366: *
367: *----------------------------------------------------------------------
368: */
369:
370: public final void _wrongException() throws Error, // If a Error was caught during the
371: // execution of the binding script, it
372: // is re-thrown.
373: RuntimeException // If a RuntimeException was caught during the
374: // execution of the binding script, it
375: // is re-thrown.
376: {
377: if (exception instanceof Error) {
378: throw (Error) exception;
379: }
380: if (exception instanceof RuntimeException) {
381: throw (RuntimeException) exception;
382: }
383:
384: if (!(exception instanceof TclException)) {
385: interp.setResult("unexpected exception: " + exception);
386: } else {
387: // The error message is already in the interp in the case of
388: // a TclException, so there is no need to set it here.
389: }
390:
391: interp.addErrorInfo("\n (command bound to event)");
392: interp.backgroundError();
393: }
394:
395: /*
396: *----------------------------------------------------------------------
397: *
398: * _return_boolean --
399: *
400: * Converts the interp's result to a boolean value and returns it.
401: *
402: * Results:
403: * A boolean value from the interp's result; false if the
404: * conversion fails.
405: *
406: * Side effects:
407: * A background error is reported if the conversion fails.
408: *
409: *----------------------------------------------------------------------
410: */
411:
412: public final boolean _return_boolean() {
413: if (exception != null) {
414: // An unexpected exception had happen during the execution of
415: // the binding. We return an "undefined" value without looking
416: // at interp.getResult().
417:
418: return false;
419: }
420:
421: TclObject result = interp.getResult();
422: try {
423: return TclBoolean.get(interp, result);
424: } catch (TclException e) {
425: interp
426: .addErrorInfo("\n (attempting to return boolean from binding)");
427: interp.backgroundError();
428: return false;
429: }
430: }
431:
432: /*
433: *----------------------------------------------------------------------
434: *
435: * _return_byte --
436: *
437: * Converts the interp's result to a byte value and returns it.
438: *
439: * Results:
440: * A byte value from the interp's result; 0 if the
441: * conversion fails.
442: *
443: * Side effects:
444: * A background error is reported if the conversion fails.
445: *
446: *----------------------------------------------------------------------
447: */
448:
449: public final byte _return_byte() {
450: return (byte) _return_int();
451: }
452:
453: /*
454: *----------------------------------------------------------------------
455: *
456: * _return_char --
457: *
458: * Converts the interp's result to a char value and returns it.
459: *
460: * Results:
461: * A char value from the interp's result; \0 if the
462: * conversion fails.
463: *
464: * Side effects:
465: * A background error is reported if the conversion fails.
466: *
467: *----------------------------------------------------------------------
468: */
469:
470: public final char _return_char() {
471: if (exception != null) {
472: // An unexpected exception had happen during the execution of
473: // the binding. We return an "undefined" value without looking
474: // at interp.getResult().
475:
476: return '\0';
477: }
478:
479: String s = interp.getResult().toString();
480:
481: if (s.length() == 1) {
482: return s.charAt(0);
483: } else {
484: interp.setResult("expecting character but got \"" + s
485: + "\"");
486: interp
487: .addErrorInfo("\n (attempting to return character from binding)");
488: interp.backgroundError();
489: return '\0';
490: }
491: }
492:
493: /*
494: *----------------------------------------------------------------------
495: *
496: * _return_double --
497: *
498: * Converts the interp's result to a double value and returns it.
499: *
500: * Results:
501: * A double value from the interp's result; 0.0 if the
502: * conversion fails.
503: *
504: * Side effects:
505: * A background error is reported if the conversion fails.
506: *
507: *----------------------------------------------------------------------
508: */
509:
510: public final double _return_double() {
511: if (exception != null) {
512: // An unexpected exception had happen during the execution of
513: // the binding. We return an "undefined" value without looking
514: // at interp.getResult().
515:
516: return 0.0;
517: }
518:
519: TclObject result = interp.getResult();
520: try {
521: return TclDouble.get(interp, result);
522: } catch (TclException e) {
523: interp
524: .addErrorInfo("\n (attempting to return floating-point number from binding)");
525: interp.backgroundError();
526: return 0.0;
527: }
528: }
529:
530: /*
531: *----------------------------------------------------------------------
532: *
533: * _return_float --
534: *
535: * Converts the interp's result to a float value and returns it.
536: *
537: * Results:
538: * A float value from the interp's result; 0.0 if the
539: * conversion fails.
540: *
541: * Side effects:
542: * A background error is reported if the conversion fails.
543: *
544: *----------------------------------------------------------------------
545: */
546:
547: public final float _return_float() {
548: return (float) _return_double();
549: }
550:
551: /*
552: *----------------------------------------------------------------------
553: *
554: * _return_int --
555: *
556: * Converts the interp's result to a int value and returns it.
557: *
558: * Results:
559: * A int value from the interp's result; 0 if the
560: * conversion fails.
561: *
562: * Side effects:
563: * A background error is reported if the conversion fails.
564: *
565: *----------------------------------------------------------------------
566: */
567:
568: public final int _return_int() {
569: if (exception != null) {
570: // An unexpected exception had happen during the execution of
571: // the binding. We return an "undefined" value without looking
572: // at interp.getResult().
573:
574: return 0;
575: }
576: TclObject result = interp.getResult();
577: try {
578: return TclInteger.get(interp, result);
579: } catch (TclException e) {
580: interp
581: .addErrorInfo("\n (attempting to return integer number from binding)");
582: interp.backgroundError();
583: return 0;
584: }
585: }
586:
587: /*
588: *----------------------------------------------------------------------
589: *
590: * _return_long --
591: *
592: * Converts the interp's result to a long value and returns it.
593: *
594: * Results:
595: * A long value from the interp's result; 0 if the
596: * conversion fails.
597: *
598: * Side effects:
599: * A background error is reported if the conversion fails.
600: *
601: *----------------------------------------------------------------------
602: */
603:
604: public final long _return_long() {
605: return (long) _return_int();
606: }
607:
608: /*
609: *----------------------------------------------------------------------
610: *
611: * _return_short --
612: *
613: * Converts the interp's result to a short value and returns it.
614: *
615: * Results:
616: * A short value from the interp's result; 0 if the
617: * conversion fails.
618: *
619: * Side effects:
620: * A background error is reported if the conversion fails.
621: *
622: *----------------------------------------------------------------------
623: */
624:
625: public final short _return_short() {
626: return (short) _return_int();
627: }
628:
629: /*
630: *----------------------------------------------------------------------
631: *
632: * _return_Object --
633: *
634: * Converts the interp's result to an instance of the given class
635: * and returns it.
636: *
637: * Results:
638: * A Object value from the interp's result; null if the
639: * conversion fails.
640: *
641: * Side effects:
642: * A background error is reported if the conversion fails.
643: *
644: *----------------------------------------------------------------------
645: */
646:
647: public final Object _return_Object(String className) // The name of the class that the object must
648: // belong to.
649: {
650: if (exception != null) {
651: // An unexpected exception had happen during the execution of
652: // the binding. We return an "undefined" value without looking
653: // at interp.getResult().
654:
655: return null;
656: }
657:
658: if (className.equals("java.lang.String")) {
659: return interp.getResult().toString();
660: }
661:
662: Class cls = null;
663: try {
664: cls = Class.forName(className);
665: } catch (ClassNotFoundException e) {
666: // This exception should never happen here because the class
667: // of the given name should have already been referenced
668: // before execution comes to here (e.g, when a parameter of
669: // this class is passed to the method).
670: //
671: // If the exception indeed happens, our byte-code generator
672: // AdaptorGen must be at fault.
673:
674: throw new TclRuntimeError("unexpected exception " + e);
675: }
676:
677: try {
678: TclObject result = interp.getResult();
679: Object obj = ReflectObject.get(interp, result);
680: if (!cls.isInstance(obj)) {
681: throw new TclException(interp, "cannot cast object "
682: + result.toString() + " (" + obj.toString()
683: + ") to required type " + className);
684: }
685: return obj;
686: } catch (TclException e) {
687: interp
688: .addErrorInfo("\n (attempting to return object from binding)");
689: interp.backgroundError();
690: return null;
691: }
692: }
693:
694: } // end EventAdaptor
|