blob: 29d9fd903743f485c299db130b747cbb54cf9dbe [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.script.javascript;
27
28import sun.org.mozilla.javascript.internal.*;
29import java.util.*;
30
31/**
32 * JSAdapter is java.lang.reflect.Proxy equivalent for JavaScript. JSAdapter
33 * calls specially named JavaScript methods on an adaptee object when property
34 * access is attempted on it.
35 *
36 * Example:
37 *
38 * var y = {
39 * __get__ : function (name) { ... }
40 * __has__ : function (name) { ... }
41 * __put__ : function (name, value) {...}
42 * __delete__ : function (name) { ... }
43 * __getIds__ : function () { ... }
44 * };
45 *
46 * var x = new JSAdapter(y);
47 *
48 * x.i; // calls y.__get__
49 * i in x; // calls y.__has__
50 * x.p = 10; // calls y.__put__
51 * delete x.p; // calls y.__delete__
52 * for (i in x) { print(i); } // calls y.__getIds__
53 *
54 * If a special JavaScript method is not found in the adaptee, then JSAdapter
55 * forwards the property access to the adaptee itself.
56 *
57 * JavaScript caller of adapter object is isolated from the fact that
58 * the property access/mutation/deletion are really calls to
59 * JavaScript methods on adaptee. Use cases include 'smart'
60 * properties, property access tracing/debugging, encaptulation with
61 * easy client access - in short JavaScript becomes more "Self" like.
62 *
63 * Note that Rhino already supports special properties like __proto__
64 * (to set, get prototype), __parent__ (to set, get parent scope). We
65 * follow the same double underscore nameing convention here. Similarly
66 * the name JSAdapter is derived from JavaAdapter -- which is a facility
67 * to extend, implement Java classes/interfaces by JavaScript.
68 *
69 * @author A. Sundararajan
70 * @since 1.6
71 */
72public final class JSAdapter implements Scriptable, Function {
73 private JSAdapter(Scriptable obj) {
74 setAdaptee(obj);
75 }
76
77 // initializer to setup JSAdapter prototype in the given scope
78 public static void init(Context cx, Scriptable scope, boolean sealed)
79 throws RhinoException {
80 JSAdapter obj = new JSAdapter(cx.newObject(scope));
81 obj.setParentScope(scope);
82 obj.setPrototype(getFunctionPrototype(scope));
83 obj.isPrototype = true;
84 ScriptableObject.defineProperty(scope, "JSAdapter", obj,
85 ScriptableObject.DONTENUM);
86 }
87
88 public String getClassName() {
89 return "JSAdapter";
90 }
91
92 public Object get(String name, Scriptable start) {
93 Function func = getAdapteeFunction(GET_PROP);
94 if (func != null) {
95 return call(func, new Object[] { name });
96 } else {
97 start = getAdaptee();
98 return start.get(name, start);
99 }
100 }
101
102 public Object get(int index, Scriptable start) {
103 Function func = getAdapteeFunction(GET_PROP);
104 if (func != null) {
105 return call(func, new Object[] { new Integer(index) });
106 } else {
107 start = getAdaptee();
108 return start.get(index, start);
109 }
110 }
111
112 public boolean has(String name, Scriptable start) {
113 Function func = getAdapteeFunction(HAS_PROP);
114 if (func != null) {
115 Object res = call(func, new Object[] { name });
116 return Context.toBoolean(res);
117 } else {
118 start = getAdaptee();
119 return start.has(name, start);
120 }
121 }
122
123 public boolean has(int index, Scriptable start) {
124 Function func = getAdapteeFunction(HAS_PROP);
125 if (func != null) {
126 Object res = call(func, new Object[] { new Integer(index) });
127 return Context.toBoolean(res);
128 } else {
129 start = getAdaptee();
130 return start.has(index, start);
131 }
132 }
133
134 public void put(String name, Scriptable start, Object value) {
135 if (start == this) {
136 Function func = getAdapteeFunction(PUT_PROP);
137 if (func != null) {
138 call(func, new Object[] { name, value });
139 } else {
140 start = getAdaptee();
141 start.put(name, start, value);
142 }
143 } else {
144 start.put(name, start, value);
145 }
146 }
147
148 public void put(int index, Scriptable start, Object value) {
149 if (start == this) {
150 Function func = getAdapteeFunction(PUT_PROP);
151 if( func != null) {
152 call(func, new Object[] { new Integer(index), value });
153 } else {
154 start = getAdaptee();
155 start.put(index, start, value);
156 }
157 } else {
158 start.put(index, start, value);
159 }
160 }
161
162 public void delete(String name) {
163 Function func = getAdapteeFunction(DEL_PROP);
164 if (func != null) {
165 call(func, new Object[] { name });
166 } else {
167 getAdaptee().delete(name);
168 }
169 }
170
171 public void delete(int index) {
172 Function func = getAdapteeFunction(DEL_PROP);
173 if (func != null) {
174 call(func, new Object[] { new Integer(index) });
175 } else {
176 getAdaptee().delete(index);
177 }
178 }
179
180 public Scriptable getPrototype() {
181 return prototype;
182 }
183
184 public void setPrototype(Scriptable prototype) {
185 this.prototype = prototype;
186 }
187
188 public Scriptable getParentScope() {
189 return parent;
190 }
191
192 public void setParentScope(Scriptable parent) {
193 this.parent = parent;
194 }
195
196 public Object[] getIds() {
197 Function func = getAdapteeFunction(GET_PROPIDS);
198 if (func != null) {
199 Object val = call(func, new Object[0]);
200 // in most cases, adaptee would return native JS array
201 if (val instanceof NativeArray) {
202 NativeArray array = (NativeArray) val;
203 Object[] res = new Object[(int)array.getLength()];
204 for (int index = 0; index < res.length; index++) {
205 res[index] = mapToId(array.get(index, array));
206 }
207 return res;
208 } else if (val instanceof NativeJavaArray) {
209 // may be attempt wrapped Java array
210 Object tmp = ((NativeJavaArray)val).unwrap();
211 Object[] res;
212 if (tmp.getClass() == Object[].class) {
213 Object[] array = (Object[]) tmp;
214 res = new Object[array.length];
215 for (int index = 0; index < array.length; index++) {
216 res[index] = mapToId(array[index]);
217 }
218 } else {
219 // just return an empty array
220 res = Context.emptyArgs;
221 }
222 return res;
223 } else {
224 // some other return type, just return empty array
225 return Context.emptyArgs;
226 }
227 } else {
228 return getAdaptee().getIds();
229 }
230 }
231
232 public boolean hasInstance(Scriptable scriptable) {
233 if (scriptable instanceof JSAdapter) {
234 return true;
235 } else {
236 Scriptable proto = scriptable.getPrototype();
237 while (proto != null) {
238 if (proto.equals(this)) return true;
239 proto = proto.getPrototype();
240 }
241 return false;
242 }
243 }
244
245 public Object getDefaultValue(Class hint) {
246 return getAdaptee().getDefaultValue(hint);
247 }
248
249 public Object call(Context cx, Scriptable scope, Scriptable thisObj,
250 Object[] args)
251 throws RhinoException {
252 if (isPrototype) {
253 return construct(cx, scope, args);
254 } else {
255 Scriptable tmp = getAdaptee();
256 if (tmp instanceof Function) {
257 return ((Function)tmp).call(cx, scope, tmp, args);
258 } else {
259 throw Context.reportRuntimeError("TypeError: not a function");
260 }
261 }
262 }
263
264 public Scriptable construct(Context cx, Scriptable scope, Object[] args)
265 throws RhinoException {
266 if (isPrototype) {
267 Scriptable topLevel = ScriptableObject.getTopLevelScope(scope);
268 JSAdapter newObj;
269 if (args.length > 0) {
270 newObj = new JSAdapter(Context.toObject(args[0], topLevel));
271 } else {
272 throw Context.reportRuntimeError("JSAdapter requires adaptee");
273 }
274 return newObj;
275 } else {
276 Scriptable tmp = getAdaptee();
277 if (tmp instanceof Function) {
278 return ((Function)tmp).construct(cx, scope, args);
279 } else {
280 throw Context.reportRuntimeError("TypeError: not a constructor");
281 }
282 }
283 }
284
285 public Scriptable getAdaptee() {
286 return adaptee;
287 }
288
289 public void setAdaptee(Scriptable adaptee) {
290 if (adaptee == null) {
291 throw new NullPointerException("adaptee can not be null");
292 }
293 this.adaptee = adaptee;
294 }
295
296 //-- internals only below this point
297
298 // map a property id. Property id can only be an Integer or String
299 private Object mapToId(Object tmp) {
300 if (tmp instanceof Double) {
301 return new Integer(((Double)tmp).intValue());
302 } else {
303 return Context.toString(tmp);
304 }
305 }
306
307 private static Scriptable getFunctionPrototype(Scriptable scope) {
308 return ScriptableObject.getFunctionPrototype(scope);
309 }
310
311 private Function getAdapteeFunction(String name) {
312 Object o = ScriptableObject.getProperty(getAdaptee(), name);
313 return (o instanceof Function)? (Function)o : null;
314 }
315
316 private Object call(Function func, Object[] args) {
317 Context cx = Context.getCurrentContext();
318 Scriptable thisObj = getAdaptee();
319 Scriptable scope = func.getParentScope();
320 try {
321 return func.call(cx, scope, thisObj, args);
322 } catch (RhinoException re) {
323 throw Context.reportRuntimeError(re.getMessage());
324 }
325 }
326
327 private Scriptable prototype;
328 private Scriptable parent;
329 private Scriptable adaptee;
330 private boolean isPrototype;
331
332 // names of adaptee JavaScript functions
333 private static final String GET_PROP = "__get__";
334 private static final String HAS_PROP = "__has__";
335 private static final String PUT_PROP = "__put__";
336 private static final String DEL_PROP = "__delete__";
337 private static final String GET_PROPIDS = "__getIds__";
338}