4426325a129ff04c5cd5b47e8bece653f92ee0c7
[org.ibex.core.git] / src / org / xwt / util / JSObject.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt.util;
3
4 import org.mozilla.javascript.*;
5 import java.net.*;
6 import java.io.*;
7 import java.util.*;
8 import org.xwt.*;
9
10 /** Simple implementation of a JavaScript host object.
11  *
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.
15  *
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.
22  */
23 public class JSObject implements Scriptable {
24
25     // Static Data ///////////////////////////////////////////////////////////////////////    
26
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();
30         if (cx == null) {
31             if (Log.on) Log.log(JSObject.class, "Context.getCurrentContext() is null in getCurrentFunctionSourceName()");
32             return "unknown";
33         }
34         Function cf = cx.currentFunction;
35         if (cf == null) return null;
36         return (cf instanceof InterpretedFunction) ?
37             ((InterpretedFunction)cf).getSourceName() : ((InterpretedScript)cf).getSourceName();
38     }
39
40     /** cache for nodeNameToPackageName(); prevents creation of zillions of String objects */
41     private static Hash nodeNameToPackageNameHash = new Hash(1000, 2);
42
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;
48         ret = nodeName;
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);
52                 break;
53             }
54         if (ret.lastIndexOf('.') != -1)
55             ret = ret.substring(0, ret.lastIndexOf('.'));
56         ret = ret.intern();
57         nodeNameToPackageNameHash.put(nodeName, ret);
58         return ret;
59     }
60     
61     /** this holds a scope which has been initialized with the standard ECMA objects */
62     public static Scriptable defaultObjects = Context.enter().initStandardObjects(null);
63
64     // Instance Data ///////////////////////////////////////////////////////////////////////    
65
66     /** The properties that holds our scope */
67     protected Hash properties = null;
68     
69     /** If true, put()s and delete()s are prohibited */
70     boolean sealed = false;
71
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
74      */
75     boolean privateVars = false;
76
77     public JSObject() { }
78     public JSObject(boolean privateVars) { this.privateVars = privateVars; }
79
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;
83
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);
88     }
89
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));
93     }
94
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) {
97         if (!privateVars) {
98             putGlobally(name, null, value);
99             return;
100         }
101         if (properties == null) properties = new Hash();
102
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);
106     }
107
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);
113     }
114
115     /**
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
119      */
120     public void put(String name, Object value) { put(name, null, value); }
121     public void put(String name, Scriptable start, Object value) {
122         if (sealed) return;
123         if (name == null || name.equals("")) return;
124
125         if (getPrivately(name, getCurrentFunctionSourceName()) != null)
126             putPrivately(name, value, getCurrentFunctionSourceName());
127
128         for(Scriptable cur = Context.enter().currentFunction; cur != null; cur = cur.getParentScope())
129             if (cur == this) {
130                 putPrivately(name, value, getCurrentFunctionSourceName());
131                 return;
132             }
133         putGlobally(name, start, value);
134     }
135     
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);
140         return false;
141     }
142     
143     public Object[] getIds() {
144         if (properties == null) return new Object[] { };
145         else return properties.keys();
146     }
147     
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 {
150         
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
153          */
154         public static Hash lastByThread = new Hash();
155         
156         public static Top singleton = new Top();
157         private Top() { }
158         
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);
165         }
166
167         public Object get(String name, Scriptable start) {
168
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);
176         }
177
178         // Trivial methods
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[] { }; }
193
194     }
195
196     // Helper class for defining functions. //////////////////////////////////////
197
198     public static abstract class JSFunction extends JSObject implements Function {
199         public JSFunction() { setSeal(true); }
200         public Scriptable construct(Context cx, Scriptable scope, java.lang.Object[] args) { return null; }
201     }
202
203     // Trivial Methods ///////////////////////////////////////////////////////////
204
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); }
219
220 }
221