/* * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 * * Subject to the condition set forth below, permission is hereby granted to any * person obtaining a copy of this software, associated documentation and/or * data (collectively the "Software"), free of charge and under any and all * copyright rights in the Software, and any and all patent rights owned or * freely licensable by each licensor hereunder covering either (i) the * unmodified Software as contributed to or provided by such licensor, or (ii) * the Larger Works (as defined below), to deal in both * * (a) the Software, and * * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if * one is included with the Software each a "Larger Work" to which the Software * is contributed by such licensors), * * without restriction, including without limitation the rights to copy, create * derivative works of, display, perform, and distribute the Software and make, * use, sell, offer for sale, import, export, have made, and have sold the * Software and the Larger Work(s), and to sublicense the foregoing rights on * either these or other terms. * * This license is subject to the following condition: * * The above copyright notice and either this complete permission notice or at a * minimum a reference to the UPL must be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package jmri.script.jsr223graalpython; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.function.Predicate; import javax.script.AbstractScriptEngine; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptException; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context.Builder; import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.HostAccess; import org.graalvm.polyglot.HostAccess.TargetMappingPrecedence; import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; import org.graalvm.polyglot.proxy.Proxy; /** * A Graal.JS implementation of the script engine. It provides access to the polyglot context using * {@link #getPolyglotContext()}. */ public final class GraalJSScriptEngine extends AbstractScriptEngine implements Compilable, Invocable, AutoCloseable { private static final String ID = "python"; private static final String POLYGLOT_CONTEXT = "polyglot.context"; private static final String OUT_SYMBOL = "$$internal.out$$"; private static final String IN_SYMBOL = "$$internal.in$$"; private static final String ERR_SYMBOL = "$$internal.err$$"; private static final String JS_SYNTAX_EXTENSIONS_OPTION = "js.syntax-extensions"; private static final String JS_SCRIPT_ENGINE_GLOBAL_SCOPE_IMPORT_OPTION = "js.script-engine-global-scope-import"; private static final String JS_LOAD_OPTION = "js.load"; private static final String JS_PRINT_OPTION = "js.print"; private static final String JS_GLOBAL_ARGUMENTS_OPTION = "js.global-arguments"; private static final String NASHORN_COMPATIBILITY_MODE_SYSTEM_PROPERTY = "polyglot.js.nashorn-compat"; private static final String INSECURE_SCRIPTENGINE_ACCESS_SYSTEM_PROPERTY = "graaljs.insecure-scriptengine-access"; static final String MAGIC_OPTION_PREFIX = "polyglot.js."; private static final HostAccess NASHORN_HOST_ACCESS = createNashornHostAccess(); private static HostAccess createNashornHostAccess() { HostAccess.Builder b = HostAccess.newBuilder(HostAccess.ALL); // Last resort conversions similar to those in NashornBottomLinker. b.targetTypeMapping(Value.class, String.class, v -> !v.isNull(), v -> toString(v), TargetMappingPrecedence.LOWEST); b.targetTypeMapping(Number.class, Integer.class, n -> true, n -> n.intValue(), TargetMappingPrecedence.LOWEST); b.targetTypeMapping(Number.class, Double.class, n -> true, n -> n.doubleValue(), TargetMappingPrecedence.LOWEST); b.targetTypeMapping(Number.class, Long.class, n -> true, n -> n.longValue(), TargetMappingPrecedence.LOWEST); b.targetTypeMapping(Number.class, Boolean.class, n -> true, n -> toBoolean(n.doubleValue()), TargetMappingPrecedence.LOWEST); b.targetTypeMapping(String.class, Boolean.class, n -> true, n -> !n.isEmpty(), TargetMappingPrecedence.LOWEST); return b.build(); } // ToString() operation private static String toString(Value value) { return toPrimitive(value).toString(); } // "Type(result) is not Object" heuristic for the purpose of ToPrimitive() conversion private static boolean isPrimitive(Value value) { return value.isString() || value.isNumber() || value.isBoolean() || value.isNull(); } // ToPrimitive()/OrdinaryToPrimitive() operation private static Value toPrimitive(Value value) { if (value.hasMembers()) { for (String methodName : new String[]{"toString", "valueOf"}) { if (value.canInvokeMember(methodName)) { Value maybePrimitive = value.invokeMember(methodName); if (isPrimitive(maybePrimitive)) { return maybePrimitive; } } } } if (isPrimitive(value)) { return value; } else { throw new ClassCastException(); } } private static boolean toBoolean(double d) { return d != 0.0 && !Double.isNaN(d); } interface MagicBindingsOptionSetter { String getOptionKey(); Context.Builder setOption(Builder builder, Object value); } private static boolean toBoolean(MagicBindingsOptionSetter optionSetter, Object value) { if (!(value instanceof Boolean)) { throw magicOptionValueErrorBool(optionSetter.getOptionKey(), value); } return (Boolean) value; } private static final MagicBindingsOptionSetter[] MAGIC_OPTION_SETTERS = new MagicBindingsOptionSetter[]{new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowHostAccess"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowHostAccess(toBoolean(this, value) ? HostAccess.ALL : HostAccess.NONE); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowNativeAccess"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowNativeAccess(toBoolean(this, value)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowCreateThread"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowCreateThread(toBoolean(this, value)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowIO"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowIO(toBoolean(this, value)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowHostClassLookup"; } @SuppressWarnings("unchecked") @Override public Builder setOption(Builder builder, Object value) { if (value instanceof Boolean) { boolean enabled = (Boolean) value; return builder.allowHostClassLookup(enabled ? s -> true : null); } else { try { return builder.allowHostClassLookup((Predicate) value); } catch (ClassCastException e) { throw new IllegalArgumentException(String.format("failed to set graal-js option \"%s\": expected a boolean or Predicate value, got \"%s\"", getOptionKey(), value)); } } } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowHostClassLoading"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowHostClassLoading(toBoolean(this, value)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "allowAllAccess"; } @Override public Builder setOption(Builder builder, Object value) { return builder.allowAllAccess(toBoolean(this, value)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "nashorn-compat"; } @Override public Builder setOption(Builder builder, Object value) { boolean val = toBoolean(this, value); if (val) { updateForNashornCompatibilityMode(builder); } return builder.option("js.nashorn-compat", String.valueOf(val)); } }, new MagicBindingsOptionSetter() { @Override public String getOptionKey() { return MAGIC_OPTION_PREFIX + "ecmascript-version"; } @Override public Builder setOption(Builder builder, Object value) { return builder.option("js.ecmascript-version", String.valueOf(value)); } }}; private static final EconomicSet MAGIC_BINDINGS_OPTION_KEYS = EconomicSet.create(); static final EconomicMap MAGIC_BINDINGS_OPTION_MAP = EconomicMap.create(); private static final boolean NASHORN_COMPATIBILITY_MODE = true; // wasBoolean.getBoolean(NASHORN_COMPATIBILITY_MODE_SYSTEM_PROPERTY); static { for (MagicBindingsOptionSetter setter : MAGIC_OPTION_SETTERS) { MAGIC_BINDINGS_OPTION_KEYS.add(setter.getOptionKey()); MAGIC_BINDINGS_OPTION_MAP.put(setter.getOptionKey(), setter); } } private final GraalJSEngineFactory factory; private final Context.Builder contextConfig; private boolean evalCalled; public GraalJSScriptEngine(GraalJSEngineFactory factory) { this(factory, factory.getPolyglotEngine(), null); log.debug("ctor(Factory) invoked"); } GraalJSScriptEngine(GraalJSEngineFactory factory, Engine engine, Context.Builder contextConfig) { log.debug("ctor invoked"); Engine engineToUse = engine; if (engineToUse == null) { engineToUse = Engine.newBuilder().allowExperimentalOptions(true).build(); } Context.Builder contextConfigToUse = contextConfig; if (contextConfigToUse == null) { // default config contextConfigToUse = Context.newBuilder(ID).allowExperimentalOptions(true); contextConfigToUse.option(JS_SYNTAX_EXTENSIONS_OPTION, "true"); contextConfigToUse.option(JS_LOAD_OPTION, "true"); contextConfigToUse.option(JS_PRINT_OPTION, "true"); contextConfigToUse.option(JS_GLOBAL_ARGUMENTS_OPTION, "true"); if (NASHORN_COMPATIBILITY_MODE) { updateForNashornCompatibilityMode(contextConfigToUse); } else if (Boolean.getBoolean(INSECURE_SCRIPTENGINE_ACCESS_SYSTEM_PROPERTY)) { updateForScriptEngineAccessibility(contextConfigToUse); } } this.factory = (factory == null) ? new GraalJSEngineFactory(engineToUse) : factory; this.contextConfig = contextConfigToUse.option(JS_SCRIPT_ENGINE_GLOBAL_SCOPE_IMPORT_OPTION, "true").engine(engineToUse); this.context.setBindings(new GraalJSBindings(this.contextConfig, this.context), ScriptContext.ENGINE_SCOPE); } private static void updateForNashornCompatibilityMode(Context.Builder builder) { builder.allowAllAccess(true); builder.allowHostAccess(NASHORN_HOST_ACCESS); // builder.useSystemExit(true); } private static void updateForScriptEngineAccessibility(Context.Builder builder) { builder.allowHostAccess(HostAccess.ALL); } static Context createDefaultContext(Context.Builder builder) { DelegatingInputStream in = new DelegatingInputStream(); DelegatingOutputStream out = new DelegatingOutputStream(); DelegatingOutputStream err = new DelegatingOutputStream(); builder .allowAllAccess(true) .in(in).out(out).err(err); Context ctx = builder.build(); ctx.getPolyglotBindings().putMember(OUT_SYMBOL, out); ctx.getPolyglotBindings().putMember(ERR_SYMBOL, err); ctx.getPolyglotBindings().putMember(IN_SYMBOL, in); return ctx; } /** * Closes the current context and makes it unusable. Operations performed after closing will * throw an {@link IllegalStateException}. */ @Override public void close() { log.debug("close invoked"); getPolyglotContext().close(); } /** * Returns the polyglot engine associated with this script engine. * @return the polyglot engine associated with this script engine. */ public Engine getPolyglotEngine() { log.debug("getPolyglotEngine invoked"); return factory.getPolyglotEngine(); } /** * Returns the polyglot context associated with the default ScriptContext of the engine. * * @see #getPolyglotContext(ScriptContext) to access the polyglot context of a particular * context. * * @return polyglot Context from the `context` member variable */ public Context getPolyglotContext() { log.debug("getPolyglotContext invoked"); return getPolyglotContext(context); } /** * Returns the polyglot context associated with a ScriptContext. If the context is not yet * initialized then it will be initialized using the default context builder specified in * {@link #create(Engine, org.graalvm.polyglot.Context.Builder)}. * @param ctxt Input to the creation * @return a Context from the ScriptContext */ public Context getPolyglotContext(ScriptContext ctxt) { log.trace("getPolyglotContext with {}", ctxt); return getOrCreateGraalJSBindings(ctxt).getContext(); } static Value evalInternal(Context context, String script) { log.trace("evalInternal with ID {} \"{}\", \"{}\"", ID, context, script); try { return context.eval(Source.newBuilder(ID, script, "internal-script").internal(true).buildLiteral()); } catch (Exception e) { log.warn("exception in evalInternal", e); return null; } } @Override public Bindings createBindings() { log.debug("createBindings"); return new GraalJSBindings(contextConfig, null); } @Override public void setBindings(Bindings bindings, int scope) { log.debug("setBindings {} scope: {}", bindings, scope); if (scope == ScriptContext.ENGINE_SCOPE) { Bindings oldBindings = getBindings(scope); if (oldBindings instanceof GraalJSBindings) { ((GraalJSBindings) oldBindings).updateEngineScriptContext(null); } } super.setBindings(bindings, scope); if (scope == ScriptContext.ENGINE_SCOPE && (bindings instanceof GraalJSBindings)) { ((GraalJSBindings) bindings).updateEngineScriptContext(getContext()); } } @Override public Object eval(Reader reader, ScriptContext ctxt) throws ScriptException { return eval(createSource(read(reader), ctxt), ctxt); } static String read(Reader reader) throws ScriptException { final StringBuilder builder = new StringBuilder(); final char[] buffer = new char[1024]; try { try { while (true) { final int count = reader.read(buffer); if (count == -1) { break; } builder.append(buffer, 0, count); } } finally { reader.close(); } return builder.toString(); } catch (IOException ioex) { throw new ScriptException(ioex); } } @Override public Object eval(String script, ScriptContext ctxt) throws ScriptException { return eval(createSource(script, ctxt), ctxt); } private static Source createSource(String script, ScriptContext ctxt) throws ScriptException { log.trace("createSource ctxt {}", ctxt); final Object val = ctxt.getAttribute(ScriptEngine.FILENAME); if (val == null) { return Source.newBuilder(ID, script, "").buildLiteral(); } else { try { return Source.newBuilder(ID, new File(val.toString())).content(script).build(); } catch (IOException ioex) { throw new ScriptException(ioex); } } } private static void updateDelegatingIOStreams(Context polyglotContext, ScriptContext scriptContext) { Value polyglotBindings = polyglotContext.getPolyglotBindings(); ((DelegatingOutputStream) polyglotBindings.getMember(OUT_SYMBOL).asProxyObject()).setWriter(scriptContext.getWriter()); ((DelegatingOutputStream) polyglotBindings.getMember(ERR_SYMBOL).asProxyObject()).setWriter(scriptContext.getErrorWriter()); ((DelegatingInputStream) polyglotBindings.getMember(IN_SYMBOL).asProxyObject()).setReader(scriptContext.getReader()); } private Object eval(Source source, ScriptContext scriptContext) throws ScriptException { log.debug("eval({},{}) called", source, scriptContext); GraalJSBindings engineBindings = getOrCreateGraalJSBindings(scriptContext); log.trace(" engineBindings {}", engineBindings); Context polyglotContext = engineBindings.getContext(); updateDelegatingIOStreams(polyglotContext, scriptContext); try { log.trace(" try evalCalled = {}", evalCalled); if (!evalCalled) { jrunscriptInitWorkaround(source, polyglotContext); } log.trace(" start engineBindings.importGlobalBindings"); engineBindings.importGlobalBindings(scriptContext); log.trace(" return polyglotContext.eval(..)"); return polyglotContext.eval(source).as(Object.class); } catch (PolyglotException e) { log.warn("Exception in eval", e.getCause()); throw toScriptException(e); } finally { evalCalled = true; } } private static ScriptException toScriptException(PolyglotException ex) { ScriptException sex; if (ex.isHostException()) { Throwable hostException = ex.asHostException(); // ScriptException (unlike almost any other exception) does not // accept Throwable cause (requires the cause to be Exception) Exception cause; if (hostException instanceof Exception) { cause = (Exception) hostException; } else { cause = new Exception(hostException); } // Make the host exception accessible through the cause chain sex = new ScriptException(cause); // Re-use the stack-trace of PolyglotException (with guest-language stack-frames) sex.setStackTrace(ex.getStackTrace()); } else { sex = new ScriptException(ex); } return sex; } private GraalJSBindings getOrCreateGraalJSBindings(ScriptContext scriptContext) { log.debug("getOrCreateGraalJSBindings invoked with {}", scriptContext); Bindings engineB = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE); if (engineB instanceof GraalJSBindings) { log.trace(" return engineB: {}", engineB); return ((GraalJSBindings) engineB); } else { GraalJSBindings bindings = new GraalJSBindings(createContext(engineB), scriptContext); bindings.putAll(engineB); log.trace(" recreate bindings: {} engineB {}", bindings, engineB); return bindings; } } private Context createContext(Bindings engineB) { log.debug("createContext bindings: {}", engineB); Object ctx = engineB.get(POLYGLOT_CONTEXT); if (!(ctx instanceof Context)) { Context.Builder builder = contextConfig; log.trace(" invoking MAGIC_OPTION_SETTERS"); for (MagicBindingsOptionSetter optionSetter : MAGIC_OPTION_SETTERS) { log.trace(" optionSetter: {}", optionSetter); Object value = engineB.get(optionSetter.getOptionKey()); if (value != null) { log.trace(" value: {}", value); builder = optionSetter.setOption(builder, value); engineB.remove(optionSetter.getOptionKey()); } } ctx = createDefaultContext(builder); log.trace(" createDefaultContext returns {} for {}", ctx, POLYGLOT_CONTEXT); engineB.put(POLYGLOT_CONTEXT, ctx); } return (Context) ctx; } @Override public GraalJSEngineFactory getFactory() { return factory; } @Override public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { log.trace("invokeMethod {} {}", thiz, name); if (thiz == null) { throw new IllegalArgumentException("thiz is not a valid object."); } GraalJSBindings engineBindings = getOrCreateGraalJSBindings(context); engineBindings.importGlobalBindings(context); Value thisValue = engineBindings.getContext().asValue(thiz); if (!thisValue.canInvokeMember(name)) { if (!thisValue.hasMember(name)) { throw noSuchMethod(name); } else { throw notCallable(name); } } try { return thisValue.invokeMember(name, args).as(Object.class); } catch (PolyglotException e) { throw toScriptException(e); } } @Override public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { log.trace("invokeFunction {}", name); GraalJSBindings engineBindings = getOrCreateGraalJSBindings(context); engineBindings.importGlobalBindings(context); Value function = engineBindings.getContext().getBindings(ID).getMember(name); if (function == null) { throw noSuchMethod(name); } else if (!function.canExecute()) { throw notCallable(name); } try { return function.execute(args).as(Object.class); } catch (PolyglotException e) { throw toScriptException(e); } } private static NoSuchMethodException noSuchMethod(String name) throws NoSuchMethodException { throw new NoSuchMethodException(name); } private static NoSuchMethodException notCallable(String name) throws NoSuchMethodException { throw new NoSuchMethodException(name + " is not a function"); } @Override public T getInterface(Class clasz) { checkInterface(clasz); log.trace("getInterface of {}", clasz); var retval = getInterfaceInner(evalInternal(getPolyglotContext(), "locals()"), clasz); // was "this" in Javascript log.trace(" returns {}", retval); return retval; } @Override public T getInterface(Object thiz, Class clasz) { if (thiz == null) { throw new IllegalArgumentException("this cannot be null"); } checkInterface(clasz); Value thisValue = getPolyglotContext().asValue(thiz); checkThis(thisValue); return getInterfaceInner(thisValue, clasz); } private static void checkInterface(Class clasz) { if (clasz == null || !clasz.isInterface()) { throw new IllegalArgumentException("interface Class expected in getInterface"); } } private static void checkThis(Value thiz) { if (thiz.isHostObject() || !thiz.hasMembers()) { throw new IllegalArgumentException("getInterface cannot be called on non-script object"); } } private static T getInterfaceInner(Value thiz, Class iface) { if (!isInterfaceImplemented(iface, thiz)) { return null; } return thiz.as(iface); } @Override public CompiledScript compile(String script) throws ScriptException { Source source = createSource(script, getContext()); return compile(source); } @Override public CompiledScript compile(Reader reader) throws ScriptException { Source source = createSource(read(reader), getContext()); return compile(source); } private CompiledScript compile(Source source) throws ScriptException { checkSyntax(source); return new CompiledScript() { @Override public ScriptEngine getEngine() { return GraalJSScriptEngine.this; } @Override public Object eval(ScriptContext ctx) throws ScriptException { return GraalJSScriptEngine.this.eval(source, ctx); } }; } private void checkSyntax(Source source) throws ScriptException { try { getPolyglotContext().parse(source); } catch (PolyglotException pex) { throw toScriptException(pex); } } private static class DelegatingInputStream extends InputStream implements Proxy { private Reader reader; private CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); private CharBuffer charBuffer = CharBuffer.allocate(2); private ByteBuffer byteBuffer = ByteBuffer.allocate((int) encoder.maxBytesPerChar() * 2); DelegatingInputStream() { byteBuffer.flip(); } @Override public int read() throws IOException { if (reader != null) { while (!byteBuffer.hasRemaining()) { int c = reader.read(); if (c == -1) { return -1; } byteBuffer.clear(); charBuffer.put((char) c); charBuffer.flip(); encoder.encode(charBuffer, byteBuffer, false); charBuffer.compact(); byteBuffer.flip(); } return byteBuffer.get(); } return 0; } void setReader(Reader reader) { this.reader = reader; } } private static class DelegatingOutputStream extends OutputStream implements Proxy { private Writer writer; private CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); private ByteBuffer byteBuffer = ByteBuffer.allocate((int) StandardCharsets.UTF_8.newEncoder().maxBytesPerChar() * 2); private CharBuffer charBuffer = CharBuffer.allocate(byteBuffer.capacity() * (int) decoder.maxCharsPerByte()); @Override public void write(int b) throws IOException { if (writer != null) { byteBuffer.put((byte) b); byteBuffer.flip(); decoder.decode(byteBuffer, charBuffer, false); byteBuffer.compact(); charBuffer.flip(); while (charBuffer.hasRemaining()) { char c = charBuffer.get(); writer.write(c); } charBuffer.clear(); writer.flush(); // was needed to get tests to actually print } } @Override public void flush() throws IOException { if (writer != null) { writer.flush(); } } void setWriter(Writer writer) { this.writer = writer; } } /** * Creates a new GraalJSScriptEngine with default configuration. * * @see #create(Engine, Context.Builder) to customize the configuration. * @return A new script engine created with null parameters */ public static GraalJSScriptEngine create() { log.debug("create invoked"); return create(null, null); } /** * Creates a new GraalJS script engine from a polyglot Engine instance with a base configuration * for new polyglot {@link Context} instances. Polyglot context instances can be accessed from * {@link ScriptContext} instances using {@link #getPolyglotContext()}. The * {@link Builder#out(OutputStream) out},{@link Builder#err(OutputStream) err} and * {@link Builder#in(InputStream) in} stream configuration are not inherited from the provided * polyglot context config. Instead {@link ScriptContext} output and input streams are used. * * @param engine the engine to be used for context configurations or null if a * default engine should be used. * @param newContextConfig a base configuration to create new context instances or * null if the default configuration should be used to construct new * context instances. * @return A new script engine */ public static GraalJSScriptEngine create(Engine engine, Context.Builder newContextConfig) { log.debug("create(stuff) invoked"); return new GraalJSScriptEngine(null, engine, newContextConfig); } private static boolean isInterfaceImplemented(final Class iface, final Value obj) { for (final Method method : iface.getMethods()) { // ignore methods of java.lang.Object class if (method.getDeclaringClass() == Object.class) { continue; } // skip check for default methods - non-abstract, interface methods if (!Modifier.isAbstract(method.getModifiers())) { continue; } if (!obj.canInvokeMember(method.getName())) { return false; } } return true; } /** * Detects jrunscript "init.js" and installs a JSAdapter polyfill if needed. */ private static void jrunscriptInitWorkaround(Source source, Context polyglotContext) { log.trace("jrunscriptInitWorkaround with {}, {}", source, polyglotContext); if (source.getName().equals(JRUNSCRIPT_INIT_NAME)) { log.trace(" passed 1st if with match on {}", source.getName()); String initCode = source.getCharacters().toString(); log.trace(" creates initCode = {}", initCode); if (initCode.contains("jrunscript") && initCode.contains("JSAdapter") && !polyglotContext.getBindings(ID).hasMember("JSAdapter")) { log.trace(" polyglotContext.eval {} {}", ID, JSADAPTER_POLYFILL); polyglotContext.eval(ID, JSADAPTER_POLYFILL); } } } private static final String JRUNSCRIPT_INIT_NAME = ""; private static final String JSADAPTER_POLYFILL = "this.JSAdapter || " + "Object.defineProperty(this, \"JSAdapter\", {configurable:true, writable:true, enumerable: false, value: function(t) {\n" + " var target = {};\n" + " var handler = {\n" + " get: function(target, name) {return typeof t.__get__ == 'function' ? t.__get__.call(target, name) : undefined;},\n" + " has: function(target, name) {return typeof t.__has__ == 'function' ? t.__has__.call(target, name) : false;},\n" + " deleteProperty: function(target, name) {return typeof t.__delete__ == 'function' ? t.__delete__.call(target, name) : true;},\n" + " set: function(target, name, value) {return typeof t.__put__ == 'function' ? t.__put__.call(target, name, value) : undefined;},\n" + " ownKeys: function(target) {return typeof t.__getIds__ == 'function' ? t.__getIds__.call(target) : [];},\n" + " }\n" + " return new Proxy(target, handler);\n" + "}});\n"; private static IllegalArgumentException magicOptionValueErrorBool(String name, Object v) { return new IllegalArgumentException(String.format("failed to set graal-js option \"%s\": expected a boolean value, got \"%s\"", name, v)); } private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GraalJSScriptEngine.class); }