001: /*
002: * AfterCmd.java --
003: *
004: * Implements the built-in "after" Tcl command.
005: *
006: * Copyright (c) 1997 Cornell University.
007: * Copyright (c) 1997 Sun Microsystems, Inc.
008: *
009: * See the file "license.terms" for information on usage and
010: * redistribution of this file, and for a DISCLAIMER OF ALL
011: * WARRANTIES.
012: *
013: * RCS: @(#) $Id: AfterCmd.java,v 1.9 2006/06/13 06:52:47 mdejong Exp $
014: *
015: */
016:
017: package tcl.lang;
018:
019: import java.util.ArrayList;
020:
021: /*
022: * This class implements the built-in "after" command in Tcl.
023: */
024:
025: class AfterCmd implements Command {
026:
027: /*
028: * The list of handler are stored as AssocData in the interp.
029: */
030:
031: AfterAssocData assocData = null;
032:
033: /*
034: * Valid command options.
035: */
036:
037: static final private String validOpts[] = { "cancel", "idle",
038: "info" };
039:
040: static final int OPT_CANCEL = 0;
041: static final int OPT_IDLE = 1;
042: static final int OPT_INFO = 2;
043:
044: /*
045: *----------------------------------------------------------------------
046: *
047: * cmdProc --
048: *
049: * This procedure is invoked as part of the Command interface to
050: * process the "after" Tcl command. See the user documentation
051: * for details on what it does.
052: *
053: * Results:
054: * None.
055: *
056: * Side effects:
057: * See the user documentation.
058: *
059: *----------------------------------------------------------------------
060: */
061:
062: public void cmdProc(Interp interp, // Current interpreter.
063: TclObject argv[]) // Argument list.
064: throws TclException // A standard Tcl exception.
065: {
066: int i;
067: Notifier notifier = (Notifier) interp.getNotifier();
068: Object info;
069:
070: if (assocData == null) {
071: /*
072: * Create the "after" information associated for this
073: * interpreter, if it doesn't already exist.
074: */
075:
076: assocData = (AfterAssocData) interp
077: .getAssocData("tclAfter");
078: if (assocData == null) {
079: assocData = new AfterAssocData();
080: interp.setAssocData("tclAfter", assocData);
081: }
082: }
083:
084: if (argv.length < 2) {
085: throw new TclNumArgsException(interp, 1, argv,
086: "option ?arg arg ...?");
087: }
088:
089: /*
090: * First lets see if the command was passed a number as the first argument.
091: */
092:
093: boolean isNumber = false;
094: int ms = 0;
095:
096: if (argv[1].isIntType()) {
097: ms = TclInteger.get(interp, argv[1]);
098: isNumber = true;
099: } else {
100: String s = argv[1].toString();
101: if ((s.length() > 0) && (Character.isDigit(s.charAt(0)))) {
102: ms = TclInteger.get(interp, argv[1]);
103: isNumber = true;
104: }
105: }
106:
107: if (isNumber) {
108: if (ms < 0) {
109: ms = 0;
110: }
111: if (argv.length == 2) {
112: /*
113: * Sleep for at least the given milliseconds and return.
114: */
115:
116: long endTime = System.currentTimeMillis() + ms;
117: while (true) {
118: try {
119: Thread.sleep(ms);
120: return;
121: } catch (InterruptedException e) {
122: /*
123: * We got interrupted. Sleep again if we havn't slept
124: * long enough yet.
125: */
126:
127: long sysTime = System.currentTimeMillis();
128: if (sysTime >= endTime) {
129: return;
130: }
131: ms = (int) (endTime - sysTime);
132: continue;
133: }
134: }
135: }
136:
137: TclObject cmd = getCmdObject(argv);
138: cmd.preserve();
139:
140: assocData.lastAfterId++;
141: TimerInfo timerInfo = new TimerInfo(notifier, ms);
142: timerInfo.interp = interp;
143: timerInfo.command = cmd;
144: timerInfo.id = assocData.lastAfterId;
145:
146: assocData.handlers.add(timerInfo);
147:
148: interp.setResult("after#" + timerInfo.id);
149:
150: return;
151: }
152:
153: /*
154: * If it's not a number it must be a subcommand.
155: */
156:
157: int index;
158:
159: try {
160: index = TclIndex.get(interp, argv[1], validOpts, "option",
161: 0);
162: } catch (TclException e) {
163: throw new TclException(interp, "bad argument \"" + argv[1]
164: + "\": must be cancel, idle, info, or a number");
165: }
166:
167: switch (index) {
168: case OPT_CANCEL:
169: if (argv.length < 3) {
170: throw new TclNumArgsException(interp, 2, argv,
171: "id|command");
172: }
173:
174: TclObject arg = getCmdObject(argv);
175: arg.preserve();
176:
177: /*
178: * Search the timer/idle handler by id or by command.
179: */
180:
181: info = null;
182: for (i = 0; i < assocData.handlers.size(); i++) {
183: Object obj = assocData.handlers.get(i);
184: if (obj instanceof TimerInfo) {
185: TclObject cmd = ((TimerInfo) obj).command;
186:
187: if ((cmd == arg)
188: || cmd.toString().equals(arg.toString())) {
189: info = obj;
190: break;
191: }
192: } else {
193: TclObject cmd = ((IdleInfo) obj).command;
194:
195: if ((cmd == arg)
196: || cmd.toString().equals(arg.toString())) {
197: info = obj;
198: break;
199: }
200: }
201: }
202: if (info == null) {
203: info = getAfterEvent(interp, arg.toString());
204: }
205: arg.release();
206:
207: /*
208: * Cancel the handler.
209: */
210:
211: if (info != null) {
212: if (info instanceof TimerInfo) {
213: TimerInfo ti = (TimerInfo) info;
214: ti.cancel();
215: ti.command.release();
216: } else {
217: IdleInfo ii = (IdleInfo) info;
218: ii.cancel();
219: ii.command.release();
220: }
221:
222: int hindex = assocData.handlers.indexOf(info);
223: if (hindex == -1) {
224: throw new TclRuntimeError("info " + info
225: + " has no handler");
226: }
227: if (assocData.handlers.remove(hindex) == null) {
228: throw new TclRuntimeError(
229: "cound not remove handler " + hindex);
230: }
231: }
232: break;
233:
234: case OPT_IDLE:
235: if (argv.length < 3) {
236: throw new TclNumArgsException(interp, 2, argv,
237: "script script ...");
238: }
239:
240: TclObject cmd = getCmdObject(argv);
241: cmd.preserve();
242: assocData.lastAfterId++;
243:
244: IdleInfo idleInfo = new IdleInfo(notifier);
245: idleInfo.interp = interp;
246: idleInfo.command = cmd;
247: idleInfo.id = assocData.lastAfterId;
248:
249: assocData.handlers.add(idleInfo);
250:
251: interp.setResult("after#" + idleInfo.id);
252: break;
253:
254: case OPT_INFO:
255: if (argv.length == 2) {
256: /*
257: * No id is given. Return a list of current after id's.
258: */
259:
260: TclObject list = TclList.newInstance();
261: for (i = 0; i < assocData.handlers.size(); i++) {
262: int id;
263: Object obj = assocData.handlers.get(i);
264: if (obj instanceof TimerInfo) {
265: id = ((TimerInfo) obj).id;
266: } else {
267: id = ((IdleInfo) obj).id;
268: }
269: TclList.append(interp, list, TclString
270: .newInstance("after#" + id));
271: }
272: interp.setResult(list);
273: return;
274: }
275: if (argv.length != 3) {
276: throw new TclNumArgsException(interp, 2, argv, "?id?");
277: }
278:
279: /*
280: * Return command and type of the given after id.
281: */
282:
283: info = getAfterEvent(interp, argv[2].toString());
284: if (info == null) {
285: throw new TclException(interp, "event \"" + argv[2]
286: + "\" doesn't exist");
287: }
288: TclObject list = TclList.newInstance();
289: TclList
290: .append(
291: interp,
292: list,
293: ((info instanceof TimerInfo) ? ((TimerInfo) info).command
294: : ((IdleInfo) info).command));
295: TclList.append(interp, list, TclString
296: .newInstance((info instanceof TimerInfo) ? "timer"
297: : "idle"));
298:
299: interp.setResult(list);
300: break;
301: }
302: }
303:
304: /*
305: *----------------------------------------------------------------------
306: *
307: * getCmdObject --
308: *
309: * Returns a TclObject that contains the command script passed
310: * to the "after" command.
311: *
312: * Results:
313: * A concatenation of the objects from argv[2] through argv[n-1].
314: *
315: * Side effects:
316: * None.
317: *
318: *----------------------------------------------------------------------
319: */
320:
321: private TclObject getCmdObject(TclObject argv[]) // Argument list passed to the "after" command.
322: throws TclException {
323: if (argv.length == 3) {
324: return argv[2];
325: } else {
326: TclObject cmd = Util.concat(2, argv.length - 1, argv);
327: return cmd;
328: }
329: }
330:
331: /*
332: *----------------------------------------------------------------------
333: *
334: * getAfterEvent --
335: *
336: * This procedure parses an "after" id such as "after#4" and
337: * returns an AfterInfo object.
338: *
339: * Results:
340: * The return value is an AfterInfo object. if one is
341: * found that corresponds to "string", or null if no
342: * corresponding after event can be found.
343: *
344: * Side effects:
345: * None.
346: *
347: *----------------------------------------------------------------------
348: */
349:
350: private Object getAfterEvent(Interp interp, String string) // Textual identifier for after event, such
351: // as "after#6".
352: {
353: if (!string.startsWith("after#")) {
354: return null;
355: }
356:
357: StrtoulResult res = interp.strtoulResult;
358: Util.strtoul(string, 6, 10, res);
359: if (res.errno != 0) {
360: return null;
361: }
362: int id = (int) res.value;
363:
364: for (int i = 0; i < assocData.handlers.size(); i++) {
365: Object obj = assocData.handlers.get(i);
366: if (obj instanceof TimerInfo) {
367: if (((TimerInfo) obj).id == id) {
368: return obj;
369: }
370: } else {
371: if (((IdleInfo) obj).id == id) {
372: return obj;
373: }
374: }
375: }
376:
377: return null;
378: }
379:
380: /*
381: * This inner class manages the list of handlers created by the
382: * "after" command. We keep the handler has an AssocData so that they
383: * will continue to exist even if the "after" command is deleted.
384: */
385:
386: class AfterAssocData implements AssocData {
387:
388: /*
389: * The set of handlers created but not yet fired.
390: */
391:
392: ArrayList handlers = new ArrayList();
393:
394: /*
395: * Timer identifier of most recently created timer.
396: */
397:
398: int lastAfterId = 0;
399:
400: /*
401: *----------------------------------------------------------------------
402: *
403: * disposeAssocData --
404: *
405: * This method is called when the interpreter is destroyed or
406: * when Interp.deleteAssocData is called on a registered
407: * AssocData instance.
408: *
409: * Results:
410: * None.
411: *
412: * Side effects:
413: * All unfired handler are cancelled; their command objects are
414: * released.
415: *
416: *----------------------------------------------------------------------
417: */
418:
419: public void disposeAssocData(Interp interp) // The interpreter in which this AssocData
420: // instance is registered in.
421: {
422: for (int i = assocData.handlers.size() - 1; i >= 0; i--) {
423: Object info = assocData.handlers.get(i);
424: if (assocData.handlers.remove(i) == null) {
425: throw new TclRuntimeError(
426: "cound not remove handler " + i);
427: }
428: if (info instanceof TimerInfo) {
429: TimerInfo ti = (TimerInfo) info;
430: ti.cancel();
431: ti.command.release();
432: } else {
433: IdleInfo ii = (IdleInfo) info;
434: ii.cancel();
435: ii.command.release();
436: }
437: }
438: assocData = null;
439: }
440:
441: } // end AfterCmd.AfterAssocData
442:
443: /*
444: * This inner class implement timer handlers for the "after"
445: * command. It stores a script to be executed when the timer event is
446: * fired.
447: */
448:
449: class TimerInfo extends TimerHandler {
450:
451: /*
452: * Interpreter in which the script should be executed.
453: */
454:
455: Interp interp;
456:
457: /*
458: * Command to execute when the timer fires.
459: */
460:
461: TclObject command;
462:
463: /*
464: * Integer identifier for command; used to cancel it.
465: */
466:
467: int id;
468:
469: /*
470: *----------------------------------------------------------------------
471: *
472: * TimerInfo --
473: *
474: * Constructs a new TimerInfo instance.
475: *
476: * Side effects:
477: * The timer is initialized by the super class's constructor.
478: *
479: *----------------------------------------------------------------------
480: */
481:
482: TimerInfo(Notifier n, // The notifier to fire the event.
483: int milliseconds) // How many milliseconds to wait
484: // before invoking processTimerEvent().
485: {
486: super (n, milliseconds);
487: }
488:
489: /*
490: *----------------------------------------------------------------------
491: *
492: * processTimerEvent --
493: *
494: * Process the timer event.
495: *
496: * Results:
497: * None.
498: *
499: * Side effects:
500: * The command executed by this timer can have arbitrary side
501: * effects.
502: *
503: *----------------------------------------------------------------------
504: */
505:
506: public void processTimerEvent() {
507: try {
508: int index = assocData.handlers.indexOf(this );
509: if (index == -1) {
510: throw new TclRuntimeError("this " + this
511: + " has no handler");
512: }
513: if (assocData.handlers.remove(index) == null) {
514: throw new TclRuntimeError(
515: "cound not remove handler " + index);
516: }
517: interp.eval(command, TCL.EVAL_GLOBAL);
518: } catch (TclException e) {
519: interp.addErrorInfo("\n (\"after\" script)");
520: interp.backgroundError();
521: } finally {
522: command.release();
523: command = null;
524: }
525: }
526:
527: public String toString() {
528: StringBuffer sb = new StringBuffer(64);
529: sb.append(super .toString());
530: sb.append("AfterCmd.TimerInfo : " + command + "\n");
531: return sb.toString();
532: }
533:
534: } // end AfterCmd.AfterInfo
535:
536: /*
537: * This inner class implement idle handlers for the "after"
538: * command. It stores a script to be executed when the idle event is
539: * fired.
540: */
541:
542: class IdleInfo extends IdleHandler {
543:
544: /*
545: * Interpreter in which the script should be executed.
546: */
547:
548: Interp interp;
549:
550: /*
551: * Command to execute when the idle event fires.
552: */
553:
554: TclObject command;
555:
556: /*
557: * Integer identifier for command; used to cancel it.
558: */
559:
560: int id;
561:
562: /*
563: *----------------------------------------------------------------------
564: *
565: * IdleInfo --
566: *
567: * Constructs a new IdleInfo instance.
568: *
569: * Side effects:
570: * The idle handler is initialized by the super class's
571: * constructor.
572: *
573: *----------------------------------------------------------------------
574: */
575:
576: IdleInfo(Notifier n) // The notifier to fire the event.
577: {
578: super (n);
579: }
580:
581: /*
582: *----------------------------------------------------------------------
583: *
584: * processIdleEvent --
585: *
586: * Process the idle event.
587: *
588: * Results:
589: * None.
590: *
591: * Side effects:
592: * The command executed by this idle handler can have arbitrary side
593: * effects.
594: *
595: *----------------------------------------------------------------------
596: */
597:
598: public void processIdleEvent() {
599: try {
600: int index = assocData.handlers.indexOf(this );
601: if (index == -1) {
602: throw new TclRuntimeError("this " + this
603: + " has no handler");
604: }
605: if (assocData.handlers.remove(index) == null) {
606: throw new TclRuntimeError(
607: "cound not remove handler " + index);
608: }
609: interp.eval(command, TCL.EVAL_GLOBAL);
610: } catch (TclException e) {
611: interp.addErrorInfo("\n (\"after\" script)");
612: interp.backgroundError();
613: } finally {
614: command.release();
615: command = null;
616: }
617: }
618:
619: public String toString() {
620: StringBuffer sb = new StringBuffer(64);
621: sb.append(super .toString());
622: sb.append("AfterCmd.IdleInfo : " + command + "\n");
623: return sb.toString();
624: }
625:
626: } // end AfterCmd.AfterInfo
627:
628: } // end AfterCmd
|