diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index 9404edba0..87fe2550e 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -386,6 +386,7 @@ simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
simedtdlg.border.SimExt = Simulation extensions
simedtdlg.SimExt.desc = Simulation extensions enable advanced features and custom functionality during flight simulations. You can for example do hardware-in-the-loop testing with them.
simedtdlg.SimExt.noExtensions = No simulation extensions defined
+simedtdlg.SimExt.add = Add extension
simedtdlg.lbl.Noflightdata = No flight data available.
simedtdlg.lbl.runsimfirst = Please run the simulation first.
simedtdlg.chart.Simflight = Simulated flight
@@ -408,6 +409,15 @@ SimulationExtension.javacode.className = Fully-qualified Java class name:
SimulationExtension.scripting.name = {language} script
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
SimulationExtension.scripting.language.label = Language:
+SimulationExtension.scripting.warning.disabled = Untrusted scripts have been disabled. You need to manually enable them.
+SimulationExtension.scripting.text.enabled = Enable script
+SimulationExtension.scripting.text.enabled.ttip = The script is run only when enabled.
+SimulationExtension.scripting.text.trusted = Trust this script on this computer
+SimulationExtension.scripting.text.trusted.msg = Untrusted scripts are disabled when loading the document
+SimulationExtension.scripting.text.trusted.clear = Clear trusted scripts
+SimulationExtension.scripting.text.trusted.clear.ttip = Clear the trusted status of all scripts on this computer
+SimulationExtension.scripting.text.trusted.cleared = All scripts are now untrusted on this computer.
+SimulationExtension.scripting.text.trusted.cleared.title = Cleared
SimulationEditDialog.btn.plot = Plot
SimulationEditDialog.btn.export = Export
diff --git a/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java b/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java
index 8261cee1d..fbed23a43 100644
--- a/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java
+++ b/core/src/net/sf/openrocket/simulation/extension/AbstractSimulationExtension.java
@@ -3,6 +3,9 @@ package net.sf.openrocket.simulation.extension;
import java.util.Collections;
import java.util.List;
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.util.AbstractChangeSource;
@@ -42,7 +45,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
}
/**
- * By default, returns the canonical name of this class.
+ * {@inheritDoc}
+ *
+ * By default, this method returns the canonical name of this class.
*/
@Override
public String getId() {
@@ -50,7 +55,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
}
/**
- * By default, returns the name provided to the constructor.
+ * {@inheritDoc}
+ *
+ * By default, this method returns the name provided to the constructor.
*/
@Override
public String getName() {
@@ -58,7 +65,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
}
/**
- * By default, returns null.
+ * {@inheritDoc}
+ *
+ * By default, this method returns null.
*/
@Override
public String getDescription() {
@@ -66,7 +75,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
}
/**
- * By default, returns an empty list.
+ * {@inheritDoc}
+ *
+ * By default, this method returns an empty list.
*/
@Override
public List getFlightDataTypes() {
@@ -74,7 +85,18 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
}
/**
- * By default, returns a new object obtained by calling Object.clone().
+ * {@inheritDoc}
+ *
+ * By default, this method does nothing.
+ */
+ @Override
+ public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings) {
+
+ }
+
+ /**
+ * By default, returns a new object obtained by calling Object.clone() and
+ * cloning the config object.
*/
@Override
public SimulationExtension clone() {
diff --git a/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java b/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java
index 26fd6a137..f0b6c93c2 100644
--- a/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java
+++ b/core/src/net/sf/openrocket/simulation/extension/SimulationExtension.java
@@ -2,6 +2,9 @@ package net.sf.openrocket.simulation.extension;
import java.util.List;
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
@@ -35,6 +38,16 @@ public interface SimulationExtension {
*/
public String getDescription();
+ /**
+ * Called once for each simulation this extension is attached to when loading a document.
+ * This may perform necessary changes to the document at load time.
+ *
+ * @param document the loaded document
+ * @param simulation the simulation this extension is attached to
+ * @param warnings the document loading warnings
+ */
+ public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings);
+
/**
* Initialize this simulation extension for running within a simulation.
* This method is called before running a simulation. It can either modify
diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java
index 03785c036..d3d3380b3 100644
--- a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java
+++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingExtension.java
@@ -5,18 +5,30 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import net.sf.openrocket.aerodynamics.Warning;
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
+import com.google.inject.Inject;
+
public class ScriptingExtension extends AbstractSimulationExtension {
private static final String DEFAULT_LANGUAGE = "JavaScript";
+ @Inject
+ private ScriptingUtil util;
+
+
public ScriptingExtension() {
setLanguage(DEFAULT_LANGUAGE);
+ setScript("");
+ setEnabled(true);
}
@Override
@@ -31,9 +43,25 @@ public class ScriptingExtension extends AbstractSimulationExtension {
return trans.get("SimulationExtension.scripting.desc");
}
+ @Override
+ public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings) {
+ /*
+ * Scripts that the user has not explicitly indicated as trusted are disabled
+ * when loading from a file. This is to prevent trojans.
+ */
+ if (isEnabled()) {
+ if (!util.isTrustedScript(getLanguage(), getScript())) {
+ setEnabled(false);
+ warnings.add(Warning.fromString(trans.get("SimulationExtension.scripting.warning.disabled")));
+ }
+ }
+ }
+
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
- conditions.getSimulationListenerList().add(getListener());
+ if (isEnabled()) {
+ conditions.getSimulationListenerList().add(getListener());
+ }
}
@@ -53,6 +81,14 @@ public class ScriptingExtension extends AbstractSimulationExtension {
config.put("language", language);
}
+ public boolean isEnabled() {
+ return config.getBoolean("enabled", false);
+ }
+
+ public void setEnabled(boolean enabled) {
+ config.put("enabled", enabled);
+ }
+
SimulationListener getListener() throws SimulationException {
ScriptEngineManager manager = new ScriptEngineManager();
diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java
index 9ca6edcd6..5eab2dfa8 100644
--- a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java
+++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java
@@ -18,6 +18,7 @@ import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationListenerException;
import net.sf.openrocket.simulation.listeners.SimulationComputationListener;
import net.sf.openrocket.simulation.listeners.SimulationEventListener;
import net.sf.openrocket.simulation.listeners.SimulationListener;
@@ -68,25 +69,25 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
- invoke(null, "startSimulation", status);
+ invoke(Void.class, null, "startSimulation", status);
}
@Override
public void endSimulation(SimulationStatus status, SimulationException exception) {
try {
- invoke(null, "endSimulation", status, exception);
+ invoke(Void.class, null, "endSimulation", status, exception);
} catch (SimulationException e) {
}
}
@Override
public boolean preStep(SimulationStatus status) throws SimulationException {
- return invoke(true, "preStep", status);
+ return invoke(Boolean.class, true, "preStep", status);
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
- invoke(null, "postStep", status);
+ invoke(Void.class, null, "postStep", status);
}
@@ -95,22 +96,22 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
- return invoke(true, "addFlightEvent", status, event);
+ return invoke(Boolean.class, true, "addFlightEvent", status, event);
}
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
- return invoke(true, "handleFlightEvent", status, event);
+ return invoke(Boolean.class, true, "handleFlightEvent", status, event);
}
@Override
public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException {
- return invoke(true, "motorIgnition", status, motorId, mount, instance);
+ return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance);
}
@Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
- return invoke(true, "recoveryDeviceDeployment", status, recoveryDevice);
+ return invoke(Boolean.class, true, "recoveryDeviceDeployment", status, recoveryDevice);
}
@@ -119,90 +120,99 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException {
- return invoke(null, "preAccelerationCalculation", status);
+ return invoke(AccelerationData.class, null, "preAccelerationCalculation", status);
}
@Override
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException {
- return invoke(null, "preAerodynamicCalculation", status);
+ return invoke(AerodynamicForces.class, null, "preAerodynamicCalculation", status);
}
@Override
public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException {
- return invoke(null, "preAtmosphericModel", status);
+ return invoke(AtmosphericConditions.class, null, "preAtmosphericModel", status);
}
@Override
public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException {
- return invoke(null, "preFlightConditions", status);
+ return invoke(FlightConditions.class, null, "preFlightConditions", status);
}
@Override
public double preGravityModel(SimulationStatus status) throws SimulationException {
- return invoke(Double.NaN, "preGravityModel", status);
+ return invoke(Double.class, Double.NaN, "preGravityModel", status);
}
@Override
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
- return invoke(null, "preMassCalculation", status);
+ return invoke(MassData.class, null, "preMassCalculation", status);
}
@Override
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException {
- return invoke(Double.NaN, "preSimpleThrustCalculation", status);
+ return invoke(Double.class, Double.NaN, "preSimpleThrustCalculation", status);
}
@Override
public Coordinate preWindModel(SimulationStatus status) throws SimulationException {
- return invoke(null, "preWindModel", status);
+ return invoke(Coordinate.class, null, "preWindModel", status);
}
@Override
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException {
- return invoke(null, "postAccelerationCalculation", status, acceleration);
+ return invoke(AccelerationData.class, null, "postAccelerationCalculation", status, acceleration);
}
@Override
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException {
- return invoke(null, "postAerodynamicCalculation", status, forces);
+ return invoke(AerodynamicForces.class, null, "postAerodynamicCalculation", status, forces);
}
@Override
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException {
- return invoke(null, "postAtmosphericModel", status, atmosphericConditions);
+ return invoke(AtmosphericConditions.class, null, "postAtmosphericModel", status, atmosphericConditions);
}
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
- return invoke(null, "postFlightConditions", status, flightConditions);
+ return invoke(FlightConditions.class, null, "postFlightConditions", status, flightConditions);
}
@Override
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException {
- return invoke(Double.NaN, "postGravityModel", status, gravity);
+ return invoke(Double.class, Double.NaN, "postGravityModel", status, gravity);
}
@Override
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
- return invoke(null, "postMassCalculation", status, massData);
+ return invoke(MassData.class, null, "postMassCalculation", status, massData);
}
@Override
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
- return invoke(Double.NaN, "postSimpleThrustCalculation", status, thrust);
+ return invoke(Double.class, Double.NaN, "postSimpleThrustCalculation", status, thrust);
}
@Override
public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
- return invoke(null, "postWindModel", status, wind);
+ return invoke(Coordinate.class, null, "postWindModel", status, wind);
}
@SuppressWarnings("unchecked")
- private T invoke(T def, String method, Object... args) throws SimulationException {
+ private T invoke(Class retType, T def, String method, Object... args) throws SimulationException {
try {
if (!missing.contains(method)) {
- return (T) invocable.invokeFunction(method, args);
+ Object o = invocable.invokeFunction(method, args);
+ if (o == null) {
+ // Use default/null if function returns nothing
+ return def;
+ } else if (!o.getClass().equals(retType)) {
+ throw new SimulationListenerException("Custom script function " + method + " returned type " +
+ o.getClass().getSimpleName() + ", expected " + retType.getSimpleName());
+ } else {
+ return (T) o;
+ }
}
} catch (NoSuchMethodException e) {
missing.add(method);
diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java
new file mode 100644
index 000000000..ac205ab59
--- /dev/null
+++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingUtil.java
@@ -0,0 +1,150 @@
+package net.sf.openrocket.simulation.extension.impl;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.prefs.BackingStoreException;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+
+import net.sf.openrocket.startup.Preferences;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.BugException;
+
+import com.google.inject.Inject;
+
+/**
+ * Utility class used by the scripting extension and its configurator.
+ */
+public class ScriptingUtil {
+
+ static final String NODE_ID = ScriptingExtension.class.getCanonicalName();
+
+ /** 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 = Arrays.asList("JavaScript");
+
+ @Inject
+ Preferences prefs;
+
+
+
+
+ /**
+ * Return the preferred internal language name based on a script language name.
+ *
+ * @return the preferred language name, or null if the language is not supported.
+ */
+ public String getLanguage(String language) {
+ if (language == null) {
+ return null;
+ }
+
+ ScriptEngineManager manager = new ScriptEngineManager();
+ ScriptEngine engine = manager.getEngineByName(language);
+ if (engine == null) {
+ return null;
+ }
+ return getLanguage(engine.getFactory());
+ }
+
+
+ public List getLanguages() {
+ List langs = new ArrayList();
+ ScriptEngineManager manager = new ScriptEngineManager();
+ for (ScriptEngineFactory factory : manager.getEngineFactories()) {
+ langs.add(getLanguage(factory));
+ }
+ return langs;
+ }
+
+
+ private String getLanguage(ScriptEngineFactory factory) {
+ for (String name : factory.getNames()) {
+ if (PREFERRED_LANGUAGE_NAMES.contains(name)) {
+ return name;
+ }
+ }
+
+ return factory.getLanguageName();
+ }
+
+
+
+ /**
+ * Test whether the user has indicated this script to be trusted,
+ * or if it is an internally trusted script.
+ */
+ public boolean isTrustedScript(String language, String script) {
+ if (language == null || script == null) {
+ return false;
+ }
+ script = normalize(script);
+ if (script.length() == 0) {
+ return true;
+ }
+ String hash = hash(language, script);
+ return prefs.getNode(NODE_ID).getBoolean(hash, false);
+ }
+
+ /**
+ * Mark a script as trusted.
+ */
+ public void setTrustedScript(String language, String script, boolean trusted) {
+ script = normalize(script);
+ String hash = hash(language, script);
+ if (trusted) {
+ prefs.getNode(NODE_ID).putBoolean(hash, true);
+ } else {
+ prefs.getNode(NODE_ID).remove(hash);
+ }
+ }
+
+ /**
+ * Clear all trusted scripts.
+ */
+ public void clearTrustedScripts() {
+ try {
+ prefs.getNode(NODE_ID).clear();
+ } catch (BackingStoreException e) {
+ throw new BugException(e);
+ }
+ }
+
+
+ static String normalize(String script) {
+ return script.replaceAll("\r", "").trim();
+ }
+
+ static String hash(String language, String script) {
+ /*
+ * NOTE: Hash length must be max 80 chars, the max length of a key in a Properties object.
+ */
+
+ String output;
+ MessageDigest digest;
+
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ digest.update(language.getBytes("UTF-8"));
+ digest.update((byte) '|');
+ byte[] hash = digest.digest(script.getBytes("UTF-8"));
+ BigInteger bigInt = new BigInteger(1, hash);
+ output = bigInt.toString(16);
+ while (output.length() < 64) {
+ output = "0" + output;
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new BugException("JRE does not support SHA-256 hash algorithm", e);
+ } catch (UnsupportedEncodingException e) {
+ throw new BugException(e);
+ }
+
+ return digest.getAlgorithm() + ":" + output;
+ }
+
+}
diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/TestScriptingUtil.java b/core/src/net/sf/openrocket/simulation/extension/impl/TestScriptingUtil.java
new file mode 100644
index 000000000..bd20a5fa8
--- /dev/null
+++ b/core/src/net/sf/openrocket/simulation/extension/impl/TestScriptingUtil.java
@@ -0,0 +1,88 @@
+package net.sf.openrocket.simulation.extension.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import net.sf.openrocket.startup.MockPreferences;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestScriptingUtil {
+
+ private static final String HASH_JavaScript_foobar = "SHA-256:8f06133e0235d239355b5ca8ca0b43dece803c29b2a563222519d982abd3fc43";
+
+ private ScriptingUtil util;
+
+ @Before
+ public void setup() {
+ util = new ScriptingUtil();
+ util.prefs = new MockPreferences();
+ }
+
+ /*
+ * Note: This class assumes that the JRE supports JavaScript scripting.
+ */
+
+ @Test
+ public void testGetLanguage() {
+ assertEquals(null, util.getLanguage(null));
+ assertEquals(null, util.getLanguage(""));
+ assertEquals(null, util.getLanguage("foobar"));
+ assertEquals("JavaScript", util.getLanguage("JavaScript"));
+ assertEquals("JavaScript", util.getLanguage("javascript"));
+ assertEquals("JavaScript", util.getLanguage("ECMAScript"));
+ assertEquals("JavaScript", util.getLanguage("js"));
+ }
+
+
+ @Test
+ public void testGetLanguages() {
+ assertTrue(util.getLanguages().size() >= 1);
+ assertTrue(util.getLanguages().contains("JavaScript"));
+ }
+
+ @Test
+ public void testIsTrustedScript() {
+ util.setTrustedScript("JavaScript", "foobar", true);
+ assertTrue(util.isTrustedScript("JavaScript", "foobar"));
+ assertTrue(util.isTrustedScript("JavaScript", " \n foobar \n\t\r"));
+ assertFalse(util.isTrustedScript("JavaScript", "foo\nbar"));
+ assertFalse(util.isTrustedScript("Javascript", "foobar"));
+
+ // Empty script is always considered trusted
+ assertFalse(util.isTrustedScript("foo", null));
+ assertTrue(util.isTrustedScript("foo", ""));
+ assertTrue(util.isTrustedScript("foo", " \n\r\t "));
+ }
+
+ @Test
+ public void testSetTrustedScript() {
+ util.setTrustedScript("JavaScript", " \n foobar \n\r ", true);
+ assertTrue(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, false));
+ util.setTrustedScript("JavaScript", " foobar ", false);
+ assertTrue(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, true));
+ assertFalse(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, false));
+ }
+
+ @Test
+ public void testClearTrustedScripts() {
+ util.setTrustedScript("JavaScript", "foobar", true);
+ assertTrue(util.isTrustedScript("JavaScript", "foobar"));
+ util.clearTrustedScripts();
+ assertFalse(util.isTrustedScript("JavaScript", "foobar"));
+ }
+
+ @Test
+ public void testNormalize() {
+ assertEquals("foo", ScriptingUtil.normalize("foo"));
+ assertEquals("foo bar", ScriptingUtil.normalize(" \n\r\t foo \r bar \n\t\r "));
+ }
+
+ @Test
+ public void testHash() {
+ assertEquals("SHA-256:12e6a78889b96a16d305b8e4af81119545f89eccba5fb37cc3a1ec2c53eab514", ScriptingUtil.hash("JS", ""));
+ assertEquals("SHA-256:000753e5deb2d8fa80e602ca03bcdb8e12a6b14b2b4a4d0abecdc976ad26e3ef", ScriptingUtil.hash("foo", "1165"));
+ assertEquals(HASH_JavaScript_foobar, ScriptingUtil.hash("JavaScript", "foobar"));
+ }
+}
diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java
index 0f9c7f3e7..c72008bf6 100644
--- a/core/src/net/sf/openrocket/startup/Preferences.java
+++ b/core/src/net/sf/openrocket/startup/Preferences.java
@@ -92,6 +92,8 @@ public abstract class Preferences {
public abstract void putString(String directory, String key, String value);
+ public abstract java.util.prefs.Preferences getNode(String nodeName);
+
/*
* ******************************************************************************************
*/
diff --git a/core/src/net/sf/openrocket/util/ScriptingUtil.java b/core/src/net/sf/openrocket/util/ScriptingUtil.java
deleted file mode 100644
index 35653255c..000000000
--- a/core/src/net/sf/openrocket/util/ScriptingUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package net.sf.openrocket.util;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
-
-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 = Arrays.asList("JavaScript");
-
- /**
- * Return the preferred internal language name based on a script language name.
- *
- * @return the preferred language name, or null if the language is not supported.
- */
- public static String getLanguage(String language) {
- if (language == null) {
- return null;
- }
-
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName(language);
- if (engine == null) {
- return null;
- }
- return getLanguage(engine.getFactory());
- }
-
-
- public static List getLanguages() {
- List langs = new ArrayList();
- ScriptEngineManager manager = new ScriptEngineManager();
- for (ScriptEngineFactory factory : manager.getEngineFactories()) {
- langs.add(getLanguage(factory));
- }
- return langs;
- }
-
-
- private static String getLanguage(ScriptEngineFactory factory) {
- for (String name : factory.getNames()) {
- if (PREFERRED_LANGUAGE_NAMES.contains(name)) {
- return name;
- }
- }
-
- return factory.getLanguageName();
- }
-}
diff --git a/core/test/net/sf/openrocket/ServicesForTesting.java b/core/test/net/sf/openrocket/ServicesForTesting.java
index 2d6ec6743..f80300cd5 100644
--- a/core/test/net/sf/openrocket/ServicesForTesting.java
+++ b/core/test/net/sf/openrocket/ServicesForTesting.java
@@ -151,5 +151,11 @@ public class ServicesForTesting extends AbstractModule {
return null;
}
+ @Override
+ public java.util.prefs.Preferences getNode(String nodeName) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
}
}
diff --git a/core/test/net/sf/openrocket/startup/MockPreferences.java b/core/test/net/sf/openrocket/startup/MockPreferences.java
new file mode 100644
index 000000000..eabfe18d9
--- /dev/null
+++ b/core/test/net/sf/openrocket/startup/MockPreferences.java
@@ -0,0 +1,108 @@
+package net.sf.openrocket.startup;
+
+import java.util.Set;
+import java.util.prefs.BackingStoreException;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+import net.sf.openrocket.util.BugException;
+
+public class MockPreferences extends Preferences {
+
+ private final String NODENAME = "OpenRocket-test-mock";
+ private final java.util.prefs.Preferences NODE;
+
+ public MockPreferences() {
+ java.util.prefs.Preferences root = java.util.prefs.Preferences.userRoot();
+ try {
+ if (root.nodeExists(NODENAME)) {
+ root.node(NODENAME).removeNode();
+ }
+ } catch (BackingStoreException e) {
+ throw new BugException("Unable to clear preference node", e);
+ }
+ NODE = root.node(NODENAME);
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean def) {
+ return NODE.getBoolean(key, def);
+ }
+
+ @Override
+ public void putBoolean(String key, boolean value) {
+ NODE.putBoolean(key, value);
+ }
+
+ @Override
+ public int getInt(String key, int def) {
+ return NODE.getInt(key, def);
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ NODE.putInt(key, value);
+ }
+
+ @Override
+ public double getDouble(String key, double def) {
+ return NODE.getDouble(key, def);
+ }
+
+ @Override
+ public void putDouble(String key, double value) {
+ NODE.putDouble(key, value);
+ }
+
+ @Override
+ public String getString(String key, String def) {
+ return NODE.get(key, def);
+ }
+
+ @Override
+ public void putString(String key, String value) {
+ NODE.put(key, value);
+ }
+
+ @Override
+ public String getString(String directory, String key, String def) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public void putString(String directory, String key, String value) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public java.util.prefs.Preferences getNode(String nodeName) {
+ return NODE.node(nodeName);
+ }
+
+ @Override
+ public void addUserMaterial(Material m) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public Set getUserMaterials() {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public void removeUserMaterial(Material m) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public void setComponentFavorite(ComponentPreset preset, Type type, boolean favorite) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public Set getComponentFavorites(Type type) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+}
diff --git a/core/test/net/sf/openrocket/util/TestScriptingUtil.java b/core/test/net/sf/openrocket/util/TestScriptingUtil.java
deleted file mode 100644
index 1d657f507..000000000
--- a/core/test/net/sf/openrocket/util/TestScriptingUtil.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package net.sf.openrocket.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public class TestScriptingUtil {
-
- /*
- * Note: This class assumes that the JRE supports JavaScript scripting.
- */
-
- @Test
- public void testGetLanguage() {
- assertEquals(null, ScriptingUtil.getLanguage(null));
- assertEquals(null, ScriptingUtil.getLanguage(""));
- assertEquals(null, ScriptingUtil.getLanguage("foobar"));
- assertEquals("JavaScript", ScriptingUtil.getLanguage("JavaScript"));
- assertEquals("JavaScript", ScriptingUtil.getLanguage("javascript"));
- assertEquals("JavaScript", ScriptingUtil.getLanguage("ECMAScript"));
- assertEquals("JavaScript", ScriptingUtil.getLanguage("js"));
- }
-
-
- @Test
- public void testGetLanguages() {
- assertTrue(ScriptingUtil.getLanguages().size() >= 1);
- assertTrue(ScriptingUtil.getLanguages().contains("JavaScript"));
- }
-
-}
diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
index d91776808..a4b3587f3 100644
--- a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
+++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
@@ -186,7 +186,7 @@ class SimulationOptionsPanel extends JPanel {
sub.add(desc, "aligny 0, hmin 100lp, growx, wrap para");
- final JButton addExtension = new JButton("Add extension");
+ final JButton addExtension = new JButton(trans.get("simedtdlg.SimExt.add"));
final JPopupMenu menu = getExtensionMenu();
addExtension.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
@@ -223,6 +223,10 @@ class SimulationOptionsPanel extends JPanel {
SimulationExtension e = provider.getInstance(id);
simulation.getSimulationExtensions().add(e);
updateCurrentExtensions();
+ SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
+ if (configurator != null) {
+ configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
+ }
}
});
menu.add(item);
@@ -344,86 +348,17 @@ class SimulationOptionsPanel extends JPanel {
this.add(button, "right");
}
-
- private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
- Set configurators = Application.getInjector().getInstance(new Key>() {
- });
- for (SwingSimulationExtensionConfigurator c : configurators) {
- if (c.support(extension)) {
- return c;
- }
- }
- return null;
- }
}
- //
- //
- // private class ExtensionListModel extends AbstractListModel {
- // @Override
- // public SimulationExtensionConfiguration getElementAt(int index) {
- // if (index < 0 || index >= getSize())
- // return null;
- // return simulation.getSimulationExtensions().get(index);
- // }
- //
- // @Override
- // public int getSize() {
- // return simulation.getSimulationExtensions().size();
- // }
- // }
- //
- //
- // private class ExtensionCellRenderer extends JPanel implements ListCellRenderer {
- // private JLabel label;
- //
- // public ExtensionCellRenderer() {
- // super(new MigLayout("fill"));
- // label = new JLabel();
- //
- // }
- //
- // @Override
- // public Component getListCellRendererComponent(JList list, Object value,
- // int index, boolean isSelected, boolean cellHasFocus) {
- // SimulationExtensionConfiguration config = (SimulationExtensionConfiguration) value;
- //
- //
- //
- // String s = value.toString();
- // setText(s);
- //
- // // Attempt instantiating, catch any exceptions
- // Exception ex = null;
- // try {
- // Class> c = Class.forName(s);
- // @SuppressWarnings("unused")
- // SimulationListener l = (SimulationListener) c.newInstance();
- // } catch (Exception e) {
- // ex = e;
- // }
- //
- // if (ex == null) {
- // setIcon(Icons.SIMULATION_LISTENER_OK);
- // //// Listener instantiated successfully.
- // setToolTipText("Listener instantiated successfully.");
- // } else {
- // setIcon(Icons.SIMULATION_LISTENER_ERROR);
- // //// Unable to instantiate listener due to exception:
- // setToolTipText("Unable to instantiate listener due to exception:
" +
- // ex.toString());
- // }
- //
- // if (isSelected) {
- // setBackground(list.getSelectionBackground());
- // setForeground(list.getSelectionForeground());
- // } else {
- // setBackground(list.getBackground());
- // setForeground(list.getForeground());
- // }
- // setOpaque(true);
- // return this;
- // }
- // }
+ private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
+ Set configurators = Application.getInjector().getInstance(new Key>() {
+ });
+ for (SwingSimulationExtensionConfigurator c : configurators) {
+ if (c.support(extension)) {
+ return c;
+ }
+ }
+ return null;
+ }
}
diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
index 499620c9b..b4bd15cdf 100644
--- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
+++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
@@ -45,8 +45,8 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
for (String lang : new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt", "tr" }) {
list.add(new Locale(lang));
}
- list.add(new Locale("zh","CN"));
- list.add(new Locale("uk","UA"));
+ list.add(new Locale("zh", "CN"));
+ list.add(new Locale("uk", "UA"));
SUPPORTED_LOCALES = Collections.unmodifiableList(list);
}
@@ -200,6 +200,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
* @param nodeName the node name
* @return the preferences object for that node
*/
+ @Override
public Preferences getNode(String nodeName) {
return PREFNODE.node(nodeName);
}
@@ -418,11 +419,11 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public boolean computeFlightInBackground() {
return PREFNODE.getBoolean("backgroundFlight", true);
}
-
+
public void setComputeFlightInBackground(boolean b) {
PREFNODE.putBoolean("backgroundFlight", b);
}
-
+
public Simulation getBackgroundSimulation(Rocket rocket) {
Simulation s = new Simulation(rocket);
SimulationOptions cond = s.getOptions();
diff --git a/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java b/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java
index 9c6af0ba8..8eb2ea1ae 100644
--- a/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java
+++ b/swing/src/net/sf/openrocket/simulation/extension/AbstractSwingSimulationExtensionConfigurator.java
@@ -57,6 +57,7 @@ public abstract class AbstractSwingSimulationExtensionConfigurator {
+ @Inject
+ private ScriptingUtil util;
+
private JComboBox languageSelector;
private RSyntaxTextArea text;
+ private JCheckBox trusted;
private ScriptingExtension extension;
private Simulation simulation;
@@ -43,9 +52,9 @@ public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfi
this.extension = extension;
this.simulation = simulation;
- panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.language.label"), Style.BOLD), "");
+ panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.language.label"), Style.BOLD), "spanx, split");
- String[] languages = ScriptingUtil.getLanguages().toArray(new String[0]);
+ String[] languages = util.getLanguages().toArray(new String[0]);
languageSelector = new JComboBox(languages);
languageSelector.setEditable(false);
languageSelector.addActionListener(new ActionListener() {
@@ -79,13 +88,48 @@ public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfi
});
RTextScrollPane scroll = new RTextScrollPane(text);
- panel.add(scroll, "spanx, grow");
+ panel.add(scroll, "spanx, grow, wrap para");
- setLanguage(ScriptingUtil.getLanguage(extension.getLanguage()));
+
+ BooleanModel enabled = new BooleanModel(extension, "Enabled");
+ JCheckBox check = new JCheckBox(enabled);
+ check.setText(trans.get("SimulationExtension.scripting.text.enabled"));
+ check.setToolTipText(trans.get("SimulationExtension.scripting.text.enabled.ttip"));
+ panel.add(check, "spanx, wrap rel");
+
+ trusted = new JCheckBox(trans.get("SimulationExtension.scripting.text.trusted"));
+ trusted.setSelected(util.isTrustedScript(extension.getLanguage(), extension.getScript()));
+ panel.add(trusted, "spanx, split");
+
+ panel.add(new JPanel(), "growx");
+
+ JButton button = new JButton(trans.get("SimulationExtension.scripting.text.trusted.clear"));
+ button.setToolTipText(trans.get("SimulationExtension.scripting.text.trusted.clear.ttip"));
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ util.clearTrustedScripts();
+ JOptionPane.showMessageDialog(getDialog(), trans.get("SimulationExtension.scripting.text.trusted.cleared"),
+ trans.get("SimulationExtension.scripting.text.trusted.cleared.title"), JOptionPane.INFORMATION_MESSAGE);
+ }
+ });
+ panel.add(button, "wrap rel");
+
+
+ StyledLabel label = new StyledLabel(trans.get("SimulationExtension.scripting.text.trusted.msg"), -1, Style.ITALIC);
+ panel.add(label);
+
+ setLanguage(util.getLanguage(extension.getLanguage()));
return panel;
}
+ @Override
+ protected void close() {
+ util.setTrustedScript(extension.getLanguage(), extension.getScript(), trusted.isSelected());
+ }
+
+
private void setLanguage(String language) {
if (language == null) {
language = "";