001: /*
002: * JavaTryCmd.java --
003: *
004: * Implements the built-in "java::try" command.
005: *
006: * Copyright (c) 1998 by Moses DeJong
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: JavaTryCmd.java,v 1.6 2006/04/27 02:16:13 mdejong Exp $
013: *
014: */
015:
016: package tcl.lang;
017:
018: /**
019: * This class implements the built-in "java::try" command. The command
020: * provides access to the java's try-catch-finally construct.
021: */
022:
023: public class JavaTryCmd implements Command {
024:
025: /**
026:
027: *----------------------------------------------------------------------
028: *
029: * cmdProc --
030: *
031: * This procedure is invoked as part of the Command interface to
032: * process the "java::try" Tcl command. See the user documentation
033: * for details on what it does.
034: *
035: * Results:
036: * None.
037: *
038: * Side effects:
039: * See the user documentation.
040: *
041: *----------------------------------------------------------------------
042: */
043:
044: public void cmdProc(Interp interp, TclObject[] argv)
045: throws TclException {
046:
047: // (argv.length % 3) == 1 is a try+catch+finally block
048: // (argv.length % 3) == 2 is a try+catch block
049:
050: int argv_length_mod = argv.length % 3;
051: boolean try_catch_finally = false;
052: boolean try_catch = false;
053:
054: if (argv_length_mod == 1) {
055: try_catch_finally = true;
056: } else if (argv_length_mod == 2) {
057: try_catch = true;
058: }
059:
060: if (argv.length < 4 || (!try_catch && !try_catch_finally)) {
061: throw new TclNumArgsException(interp, 1, argv,
062: "script ?catch exception_pair script? ?finally script?");
063: }
064:
065: // Eval the script argument
066: eval(interp, argv[1]);
067:
068: // If an exception was raised while executing the script
069: // we need to iterate over the catch blocks to see if
070: // there is an exception listed that matches the one we
071: // caught. Check for errors in the format of the catch
072: // arguments as we go.
073:
074: if (exrec.exception_thrown) {
075: if (debug) {
076: System.out
077: .print("Exception thrown inside body block, type = ");
078: if (exrec.reflect_exception != null)
079: System.out.println("ReflectException");
080: else if (exrec.tcl_exception != null)
081: System.out.println("TclException");
082: else if (exrec.runtime_exception != null)
083: System.out.println("RuntimeException");
084: else
085: throw new TclRuntimeError(
086: "Java exception not found");
087: }
088:
089: int end_loop = argv.length;
090:
091: // If there is a finally block then do not check it
092: // in this catch block processing loop
093: if (try_catch_finally) {
094: end_loop -= 2;
095: }
096:
097: for (int i = 2; i < end_loop; i += 3) {
098: TclObject catch_clause = argv[i];
099: TclObject exception_pair = argv[i + 1];
100: TclObject exception_script = argv[i + 2];
101:
102: if (!catch_clause.toString().equals("catch")) {
103: throw new TclException(interp,
104: "invalid catch clause \""
105: + catch_clause.toString() + "\"");
106: }
107:
108: boolean exception_pair_problem = false;
109: TclObject type = null;
110: TclObject var = null;
111:
112: try {
113: if (TclList.getLength(interp, exception_pair) != 2) {
114: exception_pair_problem = true;
115: } else {
116: type = TclList.index(interp, exception_pair, 0);
117: var = TclList.index(interp, exception_pair, 1);
118:
119: // double check to make sure that these TclObjects
120: // are not lists (they can only have one element)
121:
122: if (TclList.getLength(interp, type) != 1
123: || TclList.getLength(interp, var) != 1) {
124: exception_pair_problem = true;
125: }
126: }
127: } catch (TclException e) {
128: exception_pair_problem = true;
129: }
130:
131: // if we could not parse the exception pair into a two
132: // element list then we raise an error
133:
134: if (exception_pair_problem) {
135: throw new TclException(interp,
136: "invalid exception_pair \""
137: + exception_pair.toString() + "\"");
138: }
139:
140: if (debug) {
141: System.out.println("type is \"" + type.toString()
142: + "\"");
143: System.out.println("var is \"" + var.toString()
144: + "\"");
145: System.out.println("script is \""
146: + exception_script.toString() + "\"");
147: }
148:
149: // now we need to "match" the name of the exception
150: // to the name of the "type" of the exception_pair
151: // we first try to match the long form of the name
152: // (ie "java.lang.Exception" matches "java.lang.Exception")
153: // but if that does not work then we try the short form
154: // (ie "java.lang.Exception" matches "Exception")
155: // We also watch for the special case where the identifier
156: // is "TclException". In this case we match any exception
157: // generated by a Tcl command. Any exception derived
158: // from TclException will also be matched by this case
159:
160: // this will hold the Class that was matched
161: // after the search is done
162: Class matched_class = null;
163:
164: String type_name = type.toString();
165: int type_len = type_name.length();
166:
167: if (debug) {
168: System.out.println("type_name is \"" + type_name
169: + "\"");
170: }
171:
172: Class ex_type;
173: if (exrec.reflect_exception != null) {
174: ex_type = exrec.reflect_exception.getThrowable()
175: .getClass();
176: } else if (exrec.tcl_exception != null) {
177: ex_type = exrec.tcl_exception.getClass();
178: } else if (exrec.runtime_exception != null) {
179: ex_type = exrec.runtime_exception.getClass();
180: } else {
181: throw new TclRuntimeError("Exception not found");
182: }
183:
184: if (debug) {
185: System.out.println("ex_type.getName() is "
186: + ex_type.getName());
187: }
188:
189: String ex_type_name;
190:
191: // Check for special case of TclInterruptedException.
192: // This runtime exception is defined only in Jacl.
193: // This exception can't be caught since it is
194: // raised to unwind the stack and terminate execution.
195:
196: if (type_name.equals("TclInterruptedException")
197: || type_name
198: .equals("tcl.lang.TclInterruptedException")) {
199: throw new TclException(interp,
200: "can't catch TclInterruptedException");
201: } else if ((exrec.runtime_exception != null)
202: && ex_type.getName().equals(
203: "tcl.lang.TclInterruptedException")) {
204:
205: // Scripts can't be allowed to catch TclInterruptedException
206: // since this is raised when interp is being taken down.
207:
208: if (debug) {
209: System.out
210: .println("skipped TclInterruptedException");
211: }
212:
213: break; // out of for loop
214:
215: // check for special case where the type name is
216: // "TclException" and we caught a TclException
217:
218: } else if (type_name.equals("TclException")
219: || (exrec.tcl_exception != null)) {
220:
221: if (type_name.equals("TclException")
222: && (exrec.tcl_exception != null)) {
223: matched_class = TclException.class;
224:
225: if (debug) {
226: System.out.println("match for \""
227: + matched_class.getName()
228: + "\" == \"" + type_name + "\"");
229: }
230: } else {
231: // If the exception name is "TclException" but the
232: // exception is not derived from TclException or
233: // if the type is derived from TclException but
234: // the type name is not "TclException", then
235: // move on to the next catch block
236:
237: if (debug) {
238: System.out
239: .println("skipping catch block because "
240: + "of TclException mismatch");
241: System.out.println("exception name = \""
242: + type_name + "\"");
243: System.out.println("caught TclException = "
244: + (exrec.tcl_exception != null));
245: }
246:
247: continue; //for loop
248: }
249: } else {
250:
251: while (ex_type != null) {
252: ex_type_name = ex_type.getName();
253:
254: if (debug) {
255: System.out.println("ex_type_name is \""
256: + ex_type_name + "\"");
257: }
258:
259: if (ex_type_name.equals(type_name)) {
260: matched_class = ex_type; //full name match
261: } else {
262: // try to match based on the "short" name of the class
263: // so "Exception" would match "java.lang.Exception"
264: // Watch out!
265: // so "Exception" does not match "java.lang.Exception2"
266:
267: int last = ex_type_name.lastIndexOf('.');
268:
269: /*
270: if (debug) {
271: System.out.println("last is " + last);
272: System.out.println((last != -1) + " && " +
273: ((ex_type_name.length() - (last+1)) == type_len));
274:
275: System.out.println("regionmatch is " +
276: ex_type_name.regionMatches(last+1,type_name,
277: 0,type_len));
278: }
279: */
280:
281: if ((last != -1)
282: && ((ex_type_name.length() - (last + 1)) == type_len)
283: && ex_type_name.regionMatches(
284: last + 1, type_name, 0,
285: type_len)) {
286:
287: matched_class = ex_type;
288: }
289: }
290:
291: if (matched_class != null) {
292: if (debug) {
293: System.out
294: .println("match for \""
295: + matched_class
296: .getName()
297: + "\" == \""
298: + type_name + "\"");
299: }
300: break; // end this while loop when match is found
301: }
302:
303: // java.lang.Throwable is the highest a catch can go
304: if (ex_type == Throwable.class) {
305: ex_type = null;
306: } else {
307: ex_type = ex_type.getSuperclass();
308: }
309:
310: } // end while loop
311:
312: } // end else
313:
314: // if we found a match on this catch block then eval that code
315: if (matched_class != null) {
316:
317: // Set the value of the variable named in the exception_pair
318: // I think it would be best to just write over the value
319: // of the exception variable. We really do not want to
320: // quit if it already defined and addind a "scope" to
321: // an exception handler seems to go against Tcl constructs
322:
323: // Case 1: The exception type matched is a TclException
324: // In this case the value of the exception variable is
325: // the error result of the command.
326:
327: if (matched_class == TclException.class) {
328:
329: if (debug) {
330: System.out
331: .println("TclException result getting saved in exception varaible");
332: }
333:
334: TclObject res = interp.getResult();
335: res.preserve();
336:
337: try {
338: interp.setVar(var.toString(), res, 0);
339: } catch (TclException e) {
340: //this should never happen
341: throw new TclRuntimeError(
342: "could not reflect or set exception variable");
343: }
344:
345: res.release();
346:
347: } else {
348:
349: // Case 2: The exception type matched is a Java exception
350: // that is not a subclass of TclException. In this case
351: // the value of the exception variable is the exception
352: // object reflection handle (java0x1 or something).
353:
354: if (debug) {
355: System.out
356: .println("JavaException result getting saved in exception varaible");
357: }
358:
359: Throwable t;
360:
361: if (exrec.reflect_exception != null) {
362: t = exrec.reflect_exception.getThrowable();
363: } else if (exrec.runtime_exception != null) {
364: t = exrec.runtime_exception;
365: } else {
366: throw new TclRuntimeError(
367: "Java exception not found");
368: }
369:
370: try {
371: interp.setVar(var.toString(), ReflectObject
372: .newInstance(interp, matched_class,
373: t), 0);
374: } catch (TclException e) {
375: // this should never happen
376: throw new TclRuntimeError(
377: "could not reflect or set exception variable");
378: }
379:
380: }
381:
382: // Now eval the exception handler script
383: // this will also reset exrec values.
384: eval(interp, exception_script);
385:
386: if (debug) {
387: if (exrec.exception_thrown)
388: System.out
389: .println("exception thrown by exception handler");
390: else
391: System.out
392: .println("no exception thrown by exception handler");
393: }
394:
395: break; // break out of the enclosing for loop
396: // this will stop catch block processing
397: }
398:
399: } // end for loop
400:
401: } // end if block
402:
403: // check and run the finally block if there is one
404:
405: if (try_catch_finally) {
406:
407: TclObject finally_clause = argv[argv.length - 2];
408: TclObject finally_script = argv[argv.length - 1];
409:
410: if (!finally_clause.toString().equals("finally")) {
411: throw new TclException(interp,
412: "invalid finally clause \""
413: + finally_clause.toString() + "\"");
414: }
415:
416: if (debug) {
417: System.out.println("finally script is \""
418: + finally_script.toString() + "\"");
419: }
420:
421: // the finally script might change the interpreter result
422: // so we need to save the current result so that it can
423: // restored after running the finally script
424:
425: TclObject res = interp.getResult();
426: res.preserve();
427: interp.resetResult();
428:
429: // evaluate the finally scipt and make sure that exceptions
430: // in the finally script are caught and then thrown at
431: // the very end of this method. Be sure to save the
432: // exception record value from the catch block evaluation
433: // so we can rethrow any raised exception if the catch
434: // block does not raise any exceptions.
435:
436: ExRecord tmp = exrec;
437: exrec = tmp_exrec;
438: tmp_exrec = tmp;
439:
440: eval(interp, finally_script);
441:
442: // If the finally block did not raise an error
443: // then reset the previous interpreter result
444: // and use the previous exception record.
445: // Also reset when the original exception was
446: // a TclInterruptedException since an exception
447: // in the finally block is ignored in that case.
448:
449: if ((exrec.exception_thrown == false)
450: || ((exrec.exception_thrown == true)
451: && (tmp_exrec.runtime_exception != null) && tmp_exrec.runtime_exception
452: .getClass().getName().equals(
453: "tcl.lang.TclInterruptedException"))) {
454:
455: if (debug) {
456: System.out
457: .println("resetting result and exception record");
458: }
459:
460: interp.setResult(res);
461: tmp = exrec;
462: exrec = tmp_exrec;
463: tmp_exrec = tmp;
464: }
465:
466: res.release();
467: }
468:
469: // If an exception was thrown in the catch block or the
470: // finally block, then we need to throw it again.
471:
472: if (exrec.exception_thrown) {
473: if (debug) {
474: System.out
475: .print("throwing end of try method exception, type = ");
476: if (exrec.reflect_exception != null)
477: System.out.println("ReflectException");
478: else if (exrec.tcl_exception != null)
479: System.out.println("TclException");
480: else if (exrec.runtime_exception != null)
481: System.out.println("RuntimeException");
482: else
483: throw new TclRuntimeError(
484: "Java exception not found");
485: }
486:
487: if (exrec.reflect_exception != null) {
488: if (debug) {
489: System.out.println("rethrowing ReflectException "
490: + exrec.reflect_exception);
491: }
492:
493: throw exrec.reflect_exception;
494: } else if (exrec.tcl_exception != null) {
495: if (debug) {
496: System.out.println("rethrowing TclException "
497: + exrec.tcl_exception);
498: }
499:
500: throw exrec.tcl_exception;
501: } else if (exrec.runtime_exception != null) {
502: if (debug) {
503: System.out.println("rethrowing RuntimeException "
504: + exrec.runtime_exception);
505: }
506:
507: throw exrec.runtime_exception;
508: } else {
509: throw new TclRuntimeError("Java exception not found");
510: }
511: }
512: }
513:
514: private static class ExRecord {
515: // Wrapper type, holds real exception thrown by some invoked method
516: ReflectException reflect_exception;
517:
518: // An actual TclException
519: TclException tcl_exception;
520:
521: // Some undeclared exception that was not raised in an invoked method.
522: RuntimeException runtime_exception;
523:
524: // True if one of the three exceptions types was caught
525: boolean exception_thrown;
526: }
527:
528: ExRecord exrec = new ExRecord();
529: ExRecord tmp_exrec = new ExRecord();
530:
531: private void eval(Interp interp, TclObject script) {
532: exrec.reflect_exception = null;
533: exrec.tcl_exception = null;
534: exrec.runtime_exception = null;
535: exrec.exception_thrown = false;
536:
537: try {
538: interp.eval(script, 0);
539: } catch (ReflectException e) {
540: if (debug) {
541: System.out.println("eval() caught ReflectException");
542: }
543: exrec.reflect_exception = e;
544: exrec.exception_thrown = true;
545: } catch (TclException e) {
546: if (debug) {
547: System.out.println("eval() caught TclException");
548: }
549: exrec.tcl_exception = e;
550: exrec.exception_thrown = true;
551:
552: // Exception might have been return, break, or continue
553: int code = e.getCompletionCode();
554:
555: if (code == TCL.RETURN) {
556: // If the caught exception is a regular return
557: // then no error really occured and we ignore it
558: exrec.exception_thrown = false;
559: }
560: // Process TCL.ERROR, TCL.BREAK, or TCL.CONTINUE
561: // like any other exception
562: } catch (RuntimeException e) {
563: if (debug) {
564: System.out.println("catching RuntimeException");
565: }
566:
567: exrec.runtime_exception = e;
568: exrec.exception_thrown = true;
569: }
570: }
571:
572: private static final boolean debug = false; // enables debug output
573:
574: } // end of JavaTryCmd class
|