jlaskey@3: /* jlaskey@7: * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. jlaskey@3: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jlaskey@3: * jlaskey@3: * This code is free software; you can redistribute it and/or modify it jlaskey@3: * under the terms of the GNU General Public License version 2 only, as jlaskey@3: * published by the Free Software Foundation. Oracle designates this jlaskey@3: * particular file as subject to the "Classpath" exception as provided jlaskey@3: * by Oracle in the LICENSE file that accompanied this code. jlaskey@3: * jlaskey@3: * This code is distributed in the hope that it will be useful, but WITHOUT jlaskey@3: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jlaskey@3: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jlaskey@3: * version 2 for more details (a copy is included in the LICENSE file that jlaskey@3: * accompanied this code). jlaskey@3: * jlaskey@3: * You should have received a copy of the GNU General Public License version jlaskey@3: * 2 along with this work; if not, write to the Free Software Foundation, jlaskey@3: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jlaskey@3: * jlaskey@3: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jlaskey@3: * or visit www.oracle.com if you need additional information or have any jlaskey@3: * questions. jlaskey@3: */ jlaskey@3: jlaskey@3: package jdk.nashorn.api.scripting; jlaskey@3: sundar@41: import java.security.AccessController; sundar@41: import java.security.PrivilegedAction; jlaskey@3: import java.util.AbstractMap; jlaskey@3: import java.util.ArrayList; jlaskey@3: import java.util.Collection; jlaskey@3: import java.util.Collections; sundar@321: import java.util.LinkedHashSet; jlaskey@3: import java.util.Iterator; jlaskey@3: import java.util.List; jlaskey@3: import java.util.Map; jlaskey@3: import java.util.Set; jlaskey@3: import java.util.concurrent.Callable; sundar@38: import javax.script.Bindings; jlaskey@3: import jdk.nashorn.internal.runtime.Context; jlaskey@3: import jdk.nashorn.internal.runtime.ScriptFunction; jlaskey@3: import jdk.nashorn.internal.runtime.ScriptObject; sundar@33: import jdk.nashorn.internal.runtime.ScriptRuntime; jlaskey@3: jlaskey@3: /** jlaskey@3: * Mirror object that wraps a given ScriptObject instance. User can sundar@38: * access ScriptObject via the javax.script.Bindings interface or sundar@38: * netscape.javascript.JSObject interface. jlaskey@3: */ sundar@321: public final class ScriptObjectMirror extends JSObject implements Bindings { jlaskey@3: private final ScriptObject sobj; jlaskey@3: private final ScriptObject global; jlaskey@3: jlaskey@3: @Override jlaskey@3: public boolean equals(final Object other) { jlaskey@3: if (other instanceof ScriptObjectMirror) { jlaskey@3: return sobj.equals(((ScriptObjectMirror)other).sobj); jlaskey@3: } jlaskey@3: jlaskey@3: return false; jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public int hashCode() { jlaskey@3: return sobj.hashCode(); jlaskey@3: } jlaskey@3: sundar@33: @Override sundar@33: public String toString() { sundar@33: return inGlobal(new Callable() { sundar@33: @Override sundar@33: public String call() { sundar@33: return ScriptRuntime.safeToString(sobj); sundar@33: } sundar@33: }); sundar@33: } sundar@33: jlaskey@3: // JSObject methods jlaskey@3: @Override sundar@376: public Object call(final String functionName, final Object... args) { sundar@44: final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); jlaskey@3: final boolean globalChanged = (oldGlobal != global); jlaskey@3: sundar@88: try { sundar@88: if (globalChanged) { sundar@88: NashornScriptEngine.setNashornGlobal(global); jlaskey@3: } jlaskey@3: sundar@376: final Object val = functionName == null? sobj : sobj.get(functionName); sundar@88: if (! (val instanceof ScriptFunction)) { sundar@376: throw new RuntimeException("No such function " + ((functionName != null)? functionName : "")); jlaskey@3: } jlaskey@3: sundar@88: final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; sundar@88: return wrap(ScriptRuntime.checkAndApply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global); sundar@88: } catch (final RuntimeException | Error e) { sundar@88: throw e; sundar@88: } catch (final Throwable t) { sundar@88: throw new RuntimeException(t); sundar@88: } finally { sundar@88: if (globalChanged) { sundar@88: NashornScriptEngine.setNashornGlobal(oldGlobal); sundar@88: } sundar@88: } jlaskey@3: } jlaskey@3: jlaskey@3: @Override sundar@376: public Object newObject(final String functionName, final Object... args) { sundar@376: final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); sundar@376: final boolean globalChanged = (oldGlobal != global); sundar@376: sundar@376: try { sundar@376: if (globalChanged) { sundar@376: NashornScriptEngine.setNashornGlobal(global); sundar@376: } sundar@376: sundar@376: final Object val = functionName == null? sobj : sobj.get(functionName); sundar@376: if (! (val instanceof ScriptFunction)) { sundar@376: throw new RuntimeException("not a constructor " + ((functionName != null)? functionName : "")); sundar@376: } sundar@376: sundar@376: final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; sundar@376: return wrap(ScriptRuntime.checkAndConstruct((ScriptFunction)val, unwrapArray(modArgs, global)), global); sundar@376: } catch (final RuntimeException | Error e) { sundar@376: throw e; sundar@376: } catch (final Throwable t) { sundar@376: throw new RuntimeException(t); sundar@376: } finally { sundar@376: if (globalChanged) { sundar@376: NashornScriptEngine.setNashornGlobal(oldGlobal); sundar@376: } sundar@376: } sundar@376: } sundar@376: sundar@376: @Override jlaskey@3: public Object eval(final String s) { jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override jlaskey@3: public Object call() { sundar@41: final Context context = AccessController.doPrivileged( sundar@41: new PrivilegedAction() { sundar@41: @Override sundar@41: public Context run() { sundar@41: return Context.getContext(); sundar@41: } sundar@41: }); sundar@41: return wrap(context.eval(global, s, null, null, false), global); jlaskey@3: } jlaskey@3: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public Object getMember(final String name) { sundar@38: return inGlobal(new Callable() { sundar@38: @Override public Object call() { sundar@38: return wrap(sobj.get(name), global); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public Object getSlot(final int index) { sundar@38: return inGlobal(new Callable() { sundar@38: @Override public Object call() { sundar@38: return wrap(sobj.get(index), global); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public void removeMember(final String name) { jlaskey@3: remove(name); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public void setMember(final String name, final Object value) { sundar@88: put(name, value); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public void setSlot(final int index, final Object value) { sundar@38: inGlobal(new Callable() { sundar@38: @Override public Void call() { sundar@41: sobj.set(index, unwrap(value, global), global.isStrictContext()); sundar@38: return null; sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: sundar@437: // javax.script.Bindings methods sundar@437: jlaskey@3: @Override jlaskey@3: public void clear() { jlaskey@3: inGlobal(new Callable() { jlaskey@3: @Override public Object call() { jlaskey@3: sobj.clear(); jlaskey@3: return null; sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public boolean containsKey(final Object key) { jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override public Boolean call() { jlaskey@3: return sobj.containsKey(unwrap(key, global)); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public boolean containsValue(final Object value) { jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override public Boolean call() { jlaskey@3: return sobj.containsValue(unwrap(value, global)); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override sundar@38: public Set> entrySet() { sundar@38: return inGlobal(new Callable>>() { sundar@38: @Override public Set> call() { jlaskey@3: final Iterator iter = sobj.propertyIterator(); sundar@321: final Set> entries = new LinkedHashSet<>(); jlaskey@3: jlaskey@3: while (iter.hasNext()) { sundar@38: final String key = iter.next(); sundar@38: final Object value = translateUndefined(wrap(sobj.get(key), global)); jlaskey@3: entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); jlaskey@3: } jlaskey@3: jlaskey@3: return Collections.unmodifiableSet(entries); jlaskey@3: } jlaskey@3: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public Object get(final Object key) { sundar@38: return inGlobal(new Callable() { sundar@38: @Override public Object call() { sundar@38: return translateUndefined(wrap(sobj.get(key), global)); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public boolean isEmpty() { sundar@38: return inGlobal(new Callable() { sundar@38: @Override public Boolean call() { sundar@38: return sobj.isEmpty(); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override sundar@38: public Set keySet() { sundar@38: return inGlobal(new Callable>() { sundar@38: @Override public Set call() { sundar@38: final Iterator iter = sobj.propertyIterator(); sundar@321: final Set keySet = new LinkedHashSet<>(); jlaskey@3: sundar@38: while (iter.hasNext()) { sundar@38: keySet.add(iter.next()); sundar@38: } sundar@38: sundar@38: return Collections.unmodifiableSet(keySet); jlaskey@3: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override sundar@38: public Object put(final String key, final Object value) { sundar@88: final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); sundar@88: final boolean globalChanged = (oldGlobal != global); jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override public Object call() { sundar@88: final Object modValue = globalChanged? wrap(value, oldGlobal) : value; sundar@88: return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global)), global)); sundar@38: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override sundar@38: public void putAll(final Map map) { sundar@88: final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); sundar@88: final boolean globalChanged = (oldGlobal != global); sundar@38: inGlobal(new Callable() { sundar@38: @Override public Object call() { sundar@414: final boolean strict = global.isStrictContext(); sundar@38: for (final Map.Entry entry : map.entrySet()) { sundar@88: final Object value = entry.getValue(); sundar@88: final Object modValue = globalChanged? wrap(value, oldGlobal) : value; sundar@88: sobj.set(entry.getKey(), unwrap(modValue, global), strict); sundar@38: } sundar@38: return null; jlaskey@3: } sundar@38: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public Object remove(final Object key) { jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override public Object call() { jlaskey@3: return wrap(sobj.remove(unwrap(key, global)), global); jlaskey@3: } jlaskey@3: }); jlaskey@3: } jlaskey@3: sundar@321: /** sundar@321: * Delete a property from this object. sundar@321: * sundar@321: * @param key the property to be deleted sundar@321: * sundar@321: * @return if the delete was successful or not sundar@321: */ sundar@321: public boolean delete(final Object key) { sundar@321: return inGlobal(new Callable() { sundar@321: @Override public Boolean call() { sundar@321: return sobj.delete(unwrap(key, global)); sundar@321: } sundar@321: }); sundar@321: } sundar@321: jlaskey@3: @Override jlaskey@3: public int size() { jlaskey@3: return inGlobal(new Callable() { jlaskey@3: @Override public Integer call() { jlaskey@3: return sobj.size(); jlaskey@3: } jlaskey@3: }); jlaskey@3: } jlaskey@3: jlaskey@3: @Override jlaskey@3: public Collection values() { sundar@38: return inGlobal(new Callable>() { sundar@38: @Override public Collection call() { sundar@38: final List values = new ArrayList<>(size()); sundar@38: final Iterator iter = sobj.valueIterator(); jlaskey@3: sundar@38: while (iter.hasNext()) { sundar@88: values.add(translateUndefined(wrap(iter.next(), global))); sundar@38: } sundar@38: sundar@38: return Collections.unmodifiableList(values); jlaskey@3: } sundar@38: }); sundar@38: } jlaskey@3: sundar@350: // Support for ECMAScript Object API on mirrors sundar@350: sundar@350: /** sundar@350: * Return the __proto__ of this object. sundar@350: * @return __proto__ object. sundar@350: */ sundar@350: public Object getProto() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Object call() { sundar@437: return wrap(sobj.getProto(), global); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * ECMA 8.12.1 [[GetOwnProperty]] (P) sundar@350: * sundar@350: * @param key property key sundar@350: * sundar@350: * @return Returns the Property Descriptor of the named own property of this sundar@350: * object, or undefined if absent. sundar@350: */ sundar@350: public Object getOwnPropertyDescriptor(final String key) { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Object call() { sundar@437: return wrap(sobj.getOwnPropertyDescriptor(key), global); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * return an array of own property keys associated with the object. sundar@350: * sundar@350: * @param all True if to include non-enumerable keys. sundar@350: * @return Array of keys. sundar@350: */ sundar@350: public String[] getOwnKeys(final boolean all) { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public String[] call() { sundar@437: return sobj.getOwnKeys(all); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * Flag this script object as non extensible sundar@350: * sundar@350: * @return the object after being made non extensible sundar@350: */ sundar@350: public ScriptObjectMirror preventExtensions() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public ScriptObjectMirror call() { sundar@437: sobj.preventExtensions(); sundar@350: return ScriptObjectMirror.this; sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * Check if this script object is extensible sundar@350: * @return true if extensible sundar@350: */ sundar@350: public boolean isExtensible() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Boolean call() { sundar@437: return sobj.isExtensible(); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * ECMAScript 15.2.3.8 - seal implementation sundar@350: * @return the sealed script object sundar@350: */ sundar@350: public ScriptObjectMirror seal() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public ScriptObjectMirror call() { sundar@437: sobj.seal(); sundar@350: return ScriptObjectMirror.this; sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * Check whether this script object is sealed sundar@350: * @return true if sealed sundar@350: */ sundar@350: public boolean isSealed() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Boolean call() { sundar@437: return sobj.isSealed(); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * ECMA 15.2.39 - freeze implementation. Freeze this script object sundar@350: * @return the frozen script object sundar@350: */ sundar@350: public ScriptObjectMirror freeze() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public ScriptObjectMirror call() { sundar@437: sobj.freeze(); sundar@350: return ScriptObjectMirror.this; sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@350: * Check whether this script object is frozen sundar@350: * @return true if frozen sundar@350: */ sundar@350: public boolean isFrozen() { sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Boolean call() { sundar@437: return sobj.isFrozen(); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: // ECMAScript instanceof check sundar@350: sundar@350: /** sundar@350: * Checking whether a script object is an instance of another by sundar@350: * walking the proto chain sundar@350: * sundar@350: * @param instance instace to check sundar@350: * @return true if 'instance' is an instance of this object sundar@350: */ sundar@350: public boolean isInstance(final ScriptObjectMirror instance) { sundar@350: // if not belongs to my global scope, return false sundar@350: if (instance == null || global != instance.global) { sundar@350: return false; sundar@350: } sundar@350: sundar@350: return inGlobal(new Callable() { sundar@350: @Override public Boolean call() { sundar@437: return sobj.isInstance(instance.sobj); sundar@350: } sundar@350: }); sundar@350: } sundar@350: sundar@350: /** sundar@437: * is this a function object? sundar@437: * sundar@437: * @return if this mirror wraps a ECMAScript function instance sundar@437: */ sundar@437: public boolean isFunction() { sundar@437: return sobj instanceof ScriptFunction; sundar@437: } sundar@437: sundar@437: /** sundar@437: * is this a 'use strict' function object? sundar@437: * sundar@437: * @return true if this mirror represents a ECMAScript 'use strict' function sundar@437: */ sundar@437: public boolean isStrictFunction() { sundar@437: return isFunction() && ((ScriptFunction)sobj).isStrict(); sundar@437: } sundar@437: sundar@437: /** sundar@437: * is this an array object? sundar@437: * sundar@437: * @return if this mirror wraps a ECMAScript array object sundar@437: */ sundar@437: public boolean isArray() { sundar@437: return sobj.isArray(); sundar@437: } sundar@437: sundar@437: /** sundar@350: * Utility to check if given object is ECMAScript undefined value sundar@350: * sundar@350: * @param obj object to check sundar@350: * @return true if 'obj' is ECMAScript undefined value sundar@350: */ sundar@350: public static boolean isUndefined(final Object obj) { sundar@350: return obj == ScriptRuntime.UNDEFINED; sundar@350: } sundar@350: sundar@350: /** sundar@322: * Make a script object mirror on given object if needed. sundar@322: * sundar@322: * @param obj object to be wrapped sundar@322: * @param homeGlobal global to which this object belongs sundar@322: * @return wrapped object sundar@322: */ sundar@322: public static Object wrap(final Object obj, final ScriptObject homeGlobal) { jlaskey@3: return (obj instanceof ScriptObject) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj; jlaskey@3: } jlaskey@3: sundar@322: /** sundar@322: * Unwrap a script object mirror if needed. sundar@322: * sundar@322: * @param obj object to be unwrapped sundar@322: * @param homeGlobal global to which this object belongs sundar@322: * @return unwrapped object sundar@322: */ sundar@322: public static Object unwrap(final Object obj, final ScriptObject homeGlobal) { jlaskey@3: if (obj instanceof ScriptObjectMirror) { jlaskey@3: final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; jlaskey@3: return (mirror.global == homeGlobal)? mirror.sobj : obj; jlaskey@3: } jlaskey@3: jlaskey@3: return obj; jlaskey@3: } jlaskey@3: sundar@322: /** sundar@322: * Wrap an array of object to script object mirrors if needed. sundar@322: * sundar@322: * @param args array to be unwrapped sundar@322: * @param homeGlobal global to which this object belongs sundar@322: * @return wrapped array sundar@322: */ sundar@322: public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) { jlaskey@3: if (args == null || args.length == 0) { jlaskey@3: return args; jlaskey@3: } jlaskey@3: jlaskey@3: final Object[] newArgs = new Object[args.length]; jlaskey@3: int index = 0; jlaskey@3: for (final Object obj : args) { jlaskey@3: newArgs[index] = wrap(obj, homeGlobal); jlaskey@3: index++; jlaskey@3: } jlaskey@3: return newArgs; jlaskey@3: } jlaskey@3: sundar@322: /** sundar@322: * Unwrap an array of script object mirrors if needed. sundar@322: * sundar@322: * @param args array to be unwrapped sundar@322: * @param homeGlobal global to which this object belongs sundar@322: * @return unwrapped array sundar@322: */ sundar@322: public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) { jlaskey@3: if (args == null || args.length == 0) { jlaskey@3: return args; jlaskey@3: } jlaskey@3: jlaskey@3: final Object[] newArgs = new Object[args.length]; jlaskey@3: int index = 0; jlaskey@3: for (final Object obj : args) { jlaskey@3: newArgs[index] = unwrap(obj, homeGlobal); jlaskey@3: index++; jlaskey@3: } jlaskey@3: return newArgs; jlaskey@3: } sundar@322: sundar@322: // package-privates below this. sundar@437: sundar@437: ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { sundar@437: this.sobj = sobj; sundar@437: this.global = global; sundar@437: } sundar@437: sundar@322: ScriptObject getScriptObject() { sundar@322: return sobj; sundar@322: } sundar@322: sundar@322: static Object translateUndefined(Object obj) { sundar@322: return (obj == ScriptRuntime.UNDEFINED)? null : obj; sundar@322: } sundar@437: sundar@437: // internals only below this. sundar@437: private V inGlobal(final Callable callable) { sundar@437: final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); sundar@437: final boolean globalChanged = (oldGlobal != global); sundar@437: if (globalChanged) { sundar@437: NashornScriptEngine.setNashornGlobal(global); sundar@437: } sundar@437: try { sundar@437: return callable.call(); sundar@437: } catch (final RuntimeException e) { sundar@437: throw e; sundar@437: } catch (final Exception e) { sundar@437: throw new AssertionError("Cannot happen", e); sundar@437: } finally { sundar@437: if (globalChanged) { sundar@437: NashornScriptEngine.setNashornGlobal(oldGlobal); sundar@437: } sundar@437: } sundar@437: } sundar@437: jlaskey@3: }