1 /* |
|
2 * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.sun.script.javascript; |
|
27 |
|
28 import sun.org.mozilla.javascript.internal.*; |
|
29 import 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 */ |
|
72 public 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 } |
|