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 ScriptEngineFactory
s.
*/
- 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