/* * 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.util.AbstractMap; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.script.Bindings; import javax.script.ScriptContext; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.TypeLiteral; import org.graalvm.polyglot.Value; import org.graalvm.polyglot.proxy.ProxyObject; import jmri.script.jsr223graalpython.GraalJSScriptEngine.MagicBindingsOptionSetter; final class GraalJSBindings extends AbstractMap implements Bindings, AutoCloseable { private static final String SCRIPT_CONTEXT_GLOBAL_BINDINGS_IMPORT_FUNCTION_NAME = "importScriptEngineGlobalBindings"; private static final TypeLiteral> STRING_MAP = new TypeLiteral<>() { }; private Context context; private Map global; private Value deleteProperty; private Value clear; private Context.Builder contextBuilder; // ScriptContext of the ScriptEngine where these bindings form ENGINE_SCOPE bindings private ScriptContext engineScriptContext; GraalJSBindings(Context.Builder contextBuilder, ScriptContext scriptContext) { log.trace("ctor(C.B, S) invoked"); this.contextBuilder = contextBuilder; this.engineScriptContext = scriptContext; } GraalJSBindings(Context context, ScriptContext scriptContext) { log.trace("ctor(C, S) invoked"); this.context = context; initGlobal(); this.engineScriptContext = scriptContext; } private void requireContext() { if (context == null) { initContext(); } } private void initContext() { log.trace("initContext"); context = GraalJSScriptEngine.createDefaultContext(contextBuilder); log.trace(" initContext created context {}", context); initGlobal(); } private void initGlobal() { log.trace("initGlobal with \"{}\"", context); var temp = GraalJSScriptEngine.evalInternal(context, "locals()"); // this line combined with next log.trace(" temp: "+temp); this.global = temp.as(STRING_MAP); // was "this" log.trace(" defines "+this.global); } private Value deletePropertyFunction() { if (this.deleteProperty == null) { this.deleteProperty = GraalJSScriptEngine.evalInternal(context, "(function(obj, prop) {delete obj[prop]})"); } return this.deleteProperty; } private Value clearFunction() { if (this.clear == null) { this.clear = GraalJSScriptEngine.evalInternal(context, "(function(obj) {for (var prop in obj) {delete obj[prop]}})"); } return this.clear; } @Override public Object put(String name, Object v) { checkKey(name); if (name.startsWith(GraalJSScriptEngine.MAGIC_OPTION_PREFIX)) { if (context == null) { MagicBindingsOptionSetter optionSetter = GraalJSScriptEngine.MAGIC_BINDINGS_OPTION_MAP.get(name); if (optionSetter == null) { throw new IllegalArgumentException("unkown graal-js option \"" + name + "\""); } else { contextBuilder = optionSetter.setOption(contextBuilder, v); return true; } } else { throw magicOptionContextInitializedError(name); } } requireContext(); return global.put(name, v); } @Override public void clear() { if (context != null) { clearFunction().execute(global); } } @Override public Object get(Object key) { checkKey((String) key); requireContext(); if (engineScriptContext != null) { importGlobalBindings(engineScriptContext); } return global.get(key); } private static void checkKey(String key) { Objects.requireNonNull(key, "key can not be null"); if (key.isEmpty()) { throw new IllegalArgumentException("key can not be empty"); } } @Override public Object remove(Object key) { requireContext(); Object prev = get(key); deletePropertyFunction().execute(global, key); return prev; } public Context getContext() { requireContext(); return context; } @Override public Set> entrySet() { requireContext(); return global.entrySet(); } @Override public void close() { if (context != null) { context.close(); } } private static IllegalStateException magicOptionContextInitializedError(String name) { return new IllegalStateException(String.format("failed to set graal-js option \"%s\": js context is already initialized", name)); } void importGlobalBindings(ScriptContext scriptContext) { // was originally there, NPE at end // Bindings globalBindings = scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE); // if (globalBindings != null && !globalBindings.isEmpty() && this != globalBindings) { // ProxyObject bindingsProxy = ProxyObject.fromMap(Collections.unmodifiableMap(globalBindings)); // log.trace("getBindings(python) {} type {}", getContext().getBindings("python"), getContext().getBindings("python").getClass()); // getContext() // .getBindings("python") // .getMember(SCRIPT_CONTEXT_GLOBAL_BINDINGS_IMPORT_FUNCTION_NAME) // .execute(bindingsProxy); // } } void updateEngineScriptContext(ScriptContext scriptContext) { engineScriptContext = scriptContext; } private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GraalJSBindings.class); }