001: /*
002: * CachedScript.java
003: *
004: * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * See the file "LICENSE.txt" for information on usage and redistribution of
007: * this file, and for a DISCLAIMER OF ALL WARRANTIES.
008: */
009: package pnuts.ext;
010:
011: import pnuts.lang.Pnuts;
012: import pnuts.lang.Runtime;
013: import pnuts.lang.Visitor;
014: import pnuts.lang.Implementation;
015: import pnuts.lang.ParseException;
016: import pnuts.lang.PnutsException;
017: import pnuts.lang.Context;
018: import pnuts.compiler.Compiler;
019: import java.net.URL;
020: import java.net.URLConnection;
021: import java.io.IOException;
022: import java.io.Reader;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025:
026: /**
027: * Executable script that is automatically recompiled
028: */
029: public class CachedScript extends Pnuts {
030: protected URL scriptURL;
031: protected long parsedTime;
032: protected Pnuts script;
033: protected String encoding;
034:
035: /**
036: * Constructor
037: *
038: * @param scriptURL the URL of the script
039: */
040: public CachedScript(URL scriptURL) throws IOException,
041: ParseException {
042: this (scriptURL, null, null);
043: }
044:
045: /**
046: * Constructor
047: *
048: * @param scriptURL the URL of the script
049: * @param encoding the character encoding of the script. If null, the default encoding is used.
050: * @param context the context in which the script is first parsed/compiled.
051: */
052: public CachedScript(URL scriptURL, String encoding, Context context)
053: throws IOException, ParseException {
054: this .scriptURL = scriptURL;
055: this .encoding = encoding;
056: update(context);
057: }
058:
059: public String unparse() {
060: return script.unparse();
061: }
062:
063: public Object run(Context c) {
064: try {
065: if (needToUpdate()) {
066: update(c);
067: }
068: } catch (PnutsException pe) {
069: throw pe;
070: } catch (Exception e) {
071: throw new PnutsException(e, c);
072: }
073: return script.run(c);
074: }
075:
076: public Object accept(Visitor v, Context c) {
077: try {
078: if (needToUpdate()) {
079: update(c);
080: }
081: } catch (PnutsException pe) {
082: throw pe;
083: } catch (Exception e) {
084: throw new PnutsException(e, c);
085: }
086: return script.accept(v, c);
087: }
088:
089: /**
090: * Determin if the script should be recompiled
091: *
092: * @return true if the script should be recompiled
093: */
094: protected boolean needToUpdate() {
095: long modified = lastModified(scriptURL);
096: return modified < 0 || (modified > parsedTime);
097: }
098:
099: static long lastModified(URL scriptURL) {
100: try {
101: final URLConnection conn = scriptURL.openConnection();
102: final InputStream in = conn.getInputStream();
103: try {
104: return conn.getLastModified();
105: } finally {
106: in.close();
107: }
108: } catch (IOException e) {
109: return -1L;
110: }
111: }
112:
113: /**
114: * Returns a compiler. If this method returns null,
115: * script won't be compiled.
116: */
117: protected Compiler getCompiler() {
118: Compiler compiler = new Compiler(null, false, true);
119: compiler.setConstantFolding(true);
120: return compiler;
121: }
122:
123: /**
124: * Parse/compile the script and update the timestamp.
125: *
126: * @param context the context in which the script is compiled.
127: */
128: protected void update(Context context) throws IOException,
129: ParseException {
130: Reader reader;
131: if (encoding != null) {
132: reader = new InputStreamReader(scriptURL.openStream(),
133: encoding);
134: } else {
135: if (context != null) {
136: reader = Runtime.getScriptReader(
137: scriptURL.openStream(), context);
138: } else {
139: reader = new InputStreamReader(scriptURL.openStream());
140: }
141: }
142: this .script = Pnuts.parse(reader);
143: script.setScriptSource(scriptURL);
144: if (context == null) {
145: context = new Context();
146: }
147: Compiler compiler = getCompiler();
148: this.script = compiler.compile(script, context);
149: this.parsedTime = System.currentTimeMillis();
150: }
151: }
|