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.data;
022:
023: import oscript.exceptions.*;
024: import oscript.fs.AbstractFile;
025: import oscript.OscriptInterpreter;
026: import oscript.util.MemberTable;
027:
028: import java.util.*;
029:
030: /**
031: * The implementation of a package system for scripts. This handles
032: * automatically <code>import</code>ing script source files when the
033: * have not yet been loaded, or are out of date. The script package
034: * system relies on a couple of coding conventions:
035: * <ul>
036: * <li> The object foo.bar.FooBar is declared as a public function
037: * or variable in the source file <i>foo/bar/FooBar.os</i>
038: * <li> Only one public function or variable, <code>FooBar</code>,
039: * is declared in the source file <i>foo/bar/FooBar.os</i>.
040: * Other variables or functions that are private (ie. not
041: * <code>public</code>) to that source file may be declared
042: * in the source file.
043: * <li> Any non-public variables/functions will be private to
044: * that source file.
045: * </ul>
046: * The way the package system is used:
047: * <pre>
048: * const var foo = new ScriptPackage("/path/to/foo");
049: * var fb = new foo.bar.FooBar();
050: * </pre>
051: * When resolving the request for the member <code>FooBar</code>,
052: * if the source file <i>/path/to/foo/bar/FooBar.os</i> has not yet
053: * been loaded, or has been modified since the last time it was
054: * accessed, the package system will create a new scope and import
055: * the source file into that scope. It will then return the public
056: * variable/function <code>FooBar</code>.
057: *
058: * @author Rob Clark (rob@ti.com)
059: */
060: public final class ScriptPackage extends OObject {
061: private String path;
062: private Scope parentScope;
063: private oscript.util.SymbolMap memberTable;
064:
065: /**
066: * The type object for an instance of ScriptPackage.
067: */
068: public final static Value TYPE = BuiltinType
069: .makeBuiltinType("oscript.data.ScriptPackage");
070: public final static String PARENT_TYPE_NAME = "oscript.data.OObject";
071: public final static String TYPE_NAME = "ScriptPackage";
072: public final static String[] MEMBER_NAMES = new String[] {
073: "castToString", "getMember", "reset" };
074:
075: /*=======================================================================*/
076: /**
077: * Class Constructor.
078: *
079: * @param javaPackage the java package this is a wrapper for
080: * @param parentScope the parent scope of this package
081: */
082: public ScriptPackage(String path, Scope parentScope) {
083: super ();
084:
085: this .path = path;
086: this .parentScope = parentScope;
087:
088: reset();
089: }
090:
091: /*=======================================================================*/
092: /**
093: * Class Constructor.
094: *
095: * @param javaPackage the java package this is a wrapper for
096: */
097: public ScriptPackage(String path) {
098: this (path, OscriptInterpreter.getGlobalScope());
099: }
100:
101: /*=======================================================================*/
102: /**
103: * Class Constructor.
104: *
105: * @param args arguments to this constructor
106: * @throws PackagedScriptObjectException(Exception) if wrong number of args
107: */
108: public ScriptPackage(MemberTable args)
109: throws PackagedScriptObjectException {
110: this (argsToPath(args), argsToScope(args));
111: }
112:
113: private static final String argsToPath(MemberTable args)
114: throws PackagedScriptObjectException {
115: if (args.length() >= 1)
116: return args.referenceAt(0).castToString();
117: else
118: throw PackagedScriptObjectException
119: .makeExceptionWrapper(new OIllegalArgumentException(
120: "wrong number of args!"));
121: }
122:
123: private static final Scope argsToScope(MemberTable args) {
124: if (args.length() == 2)
125: return (Scope) (args.referenceAt(1).unhand());
126: else if (args.length() == 1)
127: return OscriptInterpreter.getGlobalScope();
128: else
129: throw PackagedScriptObjectException
130: .makeExceptionWrapper(new OIllegalArgumentException(
131: "wrong number of args!"));
132: }
133:
134: /*=======================================================================*/
135: /**
136: * Get the type of this object. The returned type doesn't have to take
137: * into account the possibility of a script type extending a built-in
138: * type, since that is handled by {@link #getType}.
139: *
140: * @return the object's type
141: */
142: protected Value getTypeImpl() {
143: return TYPE;
144: }
145:
146: /*=======================================================================*/
147: /**
148: * Clear the cached members. This is handy if you need to force reload
149: * members during development/debugging. <i>Because of the potential
150: * expense incurred in reloading all members the next time they are
151: * accessed, this should only be used for development/debugging, and
152: * should not be used by deployed code</i>
153: */
154: public void reset() {
155: memberTable = new oscript.util.SymbolMap();
156: }
157:
158: /*=======================================================================*/
159: /**
160: * Convert this object to a native java <code>String</code> value.
161: *
162: * @return a String value
163: * @throws PackagedScriptObjectException(NoSuchMethodException)
164: */
165: public String castToString() throws PackagedScriptObjectException {
166: return "[package: " + path + "]";
167: }
168:
169: /*=======================================================================*/
170: /**
171: * Get a member of this object.
172: *
173: * @param id the id of the symbol that maps to the member
174: * @param exception whether an exception should be thrown if the
175: * member object is not resolved
176: * @return a reference to the member
177: * @throws PackagedScriptObjectException(NoSuchMethodException)
178: * @throws PackagedScriptObjectException(NoSuchMemberException)
179: */
180: public Value getMember(int id, boolean exception)
181: throws PackagedScriptObjectException {
182: Value val = getMemberImpl(id);
183:
184: if (val != null)
185: return val;
186: else
187: return super .getMember(id, exception);
188: }
189:
190: /*=======================================================================*/
191: /**
192: * Derived classes that implement {@link #getMember} should also
193: * implement this.
194: *
195: * @param s the set to populate
196: * @param debugger <code>true</code> if being used by debugger, in
197: * which case both public and private/protected field names should
198: * be returned
199: * @see #getMember
200: */
201: protected void populateMemberSet(java.util.Set s, boolean debugger) {
202: // only list what is alreay in the member-cache... a bit lame but I
203: // don't see a better way of doing this:
204: for (Iterator itr = memberTable.keys(); itr.hasNext();)
205: s
206: .add(Symbol.getSymbol(((Integer) (itr.next()))
207: .intValue()));
208: }
209:
210: private synchronized final Value getMemberImpl(int id) {
211: try {
212: Object val = memberTable.get(id);
213:
214: if (val == Boolean.FALSE)
215: return null;
216:
217: CacheEntry ce = (CacheEntry) val;
218:
219: if (ce == null) {
220: String basePath = path + "/"
221: + Symbol.getSymbol(id).castToString();
222: AbstractFile file;
223:
224: // first check if file:
225: file = OscriptInterpreter.resolve(basePath + ".os",
226: false);
227: if (file.exists() && file.canRead()
228: && !file.isDirectory()) {
229: ce = new CacheEntry(file, null, -2);
230: watchedCacheEntrySet.put(ce, Boolean.TRUE);
231: } else {
232: file = OscriptInterpreter.resolve(basePath, false);
233: if (file.exists() && file.isDirectory())
234: ce = new CacheEntry(file, new ScriptPackage(
235: path
236: + "/"
237: + Symbol.getSymbol(id)
238: .castToString(),
239: parentScope), -1);
240: }
241:
242: if (ce == null) {
243: memberTable.put(id, Boolean.FALSE);
244: return null;
245: }
246:
247: memberTable.put(id, ce);
248: }
249:
250: // check for files that have changed since last access:
251: synchronized (ce) {
252: if (ce.entry == null) {
253: ce.time = ce.file.lastModified(); // first in case file changes while evaluating
254: Scope scope = new FileScope(parentScope, ce.file);
255: OscriptInterpreter.eval(ce.file, scope);
256: ce.entry = scope.getMember(id);
257: }
258: }
259:
260: return ce.entry;
261: } catch (oscript.parser.ParseException e) {
262: if (DEBUG)
263: e.printStackTrace();
264: throw OJavaException.makeJavaExceptionWrapper(e);
265: } catch (java.io.IOException e) {
266: if (DEBUG)
267: e.printStackTrace();
268: throw OJavaException.makeJavaExceptionWrapper(e);
269: }
270: }
271:
272: private static Map watchedCacheEntrySet = new WeakHashMap();
273:
274: /**
275: * Rather than checking the file's last modified time synchronously
276: * in getMemberImpl(), which would be a bottleneck, use a low priority
277: * thread to poll for modified files, and clear <code>entry</code>
278: * so that the thread doing the getMemberImpl()
279: */
280: static {
281:
282: oscript.util.WorkerThread.addRunnable(new Runnable() {
283: public void run() {
284: try {
285: for (Iterator itr = watchedCacheEntrySet.keySet()
286: .iterator(); itr.hasNext();) {
287: CacheEntry ce = (CacheEntry) (itr.next());
288: synchronized (ce) {
289: if ((ce.entry != null)
290: && (ce.time != ce.file
291: .lastModified())) {
292: System.err.println("changed: "
293: + ce.file);
294: ce.entry = null;
295: }
296: }
297: }
298: } catch (java.util.ConcurrentModificationException e) {
299: // ignore
300: }
301: }
302: }, 250);
303: }
304:
305: /**
306: * An entry in the id -> member cache
307: */
308: private static class CacheEntry {
309: CacheEntry(AbstractFile file, Value entry, long time) {
310: this .file = file;
311: this .entry = entry;
312: this .time = time;
313: }
314:
315: final AbstractFile file;
316: Value entry;
317: long time;
318: }
319: }
320:
321: /**
322: * Use a different type to denote the file-level scopes created by the script
323: * package system.
324: */
325: class FileScope extends BasicScope {
326: private AbstractFile file;
327:
328: FileScope(Scope parentScope, AbstractFile file) {
329: super (parentScope);
330: this .file = file;
331: }
332:
333: public AbstractFile getFile() {
334: return file;
335: }
336: }
337:
338: /*
339: * Local Variables:
340: * tab-width: 2
341: * indent-tabs-mode: nil
342: * mode: java
343: * c-indentation-style: java
344: * c-basic-offset: 2
345: * eval: (c-set-offset 'substatement-open '0)
346: * eval: (c-set-offset 'case-label '+)
347: * eval: (c-set-offset 'inclass '+)
348: * eval: (c-set-offset 'inline-open '0)
349: * End:
350: */
|