Wed, 23 Oct 2013 17:30:13 +0530
8027128: jdk.nashorn.api.scripting.JSObject should be an interface
Reviewed-by: hannesw, attila, jlaskey
1 /*
2 * Copyright (c) 2010, 2013, 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 */
26 package jdk.nashorn.api.scripting;
28 import java.security.AccessControlContext;
29 import java.security.AccessController;
30 import java.security.Permissions;
31 import java.security.PrivilegedAction;
32 import java.security.ProtectionDomain;
33 import java.util.AbstractMap;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Iterator;
38 import java.util.LinkedHashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.Callable;
43 import javax.script.Bindings;
44 import jdk.nashorn.internal.runtime.Context;
45 import jdk.nashorn.internal.runtime.GlobalObject;
46 import jdk.nashorn.internal.runtime.JSType;
47 import jdk.nashorn.internal.runtime.ScriptFunction;
48 import jdk.nashorn.internal.runtime.ScriptObject;
49 import jdk.nashorn.internal.runtime.ScriptRuntime;
51 /**
52 * Mirror object that wraps a given Nashorn Script object.
53 */
54 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
55 private static AccessControlContext getContextAccCtxt() {
56 final Permissions perms = new Permissions();
57 perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
58 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
59 }
61 private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
63 private final ScriptObject sobj;
64 private final ScriptObject global;
65 private final boolean strict;
67 @Override
68 public boolean equals(final Object other) {
69 if (other instanceof ScriptObjectMirror) {
70 return sobj.equals(((ScriptObjectMirror)other).sobj);
71 }
73 return false;
74 }
76 @Override
77 public int hashCode() {
78 return sobj.hashCode();
79 }
81 @Override
82 public String toString() {
83 return inGlobal(new Callable<String>() {
84 @Override
85 public String call() {
86 return ScriptRuntime.safeToString(sobj);
87 }
88 });
89 }
91 // JSObject methods
93 @Override
94 public Object call(final Object thiz, final Object... args) {
95 final ScriptObject oldGlobal = Context.getGlobal();
96 final boolean globalChanged = (oldGlobal != global);
98 try {
99 if (globalChanged) {
100 Context.setGlobal(global);
101 }
103 if (sobj instanceof ScriptFunction) {
104 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
105 final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
106 return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
107 }
109 throw new RuntimeException("not a function: " + toString());
110 } catch (final RuntimeException | Error e) {
111 throw e;
112 } catch (final Throwable t) {
113 throw new RuntimeException(t);
114 } finally {
115 if (globalChanged) {
116 Context.setGlobal(oldGlobal);
117 }
118 }
119 }
121 @Override
122 public Object newObject(final Object... args) {
123 final ScriptObject oldGlobal = Context.getGlobal();
124 final boolean globalChanged = (oldGlobal != global);
126 try {
127 if (globalChanged) {
128 Context.setGlobal(global);
129 }
131 if (sobj instanceof ScriptFunction) {
132 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
133 return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
134 }
136 throw new RuntimeException("not a constructor: " + toString());
137 } catch (final RuntimeException | Error e) {
138 throw e;
139 } catch (final Throwable t) {
140 throw new RuntimeException(t);
141 } finally {
142 if (globalChanged) {
143 Context.setGlobal(oldGlobal);
144 }
145 }
146 }
148 @Override
149 public Object eval(final String s) {
150 return inGlobal(new Callable<Object>() {
151 @Override
152 public Object call() {
153 final Context context = AccessController.doPrivileged(
154 new PrivilegedAction<Context>() {
155 @Override
156 public Context run() {
157 return Context.getContext();
158 }
159 }, GET_CONTEXT_ACC_CTXT);
160 return wrap(context.eval(global, s, null, null, false), global);
161 }
162 });
163 }
165 public Object callMember(final String functionName, final Object... args) {
166 functionName.getClass(); // null check
167 final ScriptObject oldGlobal = Context.getGlobal();
168 final boolean globalChanged = (oldGlobal != global);
170 try {
171 if (globalChanged) {
172 Context.setGlobal(global);
173 }
175 final Object val = sobj.get(functionName);
176 if (val instanceof ScriptFunction) {
177 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
178 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
179 } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
180 return ((JSObject)val).call(sobj, args);
181 }
183 throw new NoSuchMethodException("No such function " + functionName);
184 } catch (final RuntimeException | Error e) {
185 throw e;
186 } catch (final Throwable t) {
187 throw new RuntimeException(t);
188 } finally {
189 if (globalChanged) {
190 Context.setGlobal(oldGlobal);
191 }
192 }
193 }
195 @Override
196 public Object getMember(final String name) {
197 name.getClass();
198 return inGlobal(new Callable<Object>() {
199 @Override public Object call() {
200 return wrap(sobj.get(name), global);
201 }
202 });
203 }
205 @Override
206 public Object getSlot(final int index) {
207 return inGlobal(new Callable<Object>() {
208 @Override public Object call() {
209 return wrap(sobj.get(index), global);
210 }
211 });
212 }
214 @Override
215 public boolean hasMember(final String name) {
216 name.getClass();
217 return inGlobal(new Callable<Boolean>() {
218 @Override public Boolean call() {
219 return sobj.has(name);
220 }
221 });
222 }
224 @Override
225 public boolean hasSlot(final int slot) {
226 return inGlobal(new Callable<Boolean>() {
227 @Override public Boolean call() {
228 return sobj.has(slot);
229 }
230 });
231 }
233 @Override
234 public void removeMember(final String name) {
235 name.getClass();
236 remove(name);
237 }
239 @Override
240 public void setMember(final String name, final Object value) {
241 name.getClass();
242 put(name, value);
243 }
245 @Override
246 public void setSlot(final int index, final Object value) {
247 inGlobal(new Callable<Void>() {
248 @Override public Void call() {
249 sobj.set(index, unwrap(value, global), strict);
250 return null;
251 }
252 });
253 }
255 @Override
256 public boolean isInstance(final Object obj) {
257 if (! (obj instanceof ScriptObjectMirror)) {
258 return false;
259 }
261 final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
262 // if not belongs to my global scope, return false
263 if (global != instance.global) {
264 return false;
265 }
267 return inGlobal(new Callable<Boolean>() {
268 @Override public Boolean call() {
269 return sobj.isInstance(instance.sobj);
270 }
271 });
272 }
274 @Override
275 public String getClassName() {
276 return sobj.getClassName();
277 }
279 @Override
280 public boolean isFunction() {
281 return sobj instanceof ScriptFunction;
282 }
284 @Override
285 public boolean isStrictFunction() {
286 return isFunction() && ((ScriptFunction)sobj).isStrict();
287 }
289 @Override
290 public boolean isArray() {
291 return sobj.isArray();
292 }
294 // javax.script.Bindings methods
296 @Override
297 public void clear() {
298 inGlobal(new Callable<Object>() {
299 @Override public Object call() {
300 sobj.clear(strict);
301 return null;
302 }
303 });
304 }
306 @Override
307 public boolean containsKey(final Object key) {
308 return inGlobal(new Callable<Boolean>() {
309 @Override public Boolean call() {
310 return sobj.containsKey(unwrap(key, global));
311 }
312 });
313 }
315 @Override
316 public boolean containsValue(final Object value) {
317 return inGlobal(new Callable<Boolean>() {
318 @Override public Boolean call() {
319 return sobj.containsValue(unwrap(value, global));
320 }
321 });
322 }
324 @Override
325 public Set<Map.Entry<String, Object>> entrySet() {
326 return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
327 @Override public Set<Map.Entry<String, Object>> call() {
328 final Iterator<String> iter = sobj.propertyIterator();
329 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
331 while (iter.hasNext()) {
332 final String key = iter.next();
333 final Object value = translateUndefined(wrap(sobj.get(key), global));
334 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
335 }
337 return Collections.unmodifiableSet(entries);
338 }
339 });
340 }
342 @Override
343 public Object get(final Object key) {
344 return inGlobal(new Callable<Object>() {
345 @Override public Object call() {
346 return translateUndefined(wrap(sobj.get(key), global));
347 }
348 });
349 }
351 @Override
352 public boolean isEmpty() {
353 return inGlobal(new Callable<Boolean>() {
354 @Override public Boolean call() {
355 return sobj.isEmpty();
356 }
357 });
358 }
360 @Override
361 public Set<String> keySet() {
362 return inGlobal(new Callable<Set<String>>() {
363 @Override public Set<String> call() {
364 final Iterator<String> iter = sobj.propertyIterator();
365 final Set<String> keySet = new LinkedHashSet<>();
367 while (iter.hasNext()) {
368 keySet.add(iter.next());
369 }
371 return Collections.unmodifiableSet(keySet);
372 }
373 });
374 }
376 @Override
377 public Object put(final String key, final Object value) {
378 final ScriptObject oldGlobal = Context.getGlobal();
379 final boolean globalChanged = (oldGlobal != global);
380 return inGlobal(new Callable<Object>() {
381 @Override public Object call() {
382 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
383 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
384 }
385 });
386 }
388 @Override
389 public void putAll(final Map<? extends String, ? extends Object> map) {
390 final ScriptObject oldGlobal = Context.getGlobal();
391 final boolean globalChanged = (oldGlobal != global);
392 inGlobal(new Callable<Object>() {
393 @Override public Object call() {
394 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
395 final Object value = entry.getValue();
396 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
397 sobj.set(entry.getKey(), unwrap(modValue, global), strict);
398 }
399 return null;
400 }
401 });
402 }
404 @Override
405 public Object remove(final Object key) {
406 return inGlobal(new Callable<Object>() {
407 @Override public Object call() {
408 return wrap(sobj.remove(unwrap(key, global), strict), global);
409 }
410 });
411 }
413 /**
414 * Delete a property from this object.
415 *
416 * @param key the property to be deleted
417 *
418 * @return if the delete was successful or not
419 */
420 public boolean delete(final Object key) {
421 return inGlobal(new Callable<Boolean>() {
422 @Override public Boolean call() {
423 return sobj.delete(unwrap(key, global), strict);
424 }
425 });
426 }
428 @Override
429 public int size() {
430 return inGlobal(new Callable<Integer>() {
431 @Override public Integer call() {
432 return sobj.size();
433 }
434 });
435 }
437 @Override
438 public Collection<Object> values() {
439 return inGlobal(new Callable<Collection<Object>>() {
440 @Override public Collection<Object> call() {
441 final List<Object> values = new ArrayList<>(size());
442 final Iterator<Object> iter = sobj.valueIterator();
444 while (iter.hasNext()) {
445 values.add(translateUndefined(wrap(iter.next(), global)));
446 }
448 return Collections.unmodifiableList(values);
449 }
450 });
451 }
453 // Support for ECMAScript Object API on mirrors
455 /**
456 * Return the __proto__ of this object.
457 * @return __proto__ object.
458 */
459 public Object getProto() {
460 return inGlobal(new Callable<Object>() {
461 @Override public Object call() {
462 return wrap(sobj.getProto(), global);
463 }
464 });
465 }
467 /**
468 * Set the __proto__ of this object.
469 * @param proto new proto for this object
470 */
471 public void setProto(final Object proto) {
472 inGlobal(new Callable<Void>() {
473 @Override public Void call() {
474 sobj.setProtoCheck(unwrap(proto, global));
475 return null;
476 }
477 });
478 }
480 /**
481 * ECMA 8.12.1 [[GetOwnProperty]] (P)
482 *
483 * @param key property key
484 *
485 * @return Returns the Property Descriptor of the named own property of this
486 * object, or undefined if absent.
487 */
488 public Object getOwnPropertyDescriptor(final String key) {
489 return inGlobal(new Callable<Object>() {
490 @Override public Object call() {
491 return wrap(sobj.getOwnPropertyDescriptor(key), global);
492 }
493 });
494 }
496 /**
497 * return an array of own property keys associated with the object.
498 *
499 * @param all True if to include non-enumerable keys.
500 * @return Array of keys.
501 */
502 public String[] getOwnKeys(final boolean all) {
503 return inGlobal(new Callable<String[]>() {
504 @Override public String[] call() {
505 return sobj.getOwnKeys(all);
506 }
507 });
508 }
510 /**
511 * Flag this script object as non extensible
512 *
513 * @return the object after being made non extensible
514 */
515 public ScriptObjectMirror preventExtensions() {
516 return inGlobal(new Callable<ScriptObjectMirror>() {
517 @Override public ScriptObjectMirror call() {
518 sobj.preventExtensions();
519 return ScriptObjectMirror.this;
520 }
521 });
522 }
524 /**
525 * Check if this script object is extensible
526 * @return true if extensible
527 */
528 public boolean isExtensible() {
529 return inGlobal(new Callable<Boolean>() {
530 @Override public Boolean call() {
531 return sobj.isExtensible();
532 }
533 });
534 }
536 /**
537 * ECMAScript 15.2.3.8 - seal implementation
538 * @return the sealed script object
539 */
540 public ScriptObjectMirror seal() {
541 return inGlobal(new Callable<ScriptObjectMirror>() {
542 @Override public ScriptObjectMirror call() {
543 sobj.seal();
544 return ScriptObjectMirror.this;
545 }
546 });
547 }
549 /**
550 * Check whether this script object is sealed
551 * @return true if sealed
552 */
553 public boolean isSealed() {
554 return inGlobal(new Callable<Boolean>() {
555 @Override public Boolean call() {
556 return sobj.isSealed();
557 }
558 });
559 }
561 /**
562 * ECMA 15.2.39 - freeze implementation. Freeze this script object
563 * @return the frozen script object
564 */
565 public ScriptObjectMirror freeze() {
566 return inGlobal(new Callable<ScriptObjectMirror>() {
567 @Override public ScriptObjectMirror call() {
568 sobj.freeze();
569 return ScriptObjectMirror.this;
570 }
571 });
572 }
574 /**
575 * Check whether this script object is frozen
576 * @return true if frozen
577 */
578 public boolean isFrozen() {
579 return inGlobal(new Callable<Boolean>() {
580 @Override public Boolean call() {
581 return sobj.isFrozen();
582 }
583 });
584 }
586 /**
587 * Utility to check if given object is ECMAScript undefined value
588 *
589 * @param obj object to check
590 * @return true if 'obj' is ECMAScript undefined value
591 */
592 public static boolean isUndefined(final Object obj) {
593 return obj == ScriptRuntime.UNDEFINED;
594 }
596 /**
597 * Make a script object mirror on given object if needed.
598 *
599 * @param obj object to be wrapped
600 * @param homeGlobal global to which this object belongs
601 * @return wrapped object
602 */
603 public static Object wrap(final Object obj, final ScriptObject homeGlobal) {
604 return (obj instanceof ScriptObject && homeGlobal != null) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj;
605 }
607 /**
608 * Unwrap a script object mirror if needed.
609 *
610 * @param obj object to be unwrapped
611 * @param homeGlobal global to which this object belongs
612 * @return unwrapped object
613 */
614 public static Object unwrap(final Object obj, final ScriptObject homeGlobal) {
615 if (obj instanceof ScriptObjectMirror) {
616 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
617 return (mirror.global == homeGlobal)? mirror.sobj : obj;
618 }
620 return obj;
621 }
623 /**
624 * Wrap an array of object to script object mirrors if needed.
625 *
626 * @param args array to be unwrapped
627 * @param homeGlobal global to which this object belongs
628 * @return wrapped array
629 */
630 public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) {
631 if (args == null || args.length == 0) {
632 return args;
633 }
635 final Object[] newArgs = new Object[args.length];
636 int index = 0;
637 for (final Object obj : args) {
638 newArgs[index] = wrap(obj, homeGlobal);
639 index++;
640 }
641 return newArgs;
642 }
644 /**
645 * Unwrap an array of script object mirrors if needed.
646 *
647 * @param args array to be unwrapped
648 * @param homeGlobal global to which this object belongs
649 * @return unwrapped array
650 */
651 public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) {
652 if (args == null || args.length == 0) {
653 return args;
654 }
656 final Object[] newArgs = new Object[args.length];
657 int index = 0;
658 for (final Object obj : args) {
659 newArgs[index] = unwrap(obj, homeGlobal);
660 index++;
661 }
662 return newArgs;
663 }
665 // package-privates below this.
667 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) {
668 assert sobj != null : "ScriptObjectMirror on null!";
669 assert global instanceof GlobalObject : "global is not a GlobalObject";
671 this.sobj = sobj;
672 this.global = global;
673 this.strict = ((GlobalObject)global).isStrictContext();
674 }
676 // accessors for script engine
677 ScriptObject getScriptObject() {
678 return sobj;
679 }
681 ScriptObject getHomeGlobal() {
682 return global;
683 }
685 static Object translateUndefined(Object obj) {
686 return (obj == ScriptRuntime.UNDEFINED)? null : obj;
687 }
689 // internals only below this.
690 private <V> V inGlobal(final Callable<V> callable) {
691 final ScriptObject oldGlobal = Context.getGlobal();
692 final boolean globalChanged = (oldGlobal != global);
693 if (globalChanged) {
694 Context.setGlobal(global);
695 }
696 try {
697 return callable.call();
698 } catch (final RuntimeException e) {
699 throw e;
700 } catch (final Exception e) {
701 throw new AssertionError("Cannot happen", e);
702 } finally {
703 if (globalChanged) {
704 Context.setGlobal(oldGlobal);
705 }
706 }
707 }
709 @Override
710 public double toNumber() {
711 return inGlobal(new Callable<Double>() {
712 @Override public Double call() {
713 return JSType.toNumber(sobj);
714 }
715 });
716 }
717 }