001: /*=============================================================================
002: * Copyright Texas Instruments, Inc., 2001. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera;
020:
021: import java.util.*;
022: import java.io.*;
023:
024: import javax.swing.JOptionPane;
025:
026: import ti.exceptions.ProgrammingErrorException;
027: import ti.chimera.registry.*;
028:
029: import oscript.OscriptInterpreter;
030: import oscript.OscriptBuiltins;
031: import oscript.data.Value;
032: import oscript.data.Scope;
033: import oscript.data.BasicScope;
034: import oscript.data.JavaBridge;
035: import oscript.data.OJavaException;
036: import oscript.parser.ParseException;
037: import oscript.exceptions.PackagedScriptObjectException;
038:
039: /**
040: * This is where it all begins. The <code>Main</code> class instantiates the
041: * registry, the script interpreter, and then evaluates <i>bootstrap.os</i>,
042: * which is where everything else happens. The <i>bootstrap.os</i> essentially
043: * defines the application.
044: *
045: * @author Rob Clark
046: * @version 0.1
047: */
048: public class Main {
049: static {
050: /* need to set these properties as early as possible, before any other
051: * classes are loaded:
052: */
053: System.setProperty("oscript.error.handler",
054: "oscript.util.SwingErrorHandler");
055: System.setProperty("sun.awt.exception.handler",
056: "ti.chimera.Main$ChimeraThreadGroup");
057: }
058:
059: /**
060: * The scope to interprete script stuff in. It is a child of the global-
061: * scope. This is the "main" context, ie. the one that bootstrap.os is
062: * evaluated in, that plugins are loaded in, etc.
063: */
064: private Scope scope = new BasicScope(OscriptInterpreter
065: .getGlobalScope());
066:
067: /**
068: * The registry. Keeps track of registered plugins, and services
069: * provided by those plugins.
070: */
071: private static Registry registry; // XXX static is a hack so ChimeraThreadGroup can see it
072: private static Main main;
073:
074: /*=======================================================================*/
075: /**
076: * Where it all starts.
077: *
078: * @param args the command-line arguments
079: */
080: public static void main(final String[] args) throws Throwable {
081: Thread t = new Thread(new ChimeraThreadGroup(), "main") {
082:
083: public void run() {
084: try {
085: mainImpl(args);
086: } catch (Throwable e) {
087: getThreadGroup().uncaughtException(this , e);
088:
089: ti.chimera.service.Prompt prompt = null;
090: if (registry != null)
091: prompt = (ti.chimera.service.Prompt) (registry
092: .getService("prompt"));
093:
094: if (prompt != null)
095: prompt.showErrorMessage("Abnormal Termination");
096: else
097: JOptionPane.showMessageDialog(null, e
098: .toString(), "Error",
099: JOptionPane.ERROR_MESSAGE);
100:
101: Main.exit(1);
102: }
103: }
104:
105: };
106: t.start();
107: t.join();
108: }
109:
110: // BTW, this class has to be public static...
111: public static class ChimeraThreadGroup extends
112: java.lang.ThreadGroup {
113: public ChimeraThreadGroup() {
114: super ("chimera");
115: }
116:
117: // for uncaught exception in thread other than AWT:
118: public void uncaughtException(Thread t, Throwable e) {
119: if (debugLevel <= 1) {
120: System.err.println("Unhandled Error: ");
121: e.printStackTrace();
122: }
123:
124: /* ignore Error, or whatever gets thrown by fatalError
125: * (which should arguably be a FatalError or something
126: * special like that)
127: */
128: if (e instanceof Error)
129: return;
130:
131: ti.chimera.service.Talkback tb = null;
132:
133: if (registry != null)
134: tb = (ti.chimera.service.Talkback) (registry
135: .getService("talkback"));
136:
137: if (tb != null) {
138: try {
139: tb.uncaughtException(t, e);
140: return;
141: } catch (Throwable tt) {
142: System.err
143: .println("error in error handling code... this is fsck'd!");
144: tt.printStackTrace();
145: }
146: }
147:
148: if (main != null)
149: System.err.println("history: " + main.getHistory());
150:
151: System.err.println(" uncaught exception in thread: " + t);
152: e.printStackTrace();
153: }
154:
155: // for exception from AWT event thread:
156: public void handle(Throwable e) {
157: uncaughtException(null, e);
158: }
159: }
160:
161: private static void mainImpl(String[] args) throws Throwable {
162: // first seperate the args into the chimera args, and the script
163: // args... it is kinda ugly to have to do it here, but if we did
164: // it in the constructor, then all our super-classes would have to
165: // know about it too...
166: String thArgs[] = null;
167: String scriptArgs[] = null;
168:
169: for (int i = 0; i < args.length; i++) {
170: if ((args[i]).equals("--")) {
171: thArgs = new String[i];
172: for (int j = 0; j < i; j++)
173: thArgs[j] = args[j];
174: scriptArgs = new String[args.length - i - 1];
175: for (int j = i + 1, k = 0; j < args.length; j++, k++)
176: scriptArgs[k] = args[j];
177: break;
178: }
179: }
180: if (thArgs == null) {
181: thArgs = args;
182: scriptArgs = new String[0];
183: }
184:
185: new Main(thArgs, scriptArgs);
186: }
187:
188: /*=======================================================================*/
189: /**
190: * Class Constructor.
191: *
192: * @param args the command-line arguments
193: * @param sargs script args
194: */
195: public Main(String[] args, String[] sargs) throws Error {
196: Main.main = main;
197:
198: /* read properties:
199: *
200: * ti.chimera.log=true:
201: * enables a debug logger which prints out timestamped log
202: * messages to stdout
203: *
204: * ti.chimera.debugLevel=NUM:
205: * overrides the default value for the debug level
206: *
207: * NOTE: I suppose you could argue that I could use the registry
208: * for this stuff, but I figured minimizing what parts of the
209: * system we depend on for debug code is probably a good thing...
210: * debug code doesn't help much if it doesn't work when things
211: * are fubar'd
212: */
213: {
214: if ("true".equals(System.getProperty("ti.chimera.log"))) {
215: addLogger(new Logger() {
216: private long lastTime = System.currentTimeMillis();
217:
218: public void log(String msg) {
219: long t = System.currentTimeMillis();
220: System.out.println("[" + t + ":"
221: + (t - lastTime) + "] " + msg);
222: lastTime = System.currentTimeMillis();
223: }
224: });
225: }
226:
227: String str = System.getProperty("ti.chimera.debugLevel");
228: if (str != null) {
229: try {
230: debugLevel = Integer.decode(str).intValue();
231: } catch (NumberFormatException e) {
232: }
233: }
234: }
235:
236: // initialize the interpreter:
237: scope.createMember("main", 0).opAssign(
238: JavaBridge.convertToScriptObject(this ));
239: try {
240: OscriptInterpreter.eval("var interpreter = main;", scope);
241: } catch (ParseException e) {
242: throw new ProgrammingErrorException(e);
243: }
244:
245: // initialize the registry:
246: registry = new Registry(this );
247:
248: // export command-line args:
249: scope.createMember("args", 0).opAssign(
250: JavaBridge.convertToScriptObject(sargs));
251:
252: // initialize other stuff:
253: Dock.init(this );
254:
255: // XXX
256: FileChooser.setMain(this );
257: // maybe OptionPane.main = this; too?
258:
259: // load the bootstrap code:
260: try {
261: if (!load("bootstrap.os"))
262: fatalError("could not load bootstrap.os");
263: if (!load("load-external-plugins.os"))
264: fatalError("could not load plugin.os");
265: } catch (ParseException e) {
266: throw new ProgrammingErrorException(e);
267: }
268:
269: try {
270: load("user.os");
271: } catch (ParseException e) {
272: fatalError("user.os contains errors: " + e.getMessage());
273: }
274:
275: // and then load an files specified on command-line:
276: for (int i = 0; i < args.length; i++) {
277: if (args[i] != null) {
278: try {
279: if (load(args[i]))
280: args[i] = null;
281: } catch (ParseException e) {
282: fatalError("bad argument: " + args[i] + ": "
283: + e.getMessage());
284: }
285: }
286: }
287: }
288:
289: /**
290: * Utility to load a script file
291: *
292: * @param path the file to load
293: * @return <code>true</code> if the file exists and is readable
294: * @throws ParseException if the file exists but contained parse errors
295: */
296: private boolean load(String path) throws ParseException {
297: try {
298: oscript.fs.AbstractFile file = OscriptInterpreter.resolve(
299: path, false);
300:
301: if (!file.canRead())
302: return false;
303:
304: if (!"os".equals(file.getExtension()))
305: return false;
306:
307: OscriptInterpreter.eval(file, scope);
308: return true;
309: } catch (IOException e) {
310: throw new ProgrammingErrorException(e);
311: }
312: }
313:
314: /*=======================================================================*/
315: /**
316: * Called for fatal errors, for example during startup. Displays error
317: * message and then throws {@link Error} which should terminate the
318: * thread.
319: *
320: * @param str the error message
321: * @throws Error
322: */
323: public void fatalError(String str) throws Error {
324: ti.chimera.service.Prompt p = (ti.chimera.service.Prompt) (registry
325: .getService("prompt"));
326:
327: if (p != null)
328: p.showErrorMessage(str);
329: else
330: System.err.println(str);
331:
332: throw new Error(str);
333: }
334:
335: /*=======================================================================*/
336: /**
337: * Set the input stream.
338: *
339: * @param in the new input reader
340: */
341: public void setIn(Reader in) {
342: if (in instanceof BufferedReader)
343: this .in = (BufferedReader) in;
344: else
345: this .in = new BufferedReader(in);
346: }
347:
348: /*=======================================================================*/
349: /**
350: * Set the output stream.
351: *
352: * @param out the new output writer
353: */
354: public void setOut(Writer out) {
355: if (out instanceof PrintWriter)
356: this .out = (PrintWriter) out;
357: else
358: this .out = new PrintWriter(out);
359: }
360:
361: /*=======================================================================*/
362: /**
363: * Set the error output stream.
364: *
365: * @param err the new error output writer
366: */
367: public void setErr(Writer err) {
368: if (err instanceof PrintWriter)
369: this .err = (PrintWriter) err;
370: else
371: this .err = new PrintWriter(err);
372: }
373:
374: /*=======================================================================*/
375: /**
376: * Get the stdout stream.
377: *
378: * @return the stdout output stream used by the test-harness
379: */
380: public PrintWriter getOut() {
381: return out;
382: }
383:
384: /*=======================================================================*/
385: /**
386: * Get the stderr stream.
387: *
388: * @return the stderr output stream used by the test-harness
389: */
390: public PrintWriter getErr() {
391: return err;
392: }
393:
394: /*=======================================================================*/
395: /**
396: * Get the stdin stream.
397: *
398: * @return the stdin input stream used by the test-harness
399: */
400: public BufferedReader getIn() {
401: return in;
402: }
403:
404: /*=======================================================================*/
405: /**
406: * Get the {@link WindowManager}.
407: *
408: * @return the window manager
409: */
410: public final ti.chimera.service.WindowManager getWindowManager() {
411: return (ti.chimera.service.WindowManager) (registry
412: .getService("window manager"));
413: }
414:
415: /*=======================================================================*/
416: /**
417: * Get the {@link Registry}.
418: *
419: * @return the registry
420: */
421: public Registry getRegistry() {
422: return registry;
423: }
424:
425: /*=======================================================================*/
426: /**
427: * Cause the system to exit, running at-exit-runnables. Note that
428: * while close runnables will still run if you exit the VM by calling
429: * {@link System#exit}, doing so may cause the VM to hang on some plat-
430: * forms, notably windows. (The cause appears to be bad interactions
431: * between some close runnables and swing from the context of the
432: * shutdown-hook thread.)
433: *
434: * @param status the exit status
435: */
436: public static void exit(int status) {
437: OscriptBuiltins.exit(status);
438: }
439:
440: /*=======================================================================*/
441: /**
442: * Add some code to run at shutdown time. See {@link java.lang.Runtime#addShutdownHook}
443: * for a description of what you can and can't do from a shutdown hook.
444: * Unlike <code>addShutdownHook</code>, this method lets you add a
445: * shutdown hook from the context of a running shutdown hook.
446: *
447: * @param hook the hook to run at shutdown
448: */
449: public void atExit(Runnable hook) {
450: OscriptBuiltins.atExit(hook, Integer.MAX_VALUE);
451: }
452:
453: /*=======================================================================*/
454: /**
455: * Store some object in a persistant manner. The object must implement
456: * the Serializable interface.
457: *
458: * @param key the key
459: * @param obj the object to store
460: * @see #restore
461: */
462: public void store(String key, Object obj) {
463: getNode(key).setValue(obj);
464: }
465:
466: /*=======================================================================*/
467: /**
468: * Retrieve some previously stored object.
469: *
470: * @param key the key
471: * @return the previously stored object, or <code>null</code> if none
472: * @see #store
473: */
474: public Object restore(String key) {
475: return getNode(key).getValue();
476: }
477:
478: /**
479: * Get the node used to store the value with the specified key. This is
480: * used for the legacy interfaces {@link #store} and {@link #restore}.
481: */
482: private Node getNode(String key) {
483: try {
484: String path = "/Legacy/" + key;
485:
486: if (!registry.exists(path)) {
487: /* at exit, we need to set the value of all legacy nodes, to
488: * take into account potential side effects if the value isn't
489: * immutable
490: */
491: if (nodeList == null) {
492: nodeList = new LinkedList();
493: atExit(new Runnable() {
494:
495: public void run() {
496: for (Iterator itr = nodeList.iterator(); itr
497: .hasNext();) {
498: Node node = (Node) (itr.next());
499: node.setValue(node.getValue());
500: }
501: }
502:
503: });
504: }
505:
506: Node node = new PersistentNode(null, null, "");
507: registry.link(node, path);
508:
509: nodeList.add(node);
510: }
511:
512: return registry.resolve(path);
513: } catch (RegistryException e) {
514: throw new ProgrammingErrorException(e);
515: }
516: }
517:
518: private LinkedList nodeList;
519:
520: /*=======================================================================*/
521: /*=======================================================================*/
522: /*=======================================================================*/
523:
524: /**
525: * A logger's <code>log</code> method is called with log messages, such
526: * as loading or starting a plugin, etc. See {@link #log}.
527: */
528: public interface Logger {
529: /**
530: * Called with log messages. This should probably not (directly or
531: * indirectly) call {@link #addLogger} or {@link #removeLogger}
532: */
533: public void log(String msg);
534: }
535:
536: private LinkedList loggerList = new LinkedList();
537:
538: /**
539: * Add a logger.
540: */
541: public void addLogger(Logger logger) {
542: synchronized (loggerList) {
543: loggerList.add(logger);
544: }
545: }
546:
547: /**
548: * Remove a logger.
549: */
550: public void removeLogger(Logger logger) {
551: synchronized (loggerList) {
552: loggerList.remove(logger);
553: }
554: }
555:
556: /**
557: * Log a message. This is called by various parts of the chimera
558: * infrastructure, for example when plugins are loaded or view's are
559: * created.
560: *
561: * @param msg the informational message to log
562: */
563: public void log(String msg) {
564: synchronized (loggerList) {
565: for (Iterator itr = loggerList.iterator(); itr.hasNext();)
566: ((Logger) (itr.next())).log(msg);
567: }
568: }
569:
570: /*=======================================================================*/
571: /*=======================================================================*/
572: /*=======================================================================*/
573:
574: /* Eventually this stuff should move...
575: */
576: private static int debugLevel = 2;
577:
578: public void setDebugLevel(int debugLevel) // XXX for now
579: {
580: this .debugLevel = debugLevel;
581: }
582:
583: private BufferedReader in = new BufferedReader(
584: new InputStreamReader(System.in));
585: private PrintWriter out = new PrintWriter(System.out);
586: private PrintWriter err = new PrintWriter(System.err);
587:
588: private String[] history = new String[500];
589: private int historyIdx = 0;
590:
591: private synchronized void recordHistory(String msg) {
592: history[historyIdx] = Thread.currentThread().getName() + ":"
593: + System.currentTimeMillis() + ": " + msg;
594: historyIdx = (historyIdx + 1) % history.length;
595: }
596:
597: public synchronized String getHistory() {
598: StringBuffer sb = new StringBuffer();
599: for (int i = 0; i < history.length; i++) {
600: int idx = (i + historyIdx) % history.length;
601: if (history[idx] != null)
602: sb.append(history[idx] + "\n");
603: }
604: return sb.toString();
605: }
606:
607: public void error(String msg) {
608: msg = "error: " + msg;
609: recordHistory(msg);
610: err.println(msg);
611: err.flush();
612: }
613:
614: public void error(String name, String msg) {
615: msg = "error: " + name + ": " + msg;
616: recordHistory(msg);
617: out.println(msg);
618: out.flush();
619: }
620:
621: public void warning(String msg) {
622: msg = "warning: " + msg;
623: recordHistory(msg);
624: out.println(msg);
625: out.flush();
626: }
627:
628: public void warning(String name, String msg) {
629: msg = "warning: " + name + ": " + msg;
630: recordHistory(msg);
631: out.println(msg);
632: out.flush();
633: }
634:
635: public void message(String msg) {
636: recordHistory(msg);
637: out.println(msg);
638: out.flush();
639: }
640:
641: public void debug(int level, String msg) {
642: msg = "debug: " + msg;
643: recordHistory(msg);
644: if (level >= debugLevel)
645: message(msg);
646: }
647:
648: /*=======================================================================*/
649: /*=======================================================================*/
650: /*=======================================================================*/
651:
652: }
653:
654: /*
655: * Local Variables:
656: * tab-width: 2
657: * indent-tabs-mode: nil
658: * mode: java
659: * c-indentation-style: java
660: * c-basic-offset: 2
661: * eval: (c-set-offset 'substatement-open '0)
662: * eval: (c-set-offset 'case-label '+)
663: * eval: (c-set-offset 'inclass '+)
664: * eval: (c-set-offset 'inline-open '0)
665: * End:
666: */
|