1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
4 import org.mozilla.javascript.*;
10 /** Simple implementation of a JavaScript host object.
12 * If privateVars is set, then any "var" declaration issued on this
13 * scope will create a variable visible only to scripts from the same
14 * directory (package) as the script that made the declaration.
16 * This is implemented by making JSObject.Top the ultimate
17 * parentScope of all privateVars-style JSObjects. Puts to JSObjects
18 * which are var-decls will not climb the scope chain, and will be
19 * put directly to the JSObject. Puts which are <i>not</i> var-decls
20 * will climb the scope chain and will be put to Top, which will then
21 * re-put the value to the JSObject as a global.
23 public class JSObject implements Scriptable {
25 // Static Data ///////////////////////////////////////////////////////////////////////
27 /** helper; retrieves the 'source code filename' (usually the nodeName) of the currently-executing function in this thread */
28 public static String getCurrentFunctionSourceName() {
29 Context cx = Context.getCurrentContext();
31 if (Log.on) Log.log(JSObject.class, "Context.getCurrentContext() is null in getCurrentFunctionSourceName()");
34 Function cf = cx.currentFunction;
35 if (cf == null) return null;
36 return (cf instanceof InterpretedFunction) ?
37 ((InterpretedFunction)cf).getSourceName() : ((InterpretedScript)cf).getSourceName();
40 /** cache for nodeNameToPackageName(); prevents creation of zillions of String objects */
41 private static Hash nodeNameToPackageNameHash = new Hash(1000, 2);
43 /** converts a nodeName (source code filename) into the 'package name' (resource name of its directory) */
44 public static String nodeNameToPackageName(String nodeName) {
45 if (nodeName == null) return null;
46 String ret = (String)nodeNameToPackageNameHash.get(nodeName);
47 if (ret != null) return ret;
49 for(int i=0; i<ret.length() - 1; i++)
50 if (ret.charAt(i) == '.' && (ret.charAt(i + 1) == '_' || Character.isDigit(ret.charAt(i + 1)))) {
51 ret = ret.substring(0, i);
54 if (ret.lastIndexOf('.') != -1)
55 ret = ret.substring(0, ret.lastIndexOf('.'));
57 nodeNameToPackageNameHash.put(nodeName, ret);
61 /** this holds a scope which has been initialized with the standard ECMA objects */
62 public static Scriptable defaultObjects = Context.enter().initStandardObjects(null);
64 // Instance Data ///////////////////////////////////////////////////////////////////////
66 /** The properties that holds our scope */
67 protected Hash properties = null;
69 /** If true, put()s and delete()s are prohibited */
70 boolean sealed = false;
72 /** If true, variables declared with 'var' become visible only to scripts from the same
73 * directory/package as the one which created the variable
75 boolean privateVars = false;
78 public JSObject(boolean privateVars) { this.privateVars = privateVars; }
80 public Object get(String name) { return get(name, null); }
81 public Object get(String name, Scriptable start) {
82 if (name == null || name.equals("") || properties == null) return null;
84 Object ret = properties.get(name, nodeNameToPackageName(getCurrentFunctionSourceName()));
85 if (ret == NOT_FOUND) return null;
86 if (ret != null) return ret;
87 return properties.get(name, null);
90 /** returns the package-private variable <tt>name</tt> associated with <tt>nodeName</tt> */
91 public Object getPrivately(String name, String nodeName) {
92 return properties == null ? null : properties.get(name, nodeNameToPackageName(nodeName));
95 /** puts a property as a private variable, accessible only to scripts in the source code file <tt>nodeName</tt> */
96 public void putPrivately(String name, Object value, String nodeName) {
98 putGlobally(name, null, value);
101 if (properties == null) properties = new Hash();
103 // we use NOT_FOUND to mark a variable which exists as a script-local, yet has a value of null
104 if (value == null) value = NOT_FOUND;
105 properties.put(name, nodeNameToPackageName(nodeName), value);
108 /** forces a property to be put globally -- accessible to all scripts */
109 public void putGlobally(String name, Scriptable start, Object value) {
110 if (name == null || name.equals("")) return;
111 if (properties == null) properties = new Hash();
112 properties.put(name, null, value);
116 * if a put is made directly to us (rather than cascading up to
117 * Top), by a script for whom we are in the ultimate parent
118 * scope, it must be a var-declaration
120 public void put(String name, Object value) { put(name, null, value); }
121 public void put(String name, Scriptable start, Object value) {
123 if (name == null || name.equals("")) return;
125 if (getPrivately(name, getCurrentFunctionSourceName()) != null)
126 putPrivately(name, value, getCurrentFunctionSourceName());
128 for(Scriptable cur = Context.enter().currentFunction; cur != null; cur = cur.getParentScope())
130 putPrivately(name, value, getCurrentFunctionSourceName());
133 putGlobally(name, start, value);
136 /** if privateVars is enabled, we always return false, to see if the put propagates up to Top */
137 public boolean has(String name, Scriptable start) {
138 if (!privateVars) return properties != null && properties.get(name) != null;
139 Top.lastByThread.put(Thread.currentThread(), this);
143 public Object[] getIds() {
144 if (properties == null) return new Object[] { };
145 else return properties.keys();
148 /** Parent scope of all JSObjects; we use the has(String) method on this object to determine if a put represents a var-declaration or a plain 'ol put */
149 private static class Top implements Scriptable {
151 /** Last JSObject to perform a has(), keyed by thread.
152 * Assumes that every Top.has() is immediately preceded by a JSObject.has() in the same thread
154 public static Hash lastByThread = new Hash();
156 public static Top singleton = new Top();
159 public boolean has(String name, Scriptable start) { return true; }
160 public void put(String name, Scriptable start, Object value) {
161 JSObject last = (JSObject)lastByThread.get(Thread.currentThread());
162 if (last.properties != null && last.properties.get(name, nodeNameToPackageName(getCurrentFunctionSourceName())) != null)
163 last.put(name, start, value);
164 else last.putGlobally(name, start, value);
167 public Object get(String name, Scriptable start) {
169 // hack since Rhino needs to be able to grab these functions to create new objects
170 if (name.equals("Object")) return JSObject.defaultObjects.get("Object", null);
171 if (name.equals("Array")) return JSObject.defaultObjects.get("Array", null);
172 if (name.equals("Function")) return JSObject.defaultObjects.get("Function", null);
173 if (name.equals("TypeError")) return JSObject.defaultObjects.get("TypeError", null);
174 if (name.equals("ConversionError")) return JSObject.defaultObjects.get("ConversionError", null);
175 return ((JSObject)lastByThread.get(Thread.currentThread())).get(name, start);
179 public String getClassName() { return "Top"; }
180 public Scriptable construct(Context cx, Scriptable scope, java.lang.Object[] args) { return null; }
181 public void delete(String name) { }
182 public Scriptable getParentScope() { return null; }
183 public void setParentScope(Scriptable p) { }
184 public boolean hasInstance(Scriptable value) { return false; }
185 public Scriptable getPrototype() { return null; }
186 public void setPrototype(Scriptable p) { }
187 public void delete(int i) { }
188 public Object getDefaultValue(Class hint) { return "Top"; }
189 public void put(int i, Scriptable start, Object value) { put(String.valueOf(i), start, value); }
190 public Object get(int i, Scriptable start) { return get(String.valueOf(i), start); }
191 public boolean has(int i, Scriptable start) { return has(String.valueOf(i), start); }
192 public Object[] getIds() { return new Object[] { }; }
196 // Helper class for defining functions. //////////////////////////////////////
198 public static abstract class JSFunction extends JSObject implements Function {
199 JSFunction() { setSeal(true); }
200 public Scriptable construct(Context cx, Scriptable scope, java.lang.Object[] args) { return null; }
203 // Trivial Methods ///////////////////////////////////////////////////////////
205 public void setSeal(boolean doseal) { sealed = doseal; }
206 public void flush() { if (properties != null) properties.clear(); }
207 public void delete(String name) { if (Log.on) Log.log(this, "JSObject.delete() should not be reachable"); }
208 public void delete(int i) { if (Log.on) Log.log(this, "JSObject.delete() should not be reachable"); }
209 public Scriptable getParentScope() { return privateVars ? Top.singleton : null; }
210 public void setParentScope(Scriptable p) { }
211 public boolean hasInstance(Scriptable value) { return false; }
212 public Scriptable getPrototype() { return null; }
213 public void setPrototype(Scriptable p) { }
214 public String getClassName() { return this.getClass().getName(); }
215 public Object getDefaultValue(Class hint) { return toString(); }
216 public void put(int i, Scriptable start, Object value) { put(String.valueOf(i), start, value); }
217 public Object get(int i, Scriptable start) { return get(String.valueOf(i), start); }
218 public boolean has(int i, Scriptable start) { return has(String.valueOf(i), start); }