| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.objects; |
| |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; |
| import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import jdk.internal.dynalink.CallSiteDescriptor; |
| import jdk.internal.dynalink.linker.GuardedInvocation; |
| import jdk.internal.dynalink.linker.LinkRequest; |
| import jdk.nashorn.internal.lookup.Lookup; |
| import jdk.nashorn.internal.objects.annotations.Constructor; |
| import jdk.nashorn.internal.objects.annotations.ScriptClass; |
| import jdk.nashorn.internal.runtime.FindProperty; |
| import jdk.nashorn.internal.runtime.JSType; |
| import jdk.nashorn.internal.runtime.PropertyMap; |
| import jdk.nashorn.internal.runtime.ScriptFunction; |
| import jdk.nashorn.internal.runtime.ScriptObject; |
| import jdk.nashorn.internal.runtime.ScriptRuntime; |
| import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator; |
| import jdk.nashorn.internal.scripts.JO; |
| |
| /** |
| * This class is the implementation of the Nashorn-specific global object named {@code JSAdapter}. It can be |
| * thought of as the {@link java.lang.reflect.Proxy} equivalent for JavaScript. NativeJSAdapter calls specially named |
| * JavaScript methods on an adaptee object when property access/update/call/new/delete is attempted on it. Example: |
| *<pre> |
| * var y = { |
| * __get__ : function (name) { ... } |
| * __has__ : function (name) { ... } |
| * __put__ : function (name, value) {...} |
| * __call__ : function (name, arg1, arg2) {...} |
| * __new__ : function (arg1, arg2) {...} |
| * __delete__ : function (name) { ... } |
| * __getIds__ : function () { ... } |
| * }; |
| * |
| * var x = new JSAdapter(y); |
| * |
| * x.i; // calls y.__get__ |
| * x.foo(); // calls y.__call__ |
| * new x(); // calls y.__new__ |
| * i in x; // calls y.__has__ |
| * x.p = 10; // calls y.__put__ |
| * delete x.p; // calls y.__delete__ |
| * for (i in x) { print(i); } // calls y.__getIds__ |
| * </pre> |
| * <p> |
| * JavaScript caller of adapter object is isolated from the fact that the property access/mutation/deletion are really |
| * calls to JavaScript methods on adaptee. |
| * </p> |
| * <p> |
| * JSAdapter constructor can optionally receive an "overrides" object. Properties of overrides object is copied to |
| * JSAdapter instance. When user accessed property is one of these, then adaptee's methods like {@code __get__}, |
| * {@code __put__} etc. are not called for those. This can be used to make certain "preferred" properties that can be |
| * accessed in the usual/faster way avoiding proxy mechanism. Example: |
| * </p> |
| * <pre> |
| * var x = new JSAdapter({ foo: 444, bar: 6546 }) { |
| * __get__: function(name) { return name; } |
| * }; |
| * |
| * x.foo; // 444 directly retrieved without __get__ call |
| * x.bar = 'hello'; // "bar" directly set without __put__ call |
| * x.prop // calls __get__("prop") as 'prop' is not overridden |
| * </pre> |
| * It is possible to pass a specific prototype for JSAdapter instance by passing three arguments to JSAdapter |
| * constructor. So exact signature of JSAdapter constructor is as follows: |
| * <pre> |
| * JSAdapter([proto], [overrides], adaptee); |
| * </pre> |
| * Both proto and overrides are optional - but adaptee is not. When proto is not passed {@code JSAdapter.prototype} is |
| * used. |
| */ |
| @ScriptClass("JSAdapter") |
| public final class NativeJSAdapter extends ScriptObject { |
| /** object get operation */ |
| public static final String __get__ = "__get__"; |
| /** object out operation */ |
| public static final String __put__ = "__put__"; |
| /** object call operation */ |
| public static final String __call__ = "__call__"; |
| /** object new operation */ |
| public static final String __new__ = "__new__"; |
| /** object getIds operation */ |
| public static final String __getIds__ = "__getIds__"; |
| /** object getKeys operation */ |
| public static final String __getKeys__ = "__getKeys__"; |
| /** object getValues operation */ |
| public static final String __getValues__ = "__getValues__"; |
| /** object has operation */ |
| public static final String __has__ = "__has__"; |
| /** object delete operation */ |
| public static final String __delete__ = "__delete__"; |
| |
| // the new extensibility, sealing and freezing operations |
| |
| /** prevent extensions operation */ |
| public static final String __preventExtensions__ = "__preventExtensions__"; |
| /** isExtensible extensions operation */ |
| public static final String __isExtensible__ = "__isExtensible__"; |
| /** seal operation */ |
| public static final String __seal__ = "__seal__"; |
| /** isSealed extensions operation */ |
| public static final String __isSealed__ = "__isSealed__"; |
| /** freeze operation */ |
| public static final String __freeze__ = "__freeze__"; |
| /** isFrozen extensions operation */ |
| public static final String __isFrozen__ = "__isFrozen__"; |
| |
| private final ScriptObject adaptee; |
| private final boolean overrides; |
| |
| private static final MethodHandle IS_JSADAPTOR = findOwnMH("isJSAdaptor", boolean.class, Object.class, Object.class, MethodHandle.class, Object.class, ScriptFunction.class); |
| |
| // initialized by nasgen |
| private static PropertyMap $nasgenmap$; |
| |
| NativeJSAdapter(final Object overrides, final ScriptObject adaptee, final ScriptObject proto, final PropertyMap map) { |
| super(proto, map); |
| this.adaptee = wrapAdaptee(adaptee); |
| if (overrides instanceof ScriptObject) { |
| this.overrides = true; |
| final ScriptObject sobj = (ScriptObject)overrides; |
| this.addBoundProperties(sobj); |
| } else { |
| this.overrides = false; |
| } |
| } |
| |
| private static ScriptObject wrapAdaptee(final ScriptObject adaptee) { |
| return new JO(adaptee); |
| } |
| |
| @Override |
| public String getClassName() { |
| return "JSAdapter"; |
| } |
| |
| @Override |
| public int getInt(final Object key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); |
| } |
| |
| @Override |
| public int getInt(final double key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); |
| } |
| |
| @Override |
| public int getInt(final int key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); |
| } |
| |
| @Override |
| public double getDouble(final Object key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); |
| } |
| |
| @Override |
| public double getDouble(final double key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); |
| } |
| |
| @Override |
| public double getDouble(final int key, final int programPoint) { |
| return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); |
| } |
| |
| @Override |
| public Object get(final Object key) { |
| return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); |
| } |
| |
| @Override |
| public Object get(final double key) { |
| return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); |
| } |
| |
| @Override |
| public Object get(final int key) { |
| return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); |
| } |
| |
| @Override |
| public void set(final Object key, final int value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final Object key, final double value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final Object key, final Object value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final double key, final int value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final double key, final double value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final double key, final Object value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final int key, final int value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final int key, final double value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public void set(final int key, final Object value, final int flags) { |
| if (overrides && super.hasOwnProperty(key)) { |
| super.set(key, value, flags); |
| } else { |
| callAdaptee(__put__, key, value, flags); |
| } |
| } |
| |
| @Override |
| public boolean has(final Object key) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return true; |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); |
| } |
| |
| @Override |
| public boolean has(final int key) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return true; |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); |
| } |
| |
| @Override |
| public boolean has(final double key) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return true; |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); |
| } |
| |
| @Override |
| public boolean delete(final int key, final boolean strict) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return super.delete(key, strict); |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); |
| } |
| |
| @Override |
| public boolean delete(final double key, final boolean strict) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return super.delete(key, strict); |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); |
| } |
| |
| @Override |
| public boolean delete(final Object key, final boolean strict) { |
| if (overrides && super.hasOwnProperty(key)) { |
| return super.delete(key, strict); |
| } |
| |
| return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); |
| } |
| |
| @Override |
| public Iterator<String> propertyIterator() { |
| // Try __getIds__ first, if not found then try __getKeys__ |
| // In jdk6, we had added "__getIds__" so this is just for compatibility. |
| Object func = adaptee.get(__getIds__); |
| if (!(func instanceof ScriptFunction)) { |
| func = adaptee.get(__getKeys__); |
| } |
| |
| Object obj; |
| if (func instanceof ScriptFunction) { |
| obj = ScriptRuntime.apply((ScriptFunction)func, adaptee); |
| } else { |
| obj = new NativeArray(0); |
| } |
| |
| final List<String> array = new ArrayList<>(); |
| for (final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(obj); iter.hasNext(); ) { |
| array.add((String)iter.next()); |
| } |
| |
| return array.iterator(); |
| } |
| |
| |
| @Override |
| public Iterator<Object> valueIterator() { |
| final Object obj = callAdaptee(new NativeArray(0), __getValues__); |
| return ArrayLikeIterator.arrayLikeIterator(obj); |
| } |
| |
| @Override |
| public ScriptObject preventExtensions() { |
| callAdaptee(__preventExtensions__); |
| return this; |
| } |
| |
| @Override |
| public boolean isExtensible() { |
| return JSType.toBoolean(callAdaptee(Boolean.TRUE, __isExtensible__)); |
| } |
| |
| @Override |
| public ScriptObject seal() { |
| callAdaptee(__seal__); |
| return this; |
| } |
| |
| @Override |
| public boolean isSealed() { |
| return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isSealed__)); |
| } |
| |
| @Override |
| public ScriptObject freeze() { |
| callAdaptee(__freeze__); |
| return this; |
| } |
| |
| @Override |
| public boolean isFrozen() { |
| return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isFrozen__)); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param isNew is this NativeJSAdapter instantiated with the new operator |
| * @param self self reference |
| * @param args arguments ([adaptee], [overrides, adaptee] or [proto, overrides, adaptee] |
| * @return new NativeJSAdapter |
| */ |
| @Constructor |
| public static NativeJSAdapter construct(final boolean isNew, final Object self, final Object... args) { |
| Object proto = UNDEFINED; |
| Object overrides = UNDEFINED; |
| Object adaptee; |
| |
| if (args == null || args.length == 0) { |
| throw typeError("not.an.object", "null"); |
| } |
| |
| switch (args.length) { |
| case 1: |
| adaptee = args[0]; |
| break; |
| |
| case 2: |
| overrides = args[0]; |
| adaptee = args[1]; |
| break; |
| |
| default: |
| //fallthru |
| case 3: |
| proto = args[0]; |
| overrides = args[1]; |
| adaptee = args[2]; |
| break; |
| } |
| |
| if (!(adaptee instanceof ScriptObject)) { |
| throw typeError("not.an.object", ScriptRuntime.safeToString(adaptee)); |
| } |
| |
| final Global global = Global.instance(); |
| if (proto != null && !(proto instanceof ScriptObject)) { |
| proto = global.getJSAdapterPrototype(); |
| } |
| |
| return new NativeJSAdapter(overrides, (ScriptObject)adaptee, (ScriptObject)proto, $nasgenmap$); |
| } |
| |
| @Override |
| protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return findHook(desc, __new__, false); |
| } |
| |
| @Override |
| protected GuardedInvocation findCallMethodMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| if (overrides && super.hasOwnProperty(desc.getNameToken(2))) { |
| try { |
| final GuardedInvocation inv = super.findCallMethodMethod(desc, request); |
| if (inv != null) { |
| return inv; |
| } |
| } catch (final Exception e) { |
| //ignored |
| } |
| } |
| |
| return findHook(desc, __call__); |
| } |
| |
| @Override |
| protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operation) { |
| final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); |
| if (overrides && super.hasOwnProperty(name)) { |
| try { |
| final GuardedInvocation inv = super.findGetMethod(desc, request, operation); |
| if (inv != null) { |
| return inv; |
| } |
| } catch (final Exception e) { |
| //ignored |
| } |
| } |
| |
| switch(operation) { |
| case "getProp": |
| case "getElem": |
| return findHook(desc, __get__); |
| case "getMethod": |
| final FindProperty find = adaptee.findProperty(__call__, true); |
| if (find != null) { |
| final Object value = find.getObjectValue(); |
| if (value instanceof ScriptFunction) { |
| final ScriptFunction func = (ScriptFunction)value; |
| // TODO: It's a shame we need to produce a function bound to this and name, when we'd only need it bound |
| // to name. Probably not a big deal, but if we can ever make it leaner, it'd be nice. |
| return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class, |
| func.createBound(this, new Object[] { name })), 0, Object.class), |
| testJSAdaptor(adaptee, null, null, null), |
| adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null); |
| } |
| } |
| throw typeError("no.such.function", desc.getNameToken(2), ScriptRuntime.safeToString(this)); |
| default: |
| break; |
| } |
| |
| throw new AssertionError("should not reach here"); |
| } |
| |
| @Override |
| protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| if (overrides && super.hasOwnProperty(desc.getNameToken(CallSiteDescriptor.NAME_OPERAND))) { |
| try { |
| final GuardedInvocation inv = super.findSetMethod(desc, request); |
| if (inv != null) { |
| return inv; |
| } |
| } catch (final Exception e) { |
| //ignored |
| } |
| } |
| |
| return findHook(desc, __put__); |
| } |
| |
| // -- Internals only below this point |
| private Object callAdaptee(final String name, final Object... args) { |
| return callAdaptee(UNDEFINED, name, args); |
| } |
| |
| private double callAdapteeDouble(final int programPoint, final String name, final Object... args) { |
| return JSType.toNumberMaybeOptimistic(callAdaptee(name, args), programPoint); |
| } |
| |
| private int callAdapteeInt(final int programPoint, final String name, final Object... args) { |
| return JSType.toInt32MaybeOptimistic(callAdaptee(name, args), programPoint); |
| } |
| |
| private Object callAdaptee(final Object retValue, final String name, final Object... args) { |
| final Object func = adaptee.get(name); |
| if (func instanceof ScriptFunction) { |
| return ScriptRuntime.apply((ScriptFunction)func, adaptee, args); |
| } |
| return retValue; |
| } |
| |
| private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook) { |
| return findHook(desc, hook, true); |
| } |
| |
| private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook, final boolean useName) { |
| final FindProperty findData = adaptee.findProperty(hook, true); |
| final MethodType type = desc.getMethodType(); |
| if (findData != null) { |
| final String name = desc.getNameTokenCount() > 2 ? desc.getNameToken(2) : null; |
| final Object value = findData.getObjectValue(); |
| if (value instanceof ScriptFunction) { |
| final ScriptFunction func = (ScriptFunction)value; |
| |
| final MethodHandle methodHandle = getCallMethodHandle(findData, type, |
| useName ? name : null); |
| if (methodHandle != null) { |
| return new GuardedInvocation( |
| methodHandle, |
| testJSAdaptor(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func), |
| adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null); |
| } |
| } |
| } |
| |
| switch (hook) { |
| case __call__: |
| throw typeError("no.such.function", desc.getNameToken(2), ScriptRuntime.safeToString(this)); |
| default: |
| final MethodHandle methodHandle = hook.equals(__put__) ? |
| MH.asType(Lookup.EMPTY_SETTER, type) : |
| Lookup.emptyGetter(type.returnType()); |
| return new GuardedInvocation(methodHandle, testJSAdaptor(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null); |
| } |
| } |
| |
| private static MethodHandle testJSAdaptor(final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) { |
| return MH.insertArguments(IS_JSADAPTOR, 1, adaptee, getter, where, func); |
| } |
| |
| @SuppressWarnings("unused") |
| private static boolean isJSAdaptor(final Object self, final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) { |
| final boolean res = self instanceof NativeJSAdapter && ((NativeJSAdapter)self).getAdaptee() == adaptee; |
| if (res && getter != null) { |
| try { |
| return getter.invokeExact(where) == func; |
| } catch (final RuntimeException | Error e) { |
| throw e; |
| } catch (final Throwable t) { |
| throw new RuntimeException(t); |
| } |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Get the adaptee |
| * @return adaptee ScriptObject |
| */ |
| public ScriptObject getAdaptee() { |
| return adaptee; |
| } |
| |
| private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(MethodHandles.lookup(), NativeJSAdapter.class, name, MH.type(rtype, types)); |
| } |
| } |