214 lines
7.8 KiB
Java
214 lines
7.8 KiB
Java
/*
|
|
* 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<String, Object> implements Bindings, AutoCloseable {
|
|
private static final String SCRIPT_CONTEXT_GLOBAL_BINDINGS_IMPORT_FUNCTION_NAME = "importScriptEngineGlobalBindings";
|
|
|
|
private static final TypeLiteral<Map<String, Object>> STRING_MAP = new TypeLiteral<>() {
|
|
};
|
|
|
|
private Context context;
|
|
private Map<String, Object> 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<Entry<String, Object>> 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);
|
|
}
|