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