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