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 */
25
26 package jdk.nashorn.api.scripting;
27
28 import java.nio.ByteBuffer;
29 import java.security.AccessControlContext;
30 import java.security.AccessController;
31 import java.security.Permissions;
32 import java.security.PrivilegedAction;
33 import java.security.ProtectionDomain;
34 import java.util.AbstractMap;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Iterator;
39 import java.util.LinkedHashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.concurrent.Callable;
45 import javax.script.Bindings;
46 import jdk.nashorn.internal.objects.Global;
47 import jdk.nashorn.internal.runtime.ConsString;
48 import jdk.nashorn.internal.runtime.Context;
49 import jdk.nashorn.internal.runtime.JSType;
50 import jdk.nashorn.internal.runtime.ScriptFunction;
51 import jdk.nashorn.internal.runtime.ScriptObject;
52 import jdk.nashorn.internal.runtime.ScriptRuntime;
53 import jdk.nashorn.internal.runtime.arrays.ArrayData;
54 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
55
56 /**
57 * Mirror object that wraps a given Nashorn Script object.
58 *
59 * @since 1.8u40
60 */
61 @jdk.Exported
62 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
63 private static AccessControlContext getContextAccCtxt() {
64 final Permissions perms = new Permissions();
65 perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
66 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
67 }
68
69 private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
70
71 private final ScriptObject sobj;
72 private final Global global;
73 private final boolean strict;
74
75 @Override
76 public boolean equals(final Object other) {
77 if (other instanceof ScriptObjectMirror) {
78 return sobj.equals(((ScriptObjectMirror)other).sobj);
79 }
80
81 return false;
82 }
83
84 @Override
85 public int hashCode() {
86 return sobj.hashCode();
87 }
88
89 @Override
90 public String toString() {
91 return inGlobal(new Callable<String>() {
92 @Override
93 public String call() {
94 return ScriptRuntime.safeToString(sobj);
95 }
96 });
97 }
98
99 // JSObject methods
100
101 @Override
102 public Object call(final Object thiz, final Object... args) {
103 final Global oldGlobal = Context.getGlobal();
104 final boolean globalChanged = (oldGlobal != global);
105
106 try {
107 if (globalChanged) {
108 Context.setGlobal(global);
109 }
110
111 if (sobj instanceof ScriptFunction) {
112 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
113 final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
114 return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
115 }
116
117 throw new RuntimeException("not a function: " + toString());
118 } catch (final NashornException ne) {
119 throw ne.initEcmaError(global);
120 } catch (final RuntimeException | Error e) {
121 throw e;
122 } catch (final Throwable t) {
123 throw new RuntimeException(t);
124 } finally {
125 if (globalChanged) {
126 Context.setGlobal(oldGlobal);
127 }
128 }
129 }
130
131 @Override
132 public Object newObject(final Object... args) {
133 final Global oldGlobal = Context.getGlobal();
134 final boolean globalChanged = (oldGlobal != global);
135
136 try {
137 if (globalChanged) {
138 Context.setGlobal(global);
139 }
140
141 if (sobj instanceof ScriptFunction) {
142 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
143 return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
144 }
145
146 throw new RuntimeException("not a constructor: " + toString());
147 } catch (final NashornException ne) {
148 throw ne.initEcmaError(global);
149 } catch (final RuntimeException | Error e) {
150 throw e;
151 } catch (final Throwable t) {
152 throw new RuntimeException(t);
153 } finally {
154 if (globalChanged) {
155 Context.setGlobal(oldGlobal);
156 }
157 }
158 }
159
160 @Override
161 public Object eval(final String s) {
162 return inGlobal(new Callable<Object>() {
163 @Override
164 public Object call() {
165 final Context context = AccessController.doPrivileged(
166 new PrivilegedAction<Context>() {
167 @Override
168 public Context run() {
169 return Context.getContext();
170 }
171 }, GET_CONTEXT_ACC_CTXT);
172 return wrap(context.eval(global, s, sobj, null, false), global);
173 }
174 });
175 }
176
177 /**
178 * Call member function
179 * @param functionName function name
180 * @param args arguments
181 * @return return value of function
182 */
183 public Object callMember(final String functionName, final Object... args) {
184 Objects.requireNonNull(functionName);
185 final Global oldGlobal = Context.getGlobal();
186 final boolean globalChanged = (oldGlobal != global);
187
188 try {
189 if (globalChanged) {
190 Context.setGlobal(global);
191 }
192
193 final Object val = sobj.get(functionName);
194 if (val instanceof ScriptFunction) {
195 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
196 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
197 } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
198 return ((JSObject)val).call(sobj, args);
199 }
200
201 throw new NoSuchMethodException("No such function " + functionName);
202 } catch (final NashornException ne) {
203 throw ne.initEcmaError(global);
204 } catch (final RuntimeException | Error e) {
205 throw e;
206 } catch (final Throwable t) {
207 throw new RuntimeException(t);
208 } finally {
209 if (globalChanged) {
210 Context.setGlobal(oldGlobal);
211 }
212 }
213 }
214
215 @Override
216 public Object getMember(final String name) {
217 Objects.requireNonNull(name);
218 return inGlobal(new Callable<Object>() {
219 @Override public Object call() {
220 return wrap(sobj.get(name), global);
221 }
222 });
223 }
224
225 @Override
226 public Object getSlot(final int index) {
227 return inGlobal(new Callable<Object>() {
228 @Override public Object call() {
229 return wrap(sobj.get(index), global);
230 }
231 });
232 }
233
234 @Override
235 public boolean hasMember(final String name) {
236 Objects.requireNonNull(name);
237 return inGlobal(new Callable<Boolean>() {
238 @Override public Boolean call() {
239 return sobj.has(name);
240 }
241 });
242 }
243
244 @Override
245 public boolean hasSlot(final int slot) {
246 return inGlobal(new Callable<Boolean>() {
247 @Override public Boolean call() {
248 return sobj.has(slot);
249 }
250 });
251 }
252
253 @Override
254 public void removeMember(final String name) {
255 Objects.requireNonNull(name);
256 remove(name);
257 }
258
259 @Override
260 public void setMember(final String name, final Object value) {
261 Objects.requireNonNull(name);
262 put(name, value);
263 }
264
265 @Override
266 public void setSlot(final int index, final Object value) {
267 inGlobal(new Callable<Void>() {
268 @Override public Void call() {
269 sobj.set(index, unwrap(value, global), getCallSiteFlags());
270 return null;
271 }
272 });
273 }
274
275 /**
276 * Nashorn extension: setIndexedPropertiesToExternalArrayData.
277 * set indexed properties be exposed from a given nio ByteBuffer.
278 *
279 * @param buf external buffer - should be a nio ByteBuffer
280 */
281 public void setIndexedPropertiesToExternalArrayData(final ByteBuffer buf) {
282 inGlobal(new Callable<Void>() {
283 @Override public Void call() {
284 sobj.setArray(ArrayData.allocate(buf));
285 return null;
286 }
287 });
288 }
289
290
291 @Override
292 public boolean isInstance(final Object obj) {
293 if (! (obj instanceof ScriptObjectMirror)) {
294 return false;
295 }
296
297 final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
298 // if not belongs to my global scope, return false
299 if (global != instance.global) {
300 return false;
301 }
302
303 return inGlobal(new Callable<Boolean>() {
304 @Override public Boolean call() {
305 return sobj.isInstance(instance.sobj);
306 }
307 });
308 }
309
310 @Override
311 public String getClassName() {
312 return sobj.getClassName();
313 }
314
315 @Override
316 public boolean isFunction() {
317 return sobj instanceof ScriptFunction;
318 }
319
320 @Override
321 public boolean isStrictFunction() {
322 return isFunction() && ((ScriptFunction)sobj).isStrict();
323 }
324
325 @Override
326 public boolean isArray() {
327 return sobj.isArray();
328 }
329
330 // javax.script.Bindings methods
331
332 @Override
333 public void clear() {
334 inGlobal(new Callable<Object>() {
335 @Override public Object call() {
336 sobj.clear(strict);
337 return null;
338 }
339 });
340 }
341
342 @Override
343 public boolean containsKey(final Object key) {
344 checkKey(key);
345 return inGlobal(new Callable<Boolean>() {
346 @Override public Boolean call() {
347 return sobj.containsKey(key);
348 }
349 });
350 }
351
352 @Override
353 public boolean containsValue(final Object value) {
354 return inGlobal(new Callable<Boolean>() {
355 @Override public Boolean call() {
356 return sobj.containsValue(unwrap(value, global));
357 }
358 });
359 }
360
361 @Override
362 public Set<Map.Entry<String, Object>> entrySet() {
363 return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
364 @Override public Set<Map.Entry<String, Object>> call() {
365 final Iterator<String> iter = sobj.propertyIterator();
366 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
367
368 while (iter.hasNext()) {
369 final String key = iter.next();
370 final Object value = translateUndefined(wrap(sobj.get(key), global));
371 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
372 }
373
374 return Collections.unmodifiableSet(entries);
375 }
376 });
377 }
378
379 @Override
380 public Object get(final Object key) {
381 checkKey(key);
382 return inGlobal(new Callable<Object>() {
383 @Override public Object call() {
384 return translateUndefined(wrap(sobj.get(key), global));
385 }
386 });
387 }
388
389 @Override
390 public boolean isEmpty() {
391 return inGlobal(new Callable<Boolean>() {
392 @Override public Boolean call() {
393 return sobj.isEmpty();
394 }
395 });
396 }
397
398 @Override
399 public Set<String> keySet() {
400 return inGlobal(new Callable<Set<String>>() {
401 @Override public Set<String> call() {
402 final Iterator<String> iter = sobj.propertyIterator();
403 final Set<String> keySet = new LinkedHashSet<>();
404
405 while (iter.hasNext()) {
406 keySet.add(iter.next());
407 }
408
409 return Collections.unmodifiableSet(keySet);
410 }
411 });
412 }
413
414 @Override
415 public Object put(final String key, final Object value) {
416 checkKey(key);
417 final ScriptObject oldGlobal = Context.getGlobal();
418 final boolean globalChanged = (oldGlobal != global);
419 return inGlobal(new Callable<Object>() {
420 @Override public Object call() {
421 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
422 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
423 }
424 });
425 }
426
427 @Override
428 public void putAll(final Map<? extends String, ? extends Object> map) {
429 Objects.requireNonNull(map, "map is null");
430 final ScriptObject oldGlobal = Context.getGlobal();
431 final boolean globalChanged = (oldGlobal != global);
432 inGlobal(new Callable<Object>() {
433 @Override public Object call() {
434 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
435 final Object value = entry.getValue();
436 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
437 final String key = entry.getKey();
438 checkKey(key);
439 sobj.set(key, unwrap(modValue, global), getCallSiteFlags());
440 }
441 return null;
442 }
443 });
444 }
445
446 @Override
447 public Object remove(final Object key) {
448 checkKey(key);
449 return inGlobal(new Callable<Object>() {
450 @Override public Object call() {
451 return translateUndefined(wrap(sobj.remove(key, strict), global));
452 }
453 });
454 }
455
456 /**
457 * Delete a property from this object.
458 *
459 * @param key the property to be deleted
460 *
461 * @return if the delete was successful or not
462 */
463 public boolean delete(final Object key) {
464 return inGlobal(new Callable<Boolean>() {
465 @Override public Boolean call() {
466 return sobj.delete(unwrap(key, global), strict);
467 }
468 });
469 }
470
471 @Override
472 public int size() {
473 return inGlobal(new Callable<Integer>() {
474 @Override public Integer call() {
475 return sobj.size();
476 }
477 });
478 }
479
480 @Override
481 public Collection<Object> values() {
482 return inGlobal(new Callable<Collection<Object>>() {
483 @Override public Collection<Object> call() {
484 final List<Object> values = new ArrayList<>(size());
485 final Iterator<Object> iter = sobj.valueIterator();
486
487 while (iter.hasNext()) {
488 values.add(translateUndefined(wrap(iter.next(), global)));
489 }
490
491 return Collections.unmodifiableList(values);
492 }
493 });
494 }
495
496 // Support for ECMAScript Object API on mirrors
497
498 /**
499 * Return the __proto__ of this object.
500 * @return __proto__ object.
501 */
502 public Object getProto() {
503 return inGlobal(new Callable<Object>() {
504 @Override public Object call() {
505 return wrap(sobj.getProto(), global);
506 }
507 });
508 }
509
510 /**
511 * Set the __proto__ of this object.
512 * @param proto new proto for this object
513 */
514 public void setProto(final Object proto) {
515 inGlobal(new Callable<Void>() {
516 @Override public Void call() {
517 sobj.setPrototypeOf(unwrap(proto, global));
518 return null;
519 }
520 });
521 }
522
523 /**
524 * ECMA 8.12.1 [[GetOwnProperty]] (P)
525 *
526 * @param key property key
527 *
528 * @return Returns the Property Descriptor of the named own property of this
529 * object, or undefined if absent.
530 */
531 public Object getOwnPropertyDescriptor(final String key) {
532 return inGlobal(new Callable<Object>() {
533 @Override public Object call() {
534 return wrap(sobj.getOwnPropertyDescriptor(key), global);
535 }
536 });
537 }
538
539 /**
540 * return an array of own property keys associated with the object.
541 *
542 * @param all True if to include non-enumerable keys.
543 * @return Array of keys.
544 */
545 public String[] getOwnKeys(final boolean all) {
546 return inGlobal(new Callable<String[]>() {
547 @Override public String[] call() {
548 return sobj.getOwnKeys(all);
549 }
550 });
551 }
552
553 /**
554 * Flag this script object as non extensible
555 *
556 * @return the object after being made non extensible
557 */
558 public ScriptObjectMirror preventExtensions() {
559 return inGlobal(new Callable<ScriptObjectMirror>() {
560 @Override public ScriptObjectMirror call() {
561 sobj.preventExtensions();
562 return ScriptObjectMirror.this;
563 }
564 });
565 }
566
567 /**
568 * Check if this script object is extensible
569 * @return true if extensible
570 */
571 public boolean isExtensible() {
572 return inGlobal(new Callable<Boolean>() {
573 @Override public Boolean call() {
574 return sobj.isExtensible();
575 }
576 });
577 }
578
579 /**
580 * ECMAScript 15.2.3.8 - seal implementation
581 * @return the sealed script object
582 */
583 public ScriptObjectMirror seal() {
584 return inGlobal(new Callable<ScriptObjectMirror>() {
585 @Override public ScriptObjectMirror call() {
586 sobj.seal();
587 return ScriptObjectMirror.this;
588 }
589 });
590 }
591
592 /**
593 * Check whether this script object is sealed
594 * @return true if sealed
595 */
596 public boolean isSealed() {
597 return inGlobal(new Callable<Boolean>() {
598 @Override public Boolean call() {
599 return sobj.isSealed();
600 }
601 });
602 }
603
604 /**
605 * ECMA 15.2.39 - freeze implementation. Freeze this script object
606 * @return the frozen script object
607 */
608 public ScriptObjectMirror freeze() {
609 return inGlobal(new Callable<ScriptObjectMirror>() {
610 @Override public ScriptObjectMirror call() {
611 sobj.freeze();
612 return ScriptObjectMirror.this;
613 }
614 });
615 }
616
617 /**
618 * Check whether this script object is frozen
619 * @return true if frozen
620 */
621 public boolean isFrozen() {
622 return inGlobal(new Callable<Boolean>() {
623 @Override public Boolean call() {
624 return sobj.isFrozen();
625 }
626 });
627 }
628
629 /**
630 * Utility to check if given object is ECMAScript undefined value
631 *
632 * @param obj object to check
633 * @return true if 'obj' is ECMAScript undefined value
634 */
635 public static boolean isUndefined(final Object obj) {
636 return obj == ScriptRuntime.UNDEFINED;
637 }
638
639 /**
640 * Utility to convert this script object to the given type.
641 *
642 * @param <T> destination type to convert to
643 * @param type destination type to convert to
644 * @return converted object
645 */
646 public <T> T to(final Class<T> type) {
647 return inGlobal(new Callable<T>() {
648 @Override
649 public T call() {
650 return type.cast(ScriptUtils.convert(sobj, type));
651 }
652 });
653 }
654
655 /**
656 * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings.
657 *
658 * @param obj object to be wrapped/converted
659 * @param homeGlobal global to which this object belongs. Not used for ConsStrings.
660 * @return wrapped/converted object
661 */
662 public static Object wrap(final Object obj, final Object homeGlobal) {
663 if(obj instanceof ScriptObject) {
664 return homeGlobal instanceof Global ? new ScriptObjectMirror((ScriptObject)obj, (Global)homeGlobal) : obj;
665 }
666 if(obj instanceof ConsString) {
667 return obj.toString();
668 }
669 return obj;
670 }
671
672 /**
673 * Unwrap a script object mirror if needed.
674 *
675 * @param obj object to be unwrapped
676 * @param homeGlobal global to which this object belongs
677 * @return unwrapped object
678 */
679 public static Object unwrap(final Object obj, final Object homeGlobal) {
680 if (obj instanceof ScriptObjectMirror) {
681 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
682 return (mirror.global == homeGlobal)? mirror.sobj : obj;
683 }
684
685 return obj;
686 }
687
688 /**
689 * Wrap an array of object to script object mirrors if needed.
690 *
691 * @param args array to be unwrapped
692 * @param homeGlobal global to which this object belongs
693 * @return wrapped array
694 */
695 public static Object[] wrapArray(final Object[] args, final Object homeGlobal) {
696 if (args == null || args.length == 0) {
697 return args;
698 }
699
700 final Object[] newArgs = new Object[args.length];
701 int index = 0;
702 for (final Object obj : args) {
703 newArgs[index] = wrap(obj, homeGlobal);
704 index++;
705 }
706 return newArgs;
707 }
708
709 /**
710 * Unwrap an array of script object mirrors if needed.
711 *
712 * @param args array to be unwrapped
713 * @param homeGlobal global to which this object belongs
714 * @return unwrapped array
715 */
716 public static Object[] unwrapArray(final Object[] args, final Object homeGlobal) {
717 if (args == null || args.length == 0) {
718 return args;
719 }
720
721 final Object[] newArgs = new Object[args.length];
722 int index = 0;
723 for (final Object obj : args) {
724 newArgs[index] = unwrap(obj, homeGlobal);
725 index++;
726 }
727 return newArgs;
728 }
729
730 /**
731 * Are the given objects mirrors to same underlying object?
732 *
733 * @param obj1 first object
734 * @param obj2 second object
735 * @return true if obj1 and obj2 are identical script objects or mirrors of it.
736 */
737 public static boolean identical(final Object obj1, final Object obj2) {
738 final Object o1 = (obj1 instanceof ScriptObjectMirror)?
739 ((ScriptObjectMirror)obj1).sobj : obj1;
740
741 final Object o2 = (obj2 instanceof ScriptObjectMirror)?
742 ((ScriptObjectMirror)obj2).sobj : obj2;
743
744 return o1 == o2;
745 }
746
747 // package-privates below this.
748
749 ScriptObjectMirror(final ScriptObject sobj, final Global global) {
750 assert sobj != null : "ScriptObjectMirror on null!";
751 assert global != null : "home Global is null";
752
753 this.sobj = sobj;
754 this.global = global;
755 this.strict = global.isStrictContext();
756 }
757
758 // accessors for script engine
759 ScriptObject getScriptObject() {
760 return sobj;
761 }
762
763 Global getHomeGlobal() {
764 return global;
765 }
766
767 static Object translateUndefined(final Object obj) {
768 return (obj == ScriptRuntime.UNDEFINED)? null : obj;
769 }
770
771 private int getCallSiteFlags() {
772 return strict ? NashornCallSiteDescriptor.CALLSITE_STRICT : 0;
773 }
774
775 // internals only below this.
776 private <V> V inGlobal(final Callable<V> callable) {
777 final Global oldGlobal = Context.getGlobal();
778 final boolean globalChanged = (oldGlobal != global);
779 if (globalChanged) {
780 Context.setGlobal(global);
781 }
782 try {
783 return callable.call();
784 } catch (final NashornException ne) {
785 throw ne.initEcmaError(global);
786 } catch (final RuntimeException e) {
787 throw e;
788 } catch (final Exception e) {
789 throw new AssertionError("Cannot happen", e);
790 } finally {
791 if (globalChanged) {
792 Context.setGlobal(oldGlobal);
793 }
794 }
795 }
796
797 /**
798 * Ensures the key is not null, empty string, or a non-String object. The contract of the {@link Bindings}
799 * interface requires that these are not accepted as keys.
800 * @param key the key to check
801 * @throws NullPointerException if key is null
802 * @throws ClassCastException if key is not a String
803 * @throws IllegalArgumentException if key is empty string
804 */
805 private static void checkKey(final Object key) {
806 Objects.requireNonNull(key, "key can not be null");
807
808 if (!(key instanceof String)) {
809 throw new ClassCastException("key should be a String. It is " + key.getClass().getName() + " instead.");
810 } else if (((String)key).length() == 0) {
811 throw new IllegalArgumentException("key can not be empty");
812 }
813 }
814
815 @Override
816 public double toNumber() {
817 return inGlobal(new Callable<Double>() {
818 @Override public Double call() {
819 return JSType.toNumber(sobj);
820 }
821 });
822 }
823 }
--- EOF ---