diff --git a/core/OpenRocket Core.iml b/core/OpenRocket Core.iml index 8a5fd8008..dce24f46b 100644 --- a/core/OpenRocket Core.iml +++ b/core/OpenRocket Core.iml @@ -270,5 +270,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/net/sf/openrocket/scripting/GraalJSScriptEngineFactory.java b/core/src/net/sf/openrocket/scripting/GraalJSScriptEngineFactory.java new file mode 100644 index 000000000..324fb1aef --- /dev/null +++ b/core/src/net/sf/openrocket/scripting/GraalJSScriptEngineFactory.java @@ -0,0 +1,99 @@ +package net.sf.openrocket.scripting; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import java.util.*; + +import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.HostAccess; + +public class GraalJSScriptEngineFactory implements ScriptEngineFactory { + private static final String ENGINE_NAME = "Graal.js"; + private static final String LANGUAGE = "ECMAScript"; + private static final String LANGUAGE_VERSION = "ECMAScript 262 Edition 11"; + private static final String[] NAMES = new String[]{"js", "JS", "JavaScript", "javascript", LANGUAGE, LANGUAGE.toLowerCase(), ENGINE_NAME, ENGINE_NAME.toLowerCase(), "Graal-js", "graal-js", "Graal.JS", "Graal-JS", "GraalJS", "GraalJSPolyglot"}; + private static final String[] MIME_TYPES = new String[]{"application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript"}; + private static final String[] EXTENSIONS = new String[]{"js", "mjs"}; + private static final List names; + private static final List mimeTypes; + private static final List extensions; + + public GraalJSScriptEngineFactory() { + } + + public ScriptEngine getScriptEngine() { + return GraalJSScriptEngine.create(null, + Context.newBuilder("js") + .allowHostAccess(HostAccess.ALL) + .allowHostClassLookup(s -> true) + .option("js.ecmascript-version", "2021")); + } + + public String getEngineName() { + return ENGINE_NAME; + } + + public String getEngineVersion() { + return ((GraalJSScriptEngine)getScriptEngine()).getPolyglotEngine().getVersion(); + } + + public List getExtensions() { + return extensions; + } + + public String getLanguageVersion() { + return LANGUAGE_VERSION; + } + + public String getLanguageName() { + return LANGUAGE; + } + + public List getMimeTypes() { + return mimeTypes; + } + + public List getNames() { + return names; + } + + public String getMethodCallSyntax(final String obj, final String method, final String... args) { + return null; + } + + public String getOutputStatement(final String toDisplay) { + return "print(" + toDisplay + ")"; + } + + public Object getParameter(String key) { + switch (key) { + case "javax.script.name": + return "javascript"; + case "javax.script.engine": + return this.getEngineName(); + case "javax.script.engine_version": + return this.getEngineVersion(); + case "javax.script.language": + return this.getLanguageName(); + case "javax.script.language_version": + return this.getLanguageVersion(); + default: + return null; + } + } + + public String getProgram(final String... statements) { + return null; + } + + static { + List nameList = Arrays.asList(NAMES); + List mimeTypeList = Arrays.asList(MIME_TYPES); + List extensionList = Arrays.asList(EXTENSIONS); + names = Collections.unmodifiableList(nameList); + mimeTypes = Collections.unmodifiableList(mimeTypeList); + extensions = Collections.unmodifiableList(extensionList); + } +} diff --git a/core/src/net/sf/openrocket/scripting/ScriptEngineManagerRedux.java b/core/src/net/sf/openrocket/scripting/ScriptEngineManagerRedux.java index 1fa3081f7..42566c8a1 100644 --- a/core/src/net/sf/openrocket/scripting/ScriptEngineManagerRedux.java +++ b/core/src/net/sf/openrocket/scripting/ScriptEngineManagerRedux.java @@ -66,8 +66,7 @@ public class ScriptEngineManagerRedux { * @see java.lang.Thread#getContextClassLoader */ public ScriptEngineManagerRedux() { - ClassLoader ctxtLoader = Thread.currentThread().getContextClassLoader(); - init(ctxtLoader); + init(Thread.currentThread().getContextClassLoader()); } /** @@ -83,96 +82,13 @@ public class ScriptEngineManagerRedux { init(loader); } - private void init(final ClassLoader loader) { - globalScope = new SimpleBindings(); - engineSpis = new TreeSet(Comparator.comparing( - ScriptEngineFactory::getEngineName, - Comparator.nullsLast(Comparator.naturalOrder())) - ); - nameAssociations = new HashMap(); - extensionAssociations = new HashMap(); - mimeTypeAssociations = new HashMap(); - initEngines(loader); - } - - private ServiceLoader getServiceLoader(final ClassLoader loader) { - if (loader != null) { - return ServiceLoader.load(ScriptEngineFactory.class, loader); - } else { - return ServiceLoader.loadInstalled(ScriptEngineFactory.class); - } - } - - private void initEngines(final ClassLoader loader) { - Iterator itr = null; - try { - ServiceLoader sl = AccessController.doPrivileged( - new PrivilegedAction>() { - @Override - public ServiceLoader run() { - return getServiceLoader(loader); - } - }); - - itr = sl.iterator(); - } catch (ServiceConfigurationError err) { - // } catch (Exception err) { - System.err.println("Can't find ScriptEngineFactory providers: " + - err.getMessage()); - if (DEBUG) { - err.printStackTrace(); - } - // do not throw any exception here. user may want to - // manage his/her own factories using this manager - // by explicit registratation (by registerXXX) methods. - return; - } - - try { - while (itr.hasNext()) { - try { - ScriptEngineFactory fact = itr.next(); - engineSpis.add(fact); - } catch (ServiceConfigurationError err) { - // } catch (Exception err) { - System.err.println("ScriptEngineManager providers.next(): " - + err.getMessage()); - if (DEBUG) { - err.printStackTrace(); - } - // one factory failed, but check other factories... - continue; - } - } - } catch (ServiceConfigurationError err) { - // } catch (Exception err) { - System.err.println("ScriptEngineManager providers.hasNext(): " - + err.getMessage()); - if (DEBUG) { - err.printStackTrace(); - } - // do not throw any exception here. user may want to - // manage his/her own factories using this manager - // by explicit registratation (by registerXXX) methods. - return; - } - } - /** - * setBindings stores the specified Bindings - * in the globalScope field. ScriptEngineManager sets this - * Bindings as global bindings for ScriptEngine - * objects created by it. - * - * @param bindings The specified Bindings - * @throws IllegalArgumentException if bindings is null. + * Gets the value for the specified key in the Global Scope + * @param key The key whose value is to be returned. + * @return The value for the specified key. */ - public void setBindings(Bindings bindings) { - if (bindings == null) { - throw new IllegalArgumentException("Global scope cannot be null."); - } - - globalScope = bindings; + public Object get(String key) { + return _globalScope.get(key); } /** @@ -183,27 +99,7 @@ public class ScriptEngineManagerRedux { * @return The globalScope field. */ public Bindings getBindings() { - return globalScope; - } - - /** - * Sets the specified key/value pair in the Global Scope. - * @param key Key to set - * @param value Value to set. - * @throws NullPointerException if key is null. - * @throws IllegalArgumentException if key is empty string. - */ - public void put(String key, Object value) { - globalScope.put(key, value); - } - - /** - * Gets the value for the specified key in the Global Scope - * @param key The key whose value is to be returned. - * @return The value for the specified key. - */ - public Object get(String key) { - return globalScope.get(key); + return _globalScope; } /** @@ -222,140 +118,46 @@ public class ScriptEngineManagerRedux { * created ScriptEngine. * @throws NullPointerException if shortName is null. */ - public ScriptEngine getEngineByName(String shortName) { - if (shortName == null) throw new NullPointerException(); - //look for registered name first - Object obj; - if (null != (obj = nameAssociations.get(shortName))) { - ScriptEngineFactory spi = (ScriptEngineFactory)obj; - try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } + private Map _factoriesByName = new HashMap<>(); + public synchronized ScriptEngine getEngineByName(String shortName) { + if (shortName == null) { + throw new NullPointerException(); } - for (ScriptEngineFactory spi : engineSpis) { - List names = null; - try { - names = spi.getNames(); - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } + String key = shortName.toLowerCase(); + if (_factoriesByName.containsKey(key)) { + return getEngineByFactory(_factoriesByName.get(key)); + } - if (names != null) { - for (String name : names) { - if (shortName.equals(name)) { - try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - } + // Look for registered name first + ScriptEngineFactory factoryNamed; + if (null != (factoryNamed = _nameAssociations.get(key))) { + try { + _factoriesByName.put(key, factoryNamed); + return getEngineByFactory(factoryNamed); + } catch (Exception exp) { + if (DEBUG) { + exp.printStackTrace(); } } } - return null; - } - - /** - * Look up and create a ScriptEngine for a given extension. The algorithm - * used by getEngineByName is used except that the search starts - * by looking for a ScriptEngineFactory registered to handle the - * given extension using registerEngineExtension. - * @param extension The given extension - * @return The engine to handle scripts with this extension. Returns null - * if not found. - * @throws NullPointerException if extension is null. - */ - public ScriptEngine getEngineByExtension(String extension) { - if (extension == null) throw new NullPointerException(); - //look for registered extension first - Object obj; - if (null != (obj = extensionAssociations.get(extension))) { - ScriptEngineFactory spi = (ScriptEngineFactory)obj; + Optional factoryName; + List names; + for (ScriptEngineFactory factory : _scriptEngineFactories) { try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; + factoryName = factory.getNames().stream().filter(l -> l.equalsIgnoreCase(shortName)).findFirst(); + if (factoryName.isPresent()) { + _factoriesByName.put(key, factory); + return getEngineByFactory(factory); + } } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - } - - for (ScriptEngineFactory spi : engineSpis) { - List exts = null; - try { - exts = spi.getExtensions(); - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - if (exts == null) continue; - for (String ext : exts) { - if (extension.equals(ext)) { - try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } + if (DEBUG) { + exp.printStackTrace(); } } } - return null; - } - /** - * Look up and create a ScriptEngine for a given mime type. The algorithm - * used by getEngineByName is used except that the search starts - * by looking for a ScriptEngineFactory registered to handle the - * given mime type using registerEngineMimeType. - * @param mimeType The given mime type - * @return The engine to handle scripts with this mime type. Returns null - * if not found. - * @throws NullPointerException if mimeType is null. - */ - public ScriptEngine getEngineByMimeType(String mimeType) { - if (mimeType == null) throw new NullPointerException(); - //look for registered types first - Object obj; - if (null != (obj = mimeTypeAssociations.get(mimeType))) { - ScriptEngineFactory spi = (ScriptEngineFactory)obj; - try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - } - - for (ScriptEngineFactory spi : engineSpis) { - List types = null; - try { - types = spi.getMimeTypes(); - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - if (types == null) continue; - for (String type : types) { - if (mimeType.equals(type)) { - try { - ScriptEngine engine = spi.getScriptEngine(); - engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); - return engine; - } catch (Exception exp) { - if (DEBUG) exp.printStackTrace(); - } - } - } - } return null; } @@ -364,12 +166,19 @@ public class ScriptEngineManagerRedux { * found by the discovery mechanism. * @return List of all discovered ScriptEngineFactorys. */ - public List getEngineFactories() { - List res = new ArrayList(engineSpis.size()); - for (ScriptEngineFactory spi : engineSpis) { - res.add(spi); - } - return Collections.unmodifiableList(res); + public synchronized List getEngineFactories() { + return List.copyOf(_scriptEngineFactories); + } + + /** + * Sets the specified key/value pair in the Global Scope. + * @param key Key to set + * @param value Value to set. + * @throws NullPointerException if key is null. + * @throws IllegalArgumentException if key is empty string. + */ + public void put(String key, Object value) { + _globalScope.put(key, value); } /** @@ -380,51 +189,112 @@ public class ScriptEngineManagerRedux { * @throws NullPointerException if any of the parameters is null. */ public void registerEngineName(String name, ScriptEngineFactory factory) { - if (name == null || factory == null) throw new NullPointerException(); - nameAssociations.put(name, factory); + if (name == null || factory == null) { + throw new NullPointerException(); + } + + _nameAssociations.put(name.toLowerCase(), factory); } /** - * Registers a ScriptEngineFactory to handle a mime type. - * Overrides any such association found using the Discovery mechanism. + * setBindings stores the specified Bindings + * in the globalScope field. ScriptEngineManager sets this + * Bindings as global bindings for ScriptEngine + * objects created by it. * - * @param type The mime type to be associated with the - * ScriptEngineFactory. - * - * @param factory The class to associate with the given mime type. - * @throws NullPointerException if any of the parameters is null. + * @param bindings The specified Bindings + * @throws IllegalArgumentException if bindings is null. */ - public void registerEngineMimeType(String type, ScriptEngineFactory factory) { - if (type == null || factory == null) throw new NullPointerException(); - mimeTypeAssociations.put(type, factory); + public void setBindings(Bindings bindings) { + if (bindings == null) { + throw new IllegalArgumentException("Global scope cannot be null."); + } + + _globalScope = bindings; } - /** - * Registers a ScriptEngineFactory to handle an extension. - * Overrides any such association found using the Discovery mechanism. - * - * @param extension The extension type to be associated with the - * ScriptEngineFactory. - * @param factory The class to associate with the given extension. - * @throws NullPointerException if any of the parameters is null. - */ - public void registerEngineExtension(String extension, ScriptEngineFactory factory) { - if (extension == null || factory == null) throw new NullPointerException(); - extensionAssociations.put(extension, factory); + private ScriptEngine getEngineByFactory(ScriptEngineFactory factory) { + if (factory == null) { + return null; + } + + ScriptEngine engine = factory.getScriptEngine(); + engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); + return engine; + } + + private ServiceLoader getServiceLoader(final ClassLoader loader) { + if (loader != null) { + return ServiceLoader.load(ScriptEngineFactory.class, loader); + } else { + return ServiceLoader.loadInstalled(ScriptEngineFactory.class); + } + } + + private void init(final ClassLoader loader) { + _scriptEngineFactories = new TreeSet<>(Comparator.comparing( + ScriptEngineFactory::getEngineName, + Comparator.nullsLast(Comparator.naturalOrder())) + ); + initEngines(loader); + } + + private void initEngines(final ClassLoader loader) { + Iterator itr; + try { + ServiceLoader loaders = AccessController.doPrivileged( + new PrivilegedAction>() { + @Override + public ServiceLoader run() { + return getServiceLoader(loader); + } + }); + + itr = loaders.iterator(); + } catch (ServiceConfigurationError err) { + // } catch (Exception err) { + System.err.println("Can't find ScriptEngineFactory providers: " + err.getMessage()); + if (DEBUG) { + err.printStackTrace(); + } + // do not throw any exception here. user may want to + // manage his/her own factories using this manager + // by explicit registration (by registerXXX) methods. + return; + } + + try { + while (itr.hasNext()) { + try { + ScriptEngineFactory factory = itr.next(); + _scriptEngineFactories.add(factory); + } catch (ServiceConfigurationError err) { + // } catch (Exception err) { + System.err.println("ScriptEngineManager providers.next(): " + err.getMessage()); + if (DEBUG) { + err.printStackTrace(); + } + // one factory failed, but check other factories... + } + } + } catch (ServiceConfigurationError err) { + // } catch (Exception err) { + System.err.println("ScriptEngineManager providers.hasNext(): " + err.getMessage()); + if (DEBUG) { + err.printStackTrace(); + } + // do not throw any exception here. user may want to + // manage his/her own factories using this manager + // by explicit registratation (by registerXXX) methods. + } } /** Set of script engine factories discovered. */ - private TreeSet engineSpis; + private TreeSet _scriptEngineFactories; /** Map of engine name to script engine factory. */ - private HashMap nameAssociations; - - /** Map of script file extension to script engine factory. */ - private HashMap extensionAssociations; - - /** Map of script MIME type to script engine factory. */ - private HashMap mimeTypeAssociations; + private HashMap _nameAssociations = new HashMap<>(); /** Global bindings associated with script engines created by this manager. */ - private Bindings globalScope; + private Bindings _globalScope = new SimpleBindings(); } diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java index a74f27d37..ef215ae3e 100644 --- a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java +++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java @@ -4,7 +4,6 @@ import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.List; import java.util.prefs.BackingStoreException; @@ -12,6 +11,7 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import net.sf.openrocket.scripting.ScriptEngineManagerRedux; +import net.sf.openrocket.scripting.GraalJSScriptEngineFactory; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.BugException; @@ -31,19 +31,25 @@ public class ScriptingUtil { /** The name to be chosen from a list of alternatives. If not found, will use the default name. */ private static final List PREFERRED_LANGUAGE_NAMES = List.of("JavaScript"); + + private static ScriptEngineManagerRedux manager; @Inject Preferences prefs; - - private static ScriptEngineManagerRedux manager; public ScriptingUtil() { if (manager == null) { // using the ScriptEngineManger from javax.script package pulls in the sun.misc.ServiceConfigurationError // which is removed in Java 9+ which causes a ClassNotFoundException to be thrown. manager = new ScriptEngineManagerRedux(); + + manager.registerEngineName("Javascript", new GraalJSScriptEngineFactory()); } } + + public ScriptEngine getEngineByName(String shortName) { + return manager.getEngineByName(shortName); + } /** * Return the preferred internal language name based on a script language name. @@ -59,29 +65,16 @@ public class ScriptingUtil { if (engine == null) { return null; } - return getLanguage(engine.getFactory()); - } - public ScriptEngine getEngineByName(String shortName) { - return manager.getEngineByName(shortName); + return getLanguageByFactory(engine.getFactory()); } public List getLanguages() { - List langs = new ArrayList<>(); + List languages = new ArrayList<>(); for (ScriptEngineFactory factory : manager.getEngineFactories()) { - langs.add(getLanguage(factory)); + languages.add(getLanguageByFactory(factory)); } - return langs; - } - - private String getLanguage(ScriptEngineFactory factory) { - for (String name : factory.getNames()) { - if (PREFERRED_LANGUAGE_NAMES.contains(name)) { - return name; - } - } - - return factory.getLanguageName(); + return languages; } /** @@ -126,6 +119,16 @@ public class ScriptingUtil { throw new BugException(e); } } + + private String getLanguageByFactory(ScriptEngineFactory factory) { + for (String name : factory.getNames()) { + if (PREFERRED_LANGUAGE_NAMES.contains(name)) { + return name; + } + } + + return factory.getLanguageName(); + } static String normalize(String script) { return script.replaceAll("\r", "").trim(); diff --git a/swing/resources/datafiles/examples/Simulation extensions and scripting.ork b/swing/resources/datafiles/examples/Simulation extensions and scripting.ork index a1592fc11..e9983fbf9 100644 Binary files a/swing/resources/datafiles/examples/Simulation extensions and scripting.ork and b/swing/resources/datafiles/examples/Simulation extensions and scripting.ork differ