001: /*=============================================================================
002: * Copyright Texas Instruments 2000-2004. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2 of the License, or (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 GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * $ProjectHeader: OSCRIPT 0.155 Fri, 20 Dec 2002 18:34:22 -0800 rclark $
019: */
020:
021: package oscript;
022:
023: import oscript.util.StackFrame;
024: import oscript.fs.*;
025: import oscript.data.*;
026: import oscript.exceptions.*;
027: import oscript.syntaxtree.*;
028: import oscript.interpreter.*;
029: import oscript.compiler.*;
030: import oscript.parser.*;
031:
032: import java.io.*;
033: import java.util.*;
034: import java.net.URL;
035: import java.net.URLClassLoader;
036:
037: /**
038: * The toplevel and main interface for the interpreter. There can only be
039: * one instance of this object. The scope object can be used to create
040: * logically isolated interpreter "instances".
041: * <p>
042: * This does need some cleanup, and perhaps should be a front-end for other
043: * stuff someone embedding this in an application might want, such as
044: * creating a new scope...
045: * <p>
046: * Description of properties that are interesting:
047: * <table>
048: * <tr>
049: * <th>property</th>
050: * <th>description</th>
051: * <th>regular default</th>
052: * <th>webstart default</th>
053: * </tr>
054: * <tr>
055: * <td>oscript.cache.path</td>
056: * <td>where the cache directory is created</th>
057: * <td>$CWD/.cache</td>
058: * <td>$HOME/.cache</td>
059: * </tr>
060: * <tr>
061: * <td>oscript.cwd</td>
062: * <td>the current working directory</td>
063: * <td>$CWD</td>
064: * <td>$HOME/Desktop || $HOME</td>
065: * </tr>
066: * </table>
067: *
068: * @author Rob Clark (rob@ti.com)
069: */
070: public class OscriptInterpreter {
071:
072: private static boolean useCompiler = true;
073: private static Scope globalScope = null;
074: private static LinkedList parserList = new LinkedList();
075: private static LinkedList scriptPathList = new LinkedList();
076: private static String[] scriptPaths; // cached version of scriptPathList
077: private static Hashtable nodeEvaluatorCacheTable;
078: private static ClassLoader classLoader;
079:
080: private static final boolean TRACE_EVAL = "true".equals(System
081: .getProperty("oscript.trace.eval", "false"));
082:
083: public final static NodeEvaluator EMPTY_EXPR_LIST_EVALUATOR;
084:
085: /**
086: * the CACHE_VERSION should change any time a change is made in the interpreter
087: * that could cause incompatibility with previously serialized cache entries. It
088: * is made part of the path to serialized cache entry, so entries serialized with
089: * different cache versions will not conflict.
090: */
091: public static final String CACHE_VERSION = 23 + "_"
092: + System.getProperty("java.version").replace('.', '_');
093:
094: private static final boolean webstart = "true".equals(System
095: .getProperty("oscript.webstart"));
096: private static final String cachePath = System.getProperty(
097: "oscript.cache.path", webstart ? System
098: .getProperty("user.home")
099: + "/.cache" : ".cache");
100:
101: static {
102: System.setProperty("oscript.cache.path", (new java.io.File(
103: cachePath)).getAbsolutePath());
104: }
105:
106: private static ClassLoader cacheClassLoader;
107:
108: public static Class loadClassFromCache(String className)
109: throws ClassNotFoundException {
110: return CompilerClassLoader.forName(className, true,
111: cacheClassLoader);
112: }
113:
114: // XXX clean this up! It should have some way to register factories, etc...
115: private static NodeEvaluatorFactory nodeCompiler;
116: private static NodeEvaluatorFactory nodeInterpreter;
117:
118: public static Value DEFAULT_ARRAY_SORT_COMPARISION_FXN;
119:
120: static {
121: try {
122: classLoader = CompilerClassLoader.getCompilerClassLoader();
123: cacheClassLoader = new URLClassLoader(
124: new URL[] { (new File(cachePath + '/')).toURL() },
125: classLoader);
126: // registerClassLoader(cacheClassLoader);
127:
128: flushNodeEvaluatorCache();
129: addScriptPath((new java.io.File(".")).getAbsolutePath());
130: addParser(new DefaultParser());
131:
132: // mount the root filesystems:
133: File[] roots = File.listRoots();
134: for (int i = 0; i < roots.length; i++)
135: AbstractFileSystem.mount(new LocalFileSystem(roots[i]),
136: "/" + roots[i]);
137:
138: // mount /cache:
139: AbstractFileSystem.mount(new LocalFileSystem(cachePath),
140: "/cache");
141:
142: String cwd;
143: if (webstart) {
144: cwd = "/" + System.getProperty("user.home");
145:
146: // look for a desktop folder, and use that instead if we find one...
147: // this should be good for macosx and windows... but others?
148: if ((new File(cwd + "/Desktop")).exists())
149: cwd += "/Desktop";
150: } else {
151: cwd = "/" + (new File(".")).getAbsolutePath();
152: }
153:
154: // big ugly hack for windoze:
155: if (System.getProperty("os.name").toLowerCase().indexOf(
156: "windows") != -1)
157: if ((cwd.length() > 2) && (cwd.charAt(2) == ':'))
158: cwd = "/" + Character.toUpperCase(cwd.charAt(1))
159: + cwd.substring(2);
160:
161: // if oscript.cwd property is set, it takes precendence:
162: cwd = System.getProperty("oscript.cwd", cwd);
163: System.setProperty("oscript.cwd", cwd);
164:
165: AbstractFileSystem.setCwd(cwd);
166:
167: // also, look in the current user.dir for scripts:
168: addScriptPath(AbstractFileSystem.getCwd());
169:
170: System.setProperty("user.dir.orig", "/"
171: + System.getProperty("user.dir"));
172: System.setProperty("user.dir", cwd);
173:
174: nodeCompiler = new CompiledNodeEvaluatorFactory();
175: nodeInterpreter = new InterpretedNodeEvaluatorFactory();
176:
177: // and finally load and evaluate base.os:
178: eval(resolve("base.os", false));
179:
180: DEFAULT_ARRAY_SORT_COMPARISION_FXN = eval("pkg.system.__defaultArraySortComparision;");
181:
182: } catch (Throwable e) {
183: e.printStackTrace();
184: }
185: }
186:
187: /*=======================================================================*/
188: /**
189: * Get a descriptive version string.
190: */
191: public static String getVersionString() {
192: String vname = Version.getVersionName();
193: int vnum = Version.getVersionNumber();
194:
195: return "ObjectScript " + vname + " (r" + vnum + ")";
196: }
197:
198: /*=======================================================================*/
199: /**
200: * Set whether the compiler should be used or not.
201: *
202: * @param useCompiler iff <code>true</code>, enabled compiler, otherwise
203: * use only the interpreter
204: */
205: public static void useCompiler(boolean useCompiler) {
206: /* NOTE: currently the RegressionTestDriver depends on the behavior of
207: * eval(AbstractFile) always creating a CompiledNodeEvaluator if
208: * useCompiler is true, and an InterpretedNodeEvaluator if not.
209: * If this ever changes, a different interface will need to be
210: * created to give the RegressionTestDriver better control this
211: */
212: OscriptInterpreter.useCompiler = useCompiler;
213: }
214:
215: /*=======================================================================*/
216: /**
217: * Flush the node-evaluator-cache...
218: */
219: static void flushNodeEvaluatorCache() {
220: nodeEvaluatorCacheTable = new Hashtable();
221: }
222:
223: /*=======================================================================*/
224: /**
225: * Set the input stream.
226: *
227: * @param in the stream to use for input
228: */
229: public static void setIn(InputStream in) {
230: OscriptBuiltins.setIn(in);
231: }
232:
233: /*=======================================================================*/
234: /**
235: * Set the output stream.
236: *
237: * @param out the stream to use for output
238: */
239: public static void setOut(PrintStream out) {
240: OscriptBuiltins.setOut(out);
241: }
242:
243: /*=======================================================================*/
244: /**
245: * Set the error stream.
246: *
247: * @param err the stream to use for error output
248: */
249: public static void setErr(PrintStream err) {
250: OscriptBuiltins.setErr(err);
251: }
252:
253: /*=======================================================================*/
254: /**
255: * Get the global scope object. The <code>globalScope</code> is static,
256: * meaning that all script code in an application shares a single global
257: * scope. But, since the interpreter is multi-threaded, you can achieve
258: * the same effect of having multiple interpreter instances by creating
259: * a new level of scope (ie with <code>globalScope</code> as it's parent,
260: * an evaluate within that scope.
261: *
262: * @return the <code>globalScope</code> object
263: */
264: public static Scope getGlobalScope() {
265: if (globalScope == null) {
266: globalScope = new GlobalScope();
267: OscriptBuiltins.init();
268: }
269:
270: return globalScope;
271: }
272:
273: /**
274: * Register a class loader that we can delegate the act of resolving
275: * classes. This allows the user of ObjectScript to give us the
276: * ability to load classes that we might not otherwise have access
277: * to.
278: */
279: public static void registerClassLoader(ClassLoader loader) {
280: CompilerClassLoader.registerClassLoader(loader);
281: }
282:
283: /*=======================================================================*/
284: /*=======================================================================*/
285: /*=======================================================================*/
286:
287: /*=======================================================================*/
288: /**
289: * helper function to implement <code>import</code> statements.
290: */
291: public static Value importHelper(String path, Scope scope) {
292: try {
293: AbstractFile file = OscriptInterpreter.resolve(path, false);
294: if (!file.exists()) {
295: StringBuffer sp = new StringBuffer();
296: for (Iterator itr = OscriptInterpreter.getScriptPath(); itr
297: .hasNext();) {
298: sp.append(itr.next());
299: if (itr.hasNext())
300: sp.append(", ");
301: }
302: throw new java.io.FileNotFoundException(path + " (in "
303: + sp + ")");
304: }
305: return OscriptInterpreter.eval(file, scope);
306: } catch (Throwable e) {
307: throw OJavaException.makeJavaExceptionWrapper(e);
308: }
309: }
310:
311: /*=======================================================================*/
312: /**
313: * Evaluate from the specified abstract file. The stream is evaluated
314: * until EOF is hit.
315: *
316: * @param file the file to evaluate
317: * @return the result of evaluating the input
318: * @throws ParseException if error parsing input
319: * @throws IOException if something goes poorly when reading file
320: */
321: public static Value eval(AbstractFile file) throws ParseException,
322: IOException {
323: return eval(file, getGlobalScope());
324: }
325:
326: /*=======================================================================*/
327: /**
328: * Evaluate from the specified abstract file. The stream is evaluated
329: * until EOF is hit.
330: *
331: * @param file the file to evaluate
332: * @param scope the scope to evaluate in
333: * @return the result of evaluating the input
334: * @throws ParseException if error parsing input
335: * @throws IOException if something goes poorly when reading file
336: */
337: public static Value eval(AbstractFile file, Scope scope)
338: throws ParseException, IOException {
339: if (TRACE_EVAL) {
340: long et = OscriptInterpreter.et;
341: long t = System.currentTimeMillis();
342: try {
343: return (Value) (StackFrame.currentStackFrame()
344: .evalNode(getNodeEvaluator(file), scope));
345: } finally {
346: t = System.currentTimeMillis() - t;
347: long total = et + t;
348: long cumm = t - (OscriptInterpreter.et - et);
349: System.err.println("eval: \t" + cumm + "\t" + t + "\t"
350: + total + "\t" + file.getPath());
351: OscriptInterpreter.et = total;
352: }
353: } else {
354: return (Value) (StackFrame.currentStackFrame().evalNode(
355: getNodeEvaluator(file), scope));
356: }
357: }
358:
359: public static long et;
360:
361: /*=======================================================================*/
362: /**
363: * Evaluate the specified sting.
364: *
365: * @param str the string to evaluate
366: * @return the result of evaluating the string
367: * @throws ParseException if error parsing string
368: */
369: public static Value eval(String str) throws ParseException {
370: return eval(str, getGlobalScope());
371: }
372:
373: // XXX fixme: work around because "eval" is a object-script keyword
374: public static final Value __eval(String str) throws ParseException {
375: Value val = eval(str);
376: if (val == Value.UNDEFINED)
377: val = Value.NULL;
378: return val;
379: }
380:
381: /*=======================================================================*/
382: /**
383: * Evaluate the specified sting.
384: *
385: * @param str the string to evaluate
386: * @param scope the scope to evaluate in
387: * @return the result of evaluating the string
388: * @throws ParseException if error parsing string
389: */
390: public static Value eval(String str, Scope scope)
391: throws ParseException {
392: Node node = parse(str);
393: NodeEvaluator ne = nodeInterpreter.createNodeEvaluator(str,
394: node);
395:
396: return (Value) (StackFrame.currentStackFrame().evalNode(ne,
397: scope));
398: }
399:
400: // XXX fixme: work around because "eval" is a object-script keyword
401: public static final Value __eval(String str, Scope scope)
402: throws ParseException {
403: Value val = eval(str, scope);
404: if (val == Value.UNDEFINED)
405: val = Value.NULL;
406: return val;
407: }
408:
409: public static final void __declareInScope(String name, Value val,
410: Scope scope) {
411: Value tmp = oscript.classwrap.ClassWrapGen.getScriptObject(val);
412: if (tmp != null)
413: val = tmp;
414: scope.createMember(name, 0).opAssign(val);
415: }
416:
417: /*=======================================================================*/
418: /*=======================================================================*/
419: /*=======================================================================*/
420:
421: /*=======================================================================*/
422: /**
423: * Add the <code>parser</code> to list of recognized parsers. The
424: * registered parsers will determine what sorts of input the interpreter
425: * can handle.
426: *
427: * @param parser the parser to register
428: * @see #removeParser
429: */
430: public static void addParser(Parser parser) {
431: parserList.add(parser);
432: }
433:
434: /*=======================================================================*/
435: /**
436: * Remove the <code>parser</code> from list of recognized parsers. The
437: * registered parsers will determine what sorts of input the interpreter
438: * can handle.
439: *
440: * @param parser the parser to register
441: * @see #addParser
442: */
443: public static void removeParser(Parser parser) {
444: parserList.remove(parser);
445: }
446:
447: /*=======================================================================*/
448: /**
449: * Parse the input stream to a syntaxtree.
450: *
451: * @param file the file to parse
452: * @return the parsed syntaxtree
453: */
454: public static Node parse(AbstractFile file) throws ParseException,
455: IOException {
456: for (Iterator itr = parserList.iterator(); itr.hasNext();) {
457: Parser p = (Parser) (itr.next());
458:
459: if (p.getExtension().equals(file.getExtension()))
460: return p.parse(file);
461: }
462:
463: throw new ProgrammingErrorException("no parser for: "
464: + file.getPath());
465: }
466:
467: /**
468: * Get node-evaluator via cache. If not in cache, and not loadable into
469: * cache from cache-fs, then actually parse and create new node-evaluator.
470: * If exists in cache, but <code>file</code> has been more recently modified,
471: * the re-parse and create new node-evaluator.
472: */
473: private static NodeEvaluator getNodeEvaluator(AbstractFile file)
474: throws ParseException, IOException {
475: CacheEntry entry = (CacheEntry) (nodeEvaluatorCacheTable
476: .get(file.getPath()));
477:
478: if (entry == null) {
479: AbstractFile filec = getCacheFile(file);
480:
481: if (filec.exists())
482: entry = readCacheEntry(filec);
483:
484: if (entry != null)
485: nodeEvaluatorCacheTable.put(file.getPath().intern(),
486: entry);
487: }
488:
489: if ((entry == null) || (entry.time != file.lastModified())) {
490: NodeEvaluator ne = createNodeEvaluator(file.getPath()
491: .intern(), parse(file));
492: entry = new CacheEntry(file, ne);
493: nodeEvaluatorCacheTable.put(file.getPath().intern(), entry);
494: writeCacheEntry(entry);
495: }
496:
497: return entry.ne;
498: }
499:
500: private static AbstractFile getCacheFile(AbstractFile file)
501: throws IOException {
502: String path = file.getPath();
503:
504: // need to sanitize path, since windoze can't deals with things like "C:"
505: // occuring in the middle of the path:
506: path = path.replace(':', '_');
507:
508: return resolve("/cache/" + CACHE_VERSION + "/" + path + "c",
509: false);
510: }
511:
512: /**
513: * this is called once at startup, to construct an empty expression
514: * list node evaluator.
515: */
516: private static NodeEvaluator getEmptyExprListEvaluator()
517: throws IOException, ParseException {
518: return getNodeEvaluator(resolve("__empty.os", false));
519: }
520:
521: static {
522: try {
523: EMPTY_EXPR_LIST_EVALUATOR = getEmptyExprListEvaluator();
524: } catch (ParseException e) {
525: e.printStackTrace();
526: throw new RuntimeException("unrecoverable error at startup");
527: } catch (IOException e) {
528: e.printStackTrace();
529: throw new RuntimeException("unrecoverable error at startup");
530: }
531: }
532:
533: /*=======================================================================*/
534: /**
535: * Parse the string to a syntaxtree.
536: *
537: * @param str the string to parse
538: * @return the parsed syntaxtree
539: */
540: public static Node parse(String str) throws ParseException {
541: try {
542: return parse(new oscript.util.InputStreamFile(
543: new StringBufferInputStream(str), "string-input.os"));
544: } catch (IOException e) {
545: throw new ProgrammingErrorException("shouldn't get here!");
546: }
547: }
548:
549: /*=======================================================================*/
550: /*=======================================================================*/
551: /*=======================================================================*/
552:
553: /* I should add some mechanism to add/remove NodeEvaluatorFactory?
554: */
555:
556: /*=======================================================================*/
557: /**
558: * Create a NodeEvaluator to evaluate a node. An application embedding
559: * the interpreter should use this method to convert the parsed syntax
560: * tree to something that can be evaluated within a scope, rather than
561: * directly using the visitors. This protects the application against
562: * changes to the parsed representation of the program.
563: *
564: * @param desc description
565: * @param node the node
566: * @return a NodeEvaluator
567: */
568: public static NodeEvaluator createNodeEvaluator(String name,
569: Node node) {
570: // XXX hack for the cached node evaluator...
571: NodeEvaluator ne = null;
572:
573: if (node instanceof Program)
574: ne = ((Program) node).nodeEvaluator;
575: else if (node instanceof FunctionCallExpressionList)
576: ne = ((FunctionCallExpressionList) node).nodeEvaluator;
577: else if (node instanceof ProgramFile)
578: ne = ((ProgramFile) node).nodeEvaluator;
579:
580: if (ne != null)
581: return ne;
582:
583: ne = createNodeEvaluatorImpl(name, node);
584:
585: if (node instanceof Program)
586: ((Program) node).nodeEvaluator = ne;
587: else if (node instanceof FunctionCallExpressionList)
588: ((FunctionCallExpressionList) node).nodeEvaluator = ne;
589: else if (node instanceof ProgramFile)
590: ((ProgramFile) node).nodeEvaluator = ne;
591:
592: return ne;
593: }
594:
595: private static NodeEvaluator createNodeEvaluatorImpl(String name,
596: Node node) {
597: NodeEvaluator ne = null;
598:
599: if (useCompiler)
600: ne = nodeCompiler.createNodeEvaluator(name, node);
601: // nodeCompiler.createNodeEvaluator could return null if compile fails
602: // for whatever reason...
603: if (ne == null)
604: ne = nodeInterpreter.createNodeEvaluator(name, node);
605:
606: return ne;
607: }
608:
609: /*=======================================================================*/
610: /*=======================================================================*/
611: /*=======================================================================*/
612:
613: /*=======================================================================*/
614: /**
615: * Add the specified path to the paths that are searched to import a
616: * file.
617: *
618: * @param path a path to search for imports
619: */
620: public static void addScriptPath(String path) {
621: try {
622: path = AbstractFileSystem.resolve(path).getPath(); // convert to absolute path
623: } catch (Throwable t) {
624: }
625:
626: synchronized (scriptPathList) {
627: if (!scriptPathList.contains(path))
628: scriptPathList.addFirst(path);
629: scriptPaths = null;
630: }
631: }
632:
633: /*=======================================================================*/
634: /**
635: * Remove the specified path to the paths that are searched to import a
636: * file.
637: *
638: * @param path a path to search for imports
639: */
640: public static void removeScriptPath(String path) {
641: try {
642: path = AbstractFileSystem.resolve(path).getPath(); // convert to absolute path
643: } catch (Throwable t) {
644: }
645:
646: synchronized (scriptPathList) {
647: scriptPathList.remove(path);
648: scriptPaths = null;
649: }
650: }
651:
652: /*=======================================================================*/
653: /**
654: * Return an iterator of entries in the script-path. Each entry is a path
655: * that is prefixed to a relative path passed to {@link #resolve} in the
656: * process of trying to resolve a file.
657: *
658: * @return an iterator of strings
659: */
660: public static Iterator getScriptPath() {
661: // copy list to avoid concurrent-mod problems
662: synchronized (scriptPathList) {
663: return new oscript.util.CollectionIterator((new LinkedList(
664: scriptPathList)).iterator());
665: }
666: }
667:
668: /*=======================================================================*/
669: /**
670: * Try to load the specified file from one of the registered filesystems.
671: *
672: * @param path the path to the file to resolve
673: * @param create create the file if it does not exist
674: * @throws IOException if something goes wrong when reading file
675: * @see #addScriptPath
676: */
677: public static AbstractFile resolve(String path, boolean create)
678: throws IOException {
679: AbstractFile file = resolveImpl(path);
680:
681: // if file doesn't exist in a currently mounted filesystem, and it is
682: // loadable as a system resource, try to figure out what filesystem to
683: // mount to make it accessible:
684: if (!file.exists()) {
685: if (create)
686: file.createNewFile();
687: else if ((path.charAt(0) != '/')
688: && tryMountSystemFileSystem(path))
689: file = resolveImpl(path);
690: }
691:
692: return file;
693: }
694:
695: private static AbstractFile resolveImpl(String path)
696: throws IOException {
697: AbstractFile file = null;
698:
699: if (path.charAt(0) == '/') {
700: file = AbstractFileSystem.resolve(path);
701: } else {
702: // list should never be empty, at a minimum it should include "":
703: String[] scriptPaths = OscriptInterpreter.scriptPaths;
704:
705: // depending on how this gets compiled, order may be important
706: // (think other thread switching OscriptInterpreter.scriptPaths
707: // back to null) so assign local copy first, just in case the
708: // generated code reads the value back for the next assignment,
709: // instead of just DUPing the value on the stack:
710: if (scriptPaths == null)
711: OscriptInterpreter.scriptPaths = scriptPaths = (String[]) (scriptPathList
712: .toArray(new String[scriptPathList.size()]));
713:
714: for (int i = 0; i < scriptPaths.length; i++) {
715: String actualPath = scriptPaths[i] + "/" + path;
716: file = AbstractFileSystem.resolve(actualPath);
717: if (file.exists())
718: break;
719: }
720: }
721:
722: // if file doesn't exist, always return the non-existant file with
723: // absolute path, so if the user wants to create the file, it gets
724: // created with the specified absolute path, rather than relative
725: // to the last entry in the script-path:
726: if (!file.exists())
727: file = AbstractFileSystem.resolve(path);
728:
729: return file;
730: }
731:
732: /**
733: * Mount a .jar file under <code>/jar/file.jar</code> and add to script path
734: * @param file the jar file to mount
735: */
736: public static final void mountJarFile(AbstractFile file)
737: throws Exception {
738: mountJarFile(file, true);
739: }
740:
741: private static final void mountJarFile(AbstractFile file,
742: boolean addToClassPath) throws Exception {
743: String vpath = "/jar/" + file.getName();
744: vpath = vpath.substring(0, vpath.length() - 4); // get rid of ".jar"
745: AbstractFileSystem.mount(new JarFileSystem((File) file, true),
746: vpath);
747: addScriptPath(vpath);
748: if (addToClassPath && (file instanceof File)) {
749: registerClassLoader(new URLClassLoader(
750: new URL[] { ((File) file).toURL() }, ClassLoader
751: .getSystemClassLoader()));
752: }
753: }
754:
755: /**
756: * hack to try an load something as a system resource, and if possible
757: * parse the URL to try and figure out the jar file or local-file-
758: * system path to mount into the abstract file system.
759: */
760: private static final boolean tryMountSystemFileSystem(String path) {
761: try {
762: URL url = classLoader.getResource(path);
763: if (url != null) {
764: url = oscript.util.ResourceResolver.resolve(url);
765: String urlStr = url.toExternalForm();
766: String filePath;
767:
768: if ((filePath = getJarFilePath(path, urlStr)) != null) {
769: // figure out name of jar file, which determines where it is to be mounted:
770: int idx1 = filePath.lastIndexOf('/');
771: idx1 = (idx1 != -1) ? idx1 : filePath
772: .lastIndexOf('\"');
773: idx1 = (idx1 != -1) ? idx1 : 0;
774: int idx2 = filePath.lastIndexOf(".jar");
775: idx2 = (idx2 != -1) ? idx2 : filePath.length();
776:
777: String name = filePath.substring(idx1 + 1, idx2);
778:
779: // work around for the name mangling by webstart:
780: if (name.startsWith("RM")
781: && (idx1 >= 8)
782: && filePath.substring(idx1 - 8, idx1)
783: .equals("DMsigned"))
784: name = name.substring(2);
785:
786: mountJarFile(AbstractFileSystem.resolve(filePath),
787: false);
788:
789: return true;
790: }
791:
792: if ((filePath = getFilePath(path, urlStr)) != null) {
793: // figure out name of the directory, which determines where it is to be mounted:
794: int idx1 = filePath.lastIndexOf('/');
795: idx1 = (idx1 != -1) ? idx1 : filePath
796: .lastIndexOf('\"');
797: idx1 = (idx1 != -1) ? idx1 : 0;
798: String vpath = "/file/" + filePath.substring(idx1);
799:
800: LocalFileSystem.mount(
801: new LocalFileSystem(filePath), vpath);
802: addScriptPath(vpath);
803: return true;
804: }
805: }
806: } catch (Throwable t) {
807: t.printStackTrace();
808: }
809:
810: return false;
811: }
812:
813: private static final String getJarFilePath(String path,
814: String urlStr) {
815: if (urlStr.startsWith("jar:file:")) {
816: int idx = urlStr.lastIndexOf('!');
817:
818: if (idx != -1)
819: return sanitizeUrl(urlStr.substring("jar:file:"
820: .length(), idx));
821: }
822: return null;
823: }
824:
825: private static final String getFilePath(String path, String urlStr) {
826: if (urlStr.startsWith("file:")) {
827: int idx = urlStr.lastIndexOf(path);
828:
829: if (idx != -1)
830: return sanitizeUrl(urlStr.substring("file:".length(),
831: idx));
832: }
833: return null;
834: }
835:
836: /**
837: * utility to string out "%20" and other escape codes, and replace them
838: * with the appropriate character.
839: */
840: private static final String sanitizeUrl(String str) {
841: int lastIdx = 0;
842: int idx;
843:
844: str = str.replace('\\', '/');
845:
846: while ((idx = str.indexOf('%', lastIdx)) != -1) {
847: String a = str.substring(0, idx);
848: String b = str.substring(idx + 3);
849: String hex = str.substring(idx + 1, idx + 3);
850:
851: char c = (char) (Integer.parseInt(hex, 16));
852:
853: str = a + c + b;
854:
855: lastIdx = idx;
856: }
857:
858: return str;
859: }
860:
861: /*=======================================================================*/
862: /*=======================================================================*/
863: /*=======================================================================*/
864:
865: private static CacheEntry readCacheEntry(AbstractFile filec)
866: throws IOException {
867: CacheEntry entry = new CacheEntry();
868:
869: InputStream is = new BufferedInputStream(filec.getInputStream());
870: ObjectInputStream ois = new ObjectInputStream(is) {
871:
872: protected Class resolveClass(ObjectStreamClass v)
873: throws IOException, ClassNotFoundException {
874: return CompilerClassLoader.forName(v.getName(), false,
875: cacheClassLoader);
876: }
877:
878: };
879:
880: try {
881: entry.readExternal(ois);
882: } catch (ClassNotFoundException e) {
883: e.printStackTrace();
884: System.exit(-1);
885: }
886:
887: return entry;
888: }
889:
890: private static void writeCacheEntry(CacheEntry entry)
891: throws IOException {
892: AbstractFile filec = getCacheFile(entry.file);
893:
894: if (!filec.exists())
895: filec.createNewFile();
896:
897: OutputStream os = filec.getOutputStream(false);
898: ObjectOutputStream oos = new ObjectOutputStream(
899: new BufferedOutputStream(os));
900:
901: entry.writeExternal(oos);
902: oos.flush();
903: oos.close();
904: }
905:
906: /**
907: * An entry in the node-evaluator cache.
908: *
909: * @see #getNodeEntry
910: */
911: public static class CacheEntry {
912: public transient AbstractFile file;
913: public long time;
914: public NodeEvaluator ne;
915:
916: public CacheEntry() {
917: }
918:
919: public void readExternal(ObjectInput in)
920: throws ClassNotFoundException, IOException {
921: time = in.readLong();
922: if (in.readByte() == 1) {
923: Class c = Class.forName(in.readUTF(), false,
924: cacheClassLoader);
925: try {
926: ne = (NodeEvaluator) (c.newInstance());
927: } catch (Throwable e) {
928: e.printStackTrace();
929: System.exit(-1);
930: }
931: }
932: }
933:
934: public void writeExternal(ObjectOutput out) throws IOException {
935: out.writeLong(time);
936: if (ne instanceof CompiledNodeEvaluator) {
937: out.writeByte(1);
938: out.writeUTF(ne.getClass().getName());
939: } else {
940: out.writeByte(0);
941: }
942: }
943:
944: CacheEntry(AbstractFile file, NodeEvaluator ne) {
945: this .file = file;
946: this .time = file.lastModified();
947: this .ne = ne;
948: }
949:
950: public String toString() {
951: return "<time: " + time + ", ne: " + ne + ">";
952: }
953: }
954: }
955:
956: /*
957: * Local Variables:
958: * tab-width: 2
959: * indent-tabs-mode: nil
960: * mode: java
961: * c-indentation-style: java
962: * c-basic-offset: 2
963: * eval: (c-set-offset 'substatement-open '0)
964: * eval: (c-set-offset 'case-label '+)
965: * eval: (c-set-offset 'inclass '+)
966: * eval: (c-set-offset 'inline-open '0)
967: * End:
968: */
|