001: /*
002: * Copyright (c) 2006 Mo DeJong
003: *
004: * See the file "license.amd" for information on usage and
005: * redistribution of this file, and for a DISCLAIMER OF ALL
006: * WARRANTIES.
007: *
008: * RCS: @(#) $Id: TJCThread.java,v 1.3 2006/08/04 23:11:14 mdejong Exp $
009: *
010: */
011:
012: // TJC Runtime Compiler main thread. This thread is accessed
013: // by any active interp that wishes to compile a Tcl method
014: // into Java class files. Generating Java code and then
015: // compiling it to Tcl code can take some time, so it is
016: // done in a separate thread and loaded into the requesting
017: // Interp when ready.
018: package tcl.lang;
019:
020: import java.util.HashMap;
021: import java.util.Vector;
022: import java.util.ArrayList;
023: import java.util.StringTokenizer;
024:
025: public class TJCThread implements Runnable {
026: public static final int STATUS_OK = 0;
027: public static final int STATUS_ERROR = 1;
028:
029: private static boolean debug = false;
030:
031: private static String debugSetup = "";
032:
033: private static String driver = null;
034:
035: // The TJCThread instance, it is possible that
036: // the thread could be terminated and then
037: // started again which would replace this instance.
038:
039: private static TJCThread tinstance = null;
040: private static Thread tmain = null;
041:
042: // Set to true when the tmain thread should
043: // terminate itself.
044:
045: private boolean terminate_request = false;
046: private boolean terminated = false;
047:
048: // Set to true when the thread is ready to process
049: // events. This may not be set until the thread has started
050: // up and possibly processed any events that were initially
051: // added to the queue.
052:
053: private boolean ready = false;
054:
055: // Thread safe queue of events to process.
056:
057: private static Vector queue = new Vector();
058:
059: // Jacl interp used to process events. The
060: // interp is created in the other thread.
061: // it should never be accessed from the
062: // caller thread.
063:
064: private Interp interp = null;
065:
066: // Event record. This is a buffer of line
067: // oriented data that indicates what events
068: // are being processed and in what order.
069:
070: private static StringBuffer eventLog = null;
071:
072: // Invoke the compiled() method when the
073: // results of a compileJavaSource() or
074: // compileTclSource() invocation are ready.
075:
076: public static interface CompiledClassReady {
077: public void compiled(final String geninfo, // Name that identifies the source
078: // for generated Java code. This
079: // "" when compiling a Java file.
080: final String jfilename, // File name for Java source,
081: // like "Test.java".
082: final String jsrcode, // Java source that was compiled.
083: final ArrayList cnames, // List of compiled class names.
084: final ArrayList cdata, // List of compiled class data as byte[].
085: final int status, final String msg);
086: }
087:
088: // Invoked to indicate that a given Java
089: // source file should be compiled.
090:
091: public static synchronized void compileJavaSource(String filename,
092: String source, CompiledClassReady callback) {
093: if (callback == null) {
094: throw new NullPointerException("callback");
095: }
096:
097: if (eventLog != null) {
098: eventLog.append("compileJavaSource " + filename + "\n");
099: }
100: if (debug) {
101: System.out.println("compileJavaSource " + filename);
102: }
103:
104: // Test 1, check that the callback works without invoking
105: // the notify to process in the other thread.
106:
107: if (filename.equals("__FakeTest1.java")) {
108: if (eventLog != null) {
109: eventLog.append("Fake Java Test 1 processed\n");
110: }
111: if (debug) {
112: System.out.println("Fake Java Test 1 processed");
113: }
114:
115: ArrayList class_names = new ArrayList();
116: class_names.add("__FakeTest1");
117:
118: byte[] bytes = { (byte) 'F', (byte) 'A', (byte) 'K',
119: (byte) 'E' };
120:
121: ArrayList class_data = new ArrayList();
122: class_data.add(bytes);
123:
124: callback.compiled("", filename, source, class_names,
125: class_data, 0, "");
126: return;
127: }
128:
129: // Add event to thread safe queue
130:
131: Vector event = new Vector();
132: event.addElement("JAVA");
133: event.addElement(filename);
134: event.addElement(source);
135: event.addElement(callback);
136:
137: queue.addElement(event);
138:
139: synchronized (tinstance) {
140: if (eventLog != null) {
141: eventLog.append("notify in compileJavaSource\n");
142: }
143: if (debug) {
144: System.out.println("notify in compileJavaSource");
145: }
146:
147: tinstance.notify(); // wake up other thread
148: }
149: }
150:
151: // Invoked to indicate that a given Tcl procedure
152: // should be compiled into a class file.
153:
154: public static synchronized void compileTclSource(String filename,
155: String proc_source, CompiledClassReady callback) {
156: if (eventLog != null) {
157: eventLog.append("compileTclSource " + filename + "\n");
158: }
159: if (debug) {
160: System.out.println("compileTclSource " + filename);
161: System.out.println("Tcl proc is:\n" + proc_source);
162: }
163:
164: // Add event to thread safe queue
165:
166: Vector event = new Vector();
167: event.addElement("TCL");
168: event.addElement(filename);
169: event.addElement(proc_source);
170: event.addElement(callback);
171:
172: queue.addElement(event);
173:
174: synchronized (tinstance) {
175: if (eventLog != null) {
176: eventLog.append("notify in compileTclSource\n");
177: }
178: if (debug) {
179: System.out.println("notify in compileTclSource");
180: }
181:
182: tinstance.notify(); // wake up other thread
183: }
184: }
185:
186: // Invoked when the user wants the main thread to die.
187:
188: public static synchronized void terminateThread() {
189: if (eventLog != null) {
190: eventLog.append("terminateThread\n");
191: }
192: if (debug) {
193: System.out.println("terminateThread");
194:
195: if (queue.size() != 0) {
196: System.out
197: .println("terminateThread with unprocessed events");
198: } else {
199: System.out
200: .println("terminateThread with no unprocessed events");
201: }
202: }
203:
204: if (tinstance.terminated) {
205: if (eventLog != null) {
206: eventLog.append("thread already terminated\n");
207: }
208: if (debug) {
209: System.out.println("thread already terminated");
210: }
211:
212: return;
213: }
214:
215: tinstance.terminate_request = true;
216:
217: synchronized (tinstance) {
218: if (eventLog != null) {
219: eventLog.append("notify in terminateThread\n");
220: }
221: if (debug) {
222: System.out.println("notify in terminateThread");
223: }
224: tinstance.notify(); // wake up other thread
225: }
226: }
227:
228: // Invoked to indicate how debug options for
229: // this module should be setup. This method
230: // must be invoked before compilation begins
231: // since there options are processed when the
232: // tmain thread starts.
233:
234: public static synchronized void debugSetup(String dbgstr) {
235: debugSetup = dbgstr;
236:
237: eventLog = null;
238:
239: // Search for known debug tokens
240:
241: StringTokenizer st = new StringTokenizer(debugSetup);
242: while (st.hasMoreTokens()) {
243: String token = (String) st.nextToken();
244: if (token.equals("-debug")) {
245: debug = true;
246: } else if (token.equals("-event")) {
247: eventLog = new StringBuffer(128);
248: } else if (token.equals("-pizza")) {
249: driver = "pizza";
250: } else if (token.equals("-janino")) {
251: driver = "janino";
252: }
253: }
254: }
255:
256: // Invoked by test code to see if the thread
257: // started up and is ready to process events.
258: // Events can be queued up before the thread
259: // is ready to start processing them.
260:
261: public static synchronized boolean isThreadReady() {
262: return tinstance.ready;
263: }
264:
265: // Invoked to query the event log, if no event log
266: // was enabled then this method will return null.
267:
268: public static synchronized String getEventLog() {
269: if (eventLog == null) {
270: return null;
271: }
272: return eventLog.toString();
273: }
274:
275: // Invoked to explicitly start up the main
276: // thread if it is not already running.
277: // Test code might want to do this without
278: // having a specific file to compile.
279:
280: public static synchronized void startThread() {
281: if (tinstance != null && !tinstance.terminate_request) {
282: // Thread already started, it is currently running,
283: // and a terminate request is not currently pending.
284:
285: if (debug) {
286: System.out.println("thread currently running");
287: }
288:
289: return;
290: }
291:
292: if (debug) {
293: System.out.println("creating new Thread()");
294: }
295:
296: tinstance = new TJCThread();
297: tmain = new Thread(tinstance);
298: tmain.setDaemon(true);
299:
300: // Drop Priority of compile thread below that
301: // of the calling thread.
302:
303: int priority = tmain.getPriority();
304: if (priority > Thread.MIN_PRIORITY) {
305: priority--;
306: tmain.setPriority(priority);
307: }
308:
309: tmain.setName("TJCThread service");
310:
311: if (eventLog != null) {
312: eventLog.append("thread create\n");
313: }
314: if (debug) {
315: System.out.println("thread create");
316: }
317: tmain.start();
318:
319: // Don't wait around for the other thread to
320: // be initialized and start running.
321: }
322:
323: public void run() {
324: if (eventLog != null) {
325: eventLog.append("thread start\n");
326: }
327: if (debug) {
328: System.out.println("thread start");
329: }
330:
331: // Loop forever waiting for next request to be
332: // added to the queue.
333:
334: while (true) {
335: if (terminate_request) {
336: break;
337: }
338:
339: // Process all events in the queue, one at
340: // a time starting with the first one. Be
341: // sure to do this in a thread safe way
342: // in case another thread is adding elements
343: // to the queue.
344:
345: while (true) {
346: Vector event;
347: synchronized (queue) {
348: if (queue.size() == 0) {
349: break;
350: }
351: event = (Vector) queue.remove(0);
352: if (debug) {
353: System.out.println("removed event, there are "
354: + queue.size() + " events left");
355: }
356: }
357: // Process the event after removing it
358: // from the queue and releasing the
359: // monitor.
360: processEvent(event);
361:
362: // Bail out if thread should die
363: if (terminate_request) {
364: break;
365: }
366: }
367:
368: // Bail out if thread should die
369: if (terminate_request) {
370: break;
371: }
372:
373: try {
374: synchronized (this ) {
375: if (eventLog != null) {
376: eventLog.append("thread wait\n");
377: }
378: if (debug) {
379: System.out.println("thread wait");
380: }
381:
382: ready = true;
383: this .wait(); // wait for next service request
384: ready = false;
385: }
386: } catch (InterruptedException e) {
387: e.printStackTrace();
388: }
389: if (terminate_request) {
390: break;
391: }
392:
393: if (eventLog != null) {
394: eventLog.append("thread wakeup\n");
395: }
396: if (debug) {
397: System.out.println("thread wakeup");
398: }
399: }
400:
401: if (terminate_request) {
402: if (eventLog != null) {
403: eventLog.append("thread terminate request\n");
404: }
405: if (debug) {
406: System.out.println("thread terminate request");
407: }
408: }
409:
410: // When execution reaches the end of run() the
411: // thread will terminate itself.
412:
413: if (eventLog != null) {
414: eventLog.append("thread terminated\n");
415: }
416: if (debug) {
417: System.out.println("thread terminated");
418: }
419: terminated = true;
420:
421: // Dispose of Jacl interp before leaving this
422: // thread. It is critical that the Jacl interp
423: // be disposed of in the same thread it was
424: // created in.
425:
426: if (interp != null) {
427: if (debug) {
428: System.out.println("Invoking interp.dispose()");
429: }
430:
431: interp.dispose();
432: interp = null;
433: }
434: }
435:
436: // Invoked once for each event Vector processed in the
437: // TJC compile thread.
438:
439: private void processEvent(Vector event) {
440: int len = event.size();
441:
442: if (eventLog != null) {
443: eventLog.append("process event\n");
444: }
445: if (debug) {
446: System.out.println("PROCESS QUEUE EVENT: " + event);
447: }
448:
449: if (len != 4) {
450: throw new RuntimeException(
451: "unexpected number of event args: " + len);
452: }
453:
454: String type = (String) event.elementAt(0);
455: String filename = (String) event.elementAt(1);
456: String source = (String) event.elementAt(2);
457: CompiledClassReady callback = (CompiledClassReady) event
458: .elementAt(3);
459:
460: // Test 2, check that the callback works from the
461: // other threads.
462:
463: if (filename.equals("__FakeTest2.java")) {
464: if (eventLog != null) {
465: eventLog.append("Fake Java Test 2 processed\n");
466: }
467: if (debug) {
468: System.out.println("Fake Java Test 2 processed");
469: }
470:
471: ArrayList class_names = new ArrayList();
472: class_names.add("__FakeTest2");
473:
474: byte[] bytes = { (byte) 'F', (byte) 'A', (byte) 'K',
475: (byte) 'E' };
476:
477: ArrayList class_data = new ArrayList();
478: class_data.add(bytes);
479:
480: callback.compiled("", filename, source, class_names,
481: class_data, 0, "");
482: return;
483: }
484:
485: try {
486: // Init interp if needed
487: if (interp == null) {
488: if (eventLog != null) {
489: eventLog.append("Interp() and init\n");
490: }
491: if (debug) {
492: System.out.println("Interp() and init");
493: }
494: interp = new Interp();
495: if (driver != null) {
496: interp.setVar("JAVA_DRIVER", null, driver, 0);
497: }
498: interp
499: .eval("source resource:/tjc/library/tjcthread.tcl");
500: }
501:
502: if (type.equals("JAVA")) {
503: processJavaSource(filename, source, callback);
504: } else if (type.equals("TCL")) {
505: processTclSource(filename, source, callback);
506: } else {
507: throw new TclException(interp, "unknown type " + type);
508: }
509: } catch (TclException te) {
510: StringBuffer msg = new StringBuffer(128);
511: msg.append("TclException: ");
512:
513: TclObject ei;
514: try {
515: ei = interp.getVar("errorInfo", null, TCL.GLOBAL_ONLY);
516: msg.append(ei.toString());
517: } catch (TclException e) {
518: msg.append(te.getMessage());
519: }
520:
521: if (eventLog != null) {
522: eventLog.append(msg.toString());
523: eventLog.append('\n');
524: }
525: if (debug) {
526: System.out.println(msg.toString());
527: }
528:
529: // Invoke callback to report error
530:
531: callback.compiled("", filename, source, null, null, 1, msg
532: .toString());
533: }
534: }
535:
536: // Compile a Java source file into bytecode and invoke the callback.
537:
538: private void processJavaSource(String filename, String source,
539: CompiledClassReady callback) throws TclException {
540: if (eventLog != null) {
541: eventLog.append("process java source: " + filename + "\n");
542: }
543: if (debug) {
544: System.out.println("processJavaSource " + filename);
545: }
546:
547: TclObject cmd_obj = TclString.newInstance("processJavaSource");
548: TclObject filename_obj = TclString.newInstance(filename);
549: TclObject source_obj = TclString.newInstance(source);
550:
551: TclObject list = TclList.newInstance();
552: TclList.append(interp, list, cmd_obj);
553: TclList.append(interp, list, filename_obj);
554: TclList.append(interp, list, source_obj);
555:
556: interp.eval(list, TCL.EVAL_GLOBAL);
557:
558: // Invoke processJavaSource, this method should set the interp
559: // result to a flat list of class names and reflected byte[]
560: // objects.
561:
562: TclObject result = interp.getResult();
563: if (debug) {
564: System.out.println("processJavaSource interp result was: "
565: + result.toString());
566: }
567:
568: ArrayList class_names = new ArrayList();
569: ArrayList class_data = new ArrayList();
570:
571: final int len = TclList.getLength(interp, result);
572: for (int i = 0; i < len; i += 2) {
573: TclObject name = TclList.index(interp, result, i);
574: TclObject reflectObj = TclList.index(interp, result, i + 1);
575:
576: class_names.add((String) name.toString());
577:
578: Object obj = ReflectObject.get(interp, reflectObj);
579: if (!(obj instanceof byte[])) {
580: throw new TclException(interp, "obj \"" + obj
581: + "\" is not a byte[] instance");
582: }
583:
584: class_data.add((byte[]) obj);
585: }
586: interp.resetResult();
587:
588: callback.compiled("", filename, source, class_names,
589: class_data, 0, "");
590: }
591:
592: // Compile a Tcl source file into bytecode and invoke the callback.
593:
594: private void processTclSource(String filename, String source,
595: CompiledClassReady callback) throws TclException {
596: TclObject cmd_obj, filename_obj, source_obj, list;
597:
598: if (eventLog != null) {
599: eventLog.append("process tcl source: " + filename + "\n");
600: }
601: if (debug) {
602: System.out.println("processTclSource " + filename);
603: }
604:
605: // Invoke the processTclSource Tcl command to generate Java
606: // code from the Tcl proc declaration.
607:
608: cmd_obj = TclString.newInstance("processTclSource");
609: filename_obj = TclString.newInstance(filename);
610: source_obj = TclString.newInstance(source);
611:
612: list = TclList.newInstance();
613: TclList.append(interp, list, cmd_obj);
614: TclList.append(interp, list, filename_obj);
615: TclList.append(interp, list, source_obj);
616:
617: interp.eval(list, TCL.EVAL_GLOBAL);
618:
619: // The result object from processTclSource is a list
620: // containing the generated proc name and the generated
621: // Java source code. Pass the Java source code to
622: // the processJavaSource Tcl command.
623:
624: TclObject processTclSourceResult = interp.getResult();
625: String proc_name = TclList.index(interp,
626: processTclSourceResult, 0).toString();
627: String java_source = TclList.index(interp,
628: processTclSourceResult, 1).toString();
629:
630: if (debug) {
631: System.out.println("processTclSource interp result was:\n"
632: + java_source);
633: }
634:
635: cmd_obj = TclString.newInstance("processJavaSource");
636: filename_obj = TclString.newInstance(filename);
637: source_obj = TclString.newInstance(java_source);
638:
639: list = TclList.newInstance();
640: TclList.append(interp, list, cmd_obj);
641: TclList.append(interp, list, filename_obj);
642: TclList.append(interp, list, source_obj);
643:
644: interp.eval(list, TCL.EVAL_GLOBAL);
645:
646: // The processJavaSource command will set the interp
647: // result to a flat list of class names and reflected
648: // byte[] objects.
649:
650: TclObject result = interp.getResult();
651: if (debug) {
652: System.out.println("processTclSource interp result was: "
653: + result.toString());
654: }
655:
656: ArrayList class_names = new ArrayList();
657: ArrayList class_data = new ArrayList();
658:
659: final int len = TclList.getLength(interp, result);
660: for (int i = 0; i < len; i += 2) {
661: TclObject name = TclList.index(interp, result, i);
662: TclObject reflectObj = TclList.index(interp, result, i + 1);
663:
664: class_names.add((String) name.toString());
665:
666: Object obj = ReflectObject.get(interp, reflectObj);
667: if (!(obj instanceof byte[])) {
668: throw new TclException(interp, "obj \"" + obj
669: + "\" is not a byte[] instance");
670: }
671:
672: class_data.add((byte[]) obj);
673: }
674: interp.resetResult();
675:
676: callback.compiled(proc_name, filename, java_source,
677: class_names, class_data, 0, "");
678: }
679:
680: // Invoked when TJCThread is garbage collected.
681:
682: protected void finalize() throws Throwable {
683: if (debug) {
684: System.out.println("TJCThread finalized");
685: }
686:
687: super.finalize();
688: }
689:
690: }
|