Merge pull request #226 from plaa/feature/scripting

Feature/scripting
This commit is contained in:
kruland2607 2015-01-10 14:29:11 -06:00
commit 073c868a1e
58 changed files with 2820 additions and 363 deletions

View File

@ -47,3 +47,6 @@ The following file format versions exist:
1.6: Introduced with OpenRocket 13.04. Added component Appearances (decals & paint)
Added configurable parameters to recovery devices, motor ignition and separation.
1.7: Introduced with OpenRocket 15.01. Added simulation extensions and related
configuration.

View File

@ -402,11 +402,11 @@ simedtdlg.lbl.Timestep = Time step:
simedtdlg.lbl.ttip.Timestep1 = <html>The time between simulation steps.<br>A smaller time step results in a more accurate but slower simulation.<br>
simedtdlg.lbl.ttip.Timestep2 = The 4<sup>th</sup> order simulation method is quite accurate with a time step of
simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
simedtdlg.border.Simlist = Simulator listeners
simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
simedtdlg.txt.longA2 = For details on writing simulation listeners, see the OpenRocket technical documentation.
simedtdlg.lbl.Curlist = Current listeners:
simedtdlg.lbl.Addsimlist = Add simulation listener
simedtdlg.border.SimExt = Simulation extensions
simedtdlg.SimExt.desc = <html><i>Simulation extensions</i> 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.SimExt.copyExtension = Copy extension
simedtdlg.lbl.Noflightdata = No flight data available.
simedtdlg.lbl.runsimfirst = Please run the simulation first.
simedtdlg.chart.Simflight = Simulated flight
@ -419,6 +419,26 @@ simedtdlg.IntensityDesc.High = High
simedtdlg.IntensityDesc.Veryhigh = Very high
simedtdlg.IntensityDesc.Extreme = Extreme
SimulationExtension.airstart.name.alt = Air-start ({alt})
SimulationExtension.airstart.name.altvel = Air-start ({alt}, {vel})
SimulationExtension.javacode.name = Java code
SimulationExtension.javacode.name.none = none
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
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 in the Simulation options.
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
SimulationEditDialog.btn.edit = Edit

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

View File

@ -23,7 +23,7 @@ import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@ -146,17 +146,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
// simulation listeners
for (Simulation sim : simulations) {
for (String className : sim.getSimulationListeners()) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
Collections.addAll(allTypes, l.getFlightDataTypes());
//System.out.println("This document has expression datatype from "+l.getName());
} catch (Exception e) {
log.error("Could not instantiate listener: " + className);
}
for (SimulationExtension c : sim.getSimulationExtensions()) {
allTypes.addAll(c.getFlightDataTypes());
}
}

View File

@ -28,7 +28,7 @@ import net.sf.openrocket.simulation.SimulationEngine;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.SimulationStepper;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@ -86,7 +86,8 @@ public class Simulation implements ChangeSource, Cloneable {
// TODO: HIGH: Change to use actual conditions class??
private SimulationOptions options;
private ArrayList<String> simulationListeners = new ArrayList<String>();
private ArrayList<SimulationExtension> simulationExtensions = new ArrayList<SimulationExtension>();
private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
@ -126,7 +127,7 @@ public class Simulation implements ChangeSource, Cloneable {
public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
List<String> listeners, FlightData data) {
List<SimulationExtension> extensions, FlightData data) {
if (rocket == null)
throw new IllegalArgumentException("rocket cannot be null");
@ -152,8 +153,8 @@ public class Simulation implements ChangeSource, Cloneable {
this.options = options;
options.addChangeListener(new ConditionListener());
if (listeners != null) {
this.simulationListeners.addAll(listeners);
if (extensions != null) {
this.simulationExtensions.addAll(extensions);
}
@ -206,14 +207,14 @@ public class Simulation implements ChangeSource, Cloneable {
/**
* Get the list of simulation listeners. The returned list is the one used by
* Get the list of simulation extensions. The returned list is the one used by
* this object; changes to it will reflect changes in the simulation.
*
* @return the actual list of simulation listeners.
* @return the actual list of simulation extensions.
*/
public List<String> getSimulationListeners() {
public List<SimulationExtension> getSimulationExtensions() {
mutex.verify();
return simulationListeners;
return simulationExtensions;
}
@ -325,16 +326,8 @@ public class Simulation implements ChangeSource, Cloneable {
simulationConditions.getSimulationListenerList().add(l);
}
for (String className : simulationListeners) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
} catch (Exception e) {
throw new SimulationListenerException("Could not instantiate listener of " +
"class: " + className, e);
}
simulationConditions.getSimulationListenerList().add(l);
for (SimulationExtension extension : simulationExtensions) {
extension.initialize(simulationConditions);
}
long t1, t2;
@ -442,7 +435,10 @@ public class Simulation implements ChangeSource, Cloneable {
copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED;
copy.options = this.options.clone();
copy.simulationListeners = this.simulationListeners.clone();
copy.simulationExtensions = new ArrayList<SimulationExtension>();
for (SimulationExtension c : this.simulationExtensions) {
copy.simulationExtensions.add(c.clone());
}
copy.listeners = new ArrayList<EventListener>();
copy.simulatedConditions = null;
copy.simulatedConfiguration = null;
@ -474,7 +470,9 @@ public class Simulation implements ChangeSource, Cloneable {
copy.name = this.name;
copy.options.copyFrom(this.options);
copy.simulatedConfiguration = this.simulatedConfiguration;
copy.simulationListeners = this.simulationListeners.clone();
for (SimulationExtension c : this.simulationExtensions) {
copy.simulationExtensions.add(c.clone());
}
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;

View File

@ -30,8 +30,10 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.BuildProperties;
import net.sf.openrocket.util.Config;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.TextUtil;
@ -217,6 +219,11 @@ public class OpenRocketSaver extends RocketSaver {
*/
private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
/*
* NOTE: Remember to update the supported versions in DocumentConfig as well!
*
* File version 1.7 is required for:
* - simulation extensions
*
* File version 1.6 is required for:
* - saving files using appearances and textures, flight configurations.
*
@ -236,6 +243,16 @@ public class OpenRocketSaver extends RocketSaver {
* Otherwise use version 1.0.
*/
/////////////////
// Version 1.7 //
/////////////////
for (Simulation sim : document.getSimulations()) {
if (!sim.getSimulationExtensions().isEmpty()) {
return FILE_VERSION_DIVISOR + 7;
}
}
/////////////////
// Version 1.6 //
/////////////////
@ -455,9 +472,18 @@ public class OpenRocketSaver extends RocketSaver {
indent--;
writeln("</conditions>");
for (String s : simulation.getSimulationListeners()) {
writeElement("listener", TextUtil.escapeXML(s));
for (SimulationExtension extension : simulation.getSimulationExtensions()) {
Config config = extension.getConfig();
writeln("<extension extensionid=\"" + TextUtil.escapeXML(extension.getId()) + "\">");
indent++;
if (config != null) {
for (String key : config.keySet()) {
Object value = config.get(key, null);
writeEntry(key, value);
}
}
indent--;
writeln("</extension>");
}
// Write basic simulation data
@ -512,6 +538,39 @@ public class OpenRocketSaver extends RocketSaver {
}
private void writeEntry(String key, Object value) throws IOException {
if (value == null) {
return;
}
String keyAttr;
if (key != null) {
keyAttr = "key=\"" + key + "\" ";
} else {
keyAttr = "";
}
if (value instanceof Boolean) {
writeln("<entry " + keyAttr + "type=\"boolean\">" + value + "</entry>");
} else if (value instanceof Number) {
writeln("<entry " + keyAttr + "type=\"number\">" + value + "</entry>");
} else if (value instanceof String) {
writeln("<entry " + keyAttr + "type=\"string\">" + TextUtil.escapeXML((String) value) + "</entry>");
} else if (value instanceof List) {
List<?> list = (List<?>) value;
writeln("<entry " + keyAttr + "type=\"list\">");
indent++;
for (Object o : list) {
writeEntry(null, o);
}
indent--;
writeln("</entry>");
} else {
// Unknown type
log.error("Unknown configuration value type " + value.getClass() + " value=" + value);
}
}
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
throws IOException {
double previousTime = -100000;

View File

@ -0,0 +1,89 @@
package net.sf.openrocket.file.openrocket.importt;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.Config;
import org.xml.sax.SAXException;
public class ConfigHandler extends AbstractElementHandler {
private ConfigHandler listHandler;
private Config config = new Config();
private List<Object> list = new ArrayList<Object>();
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
if (element.equals("entry") && "list".equals(attributes.get("type"))) {
listHandler = new ConfigHandler();
return listHandler;
} else {
return PlainTextHandler.INSTANCE;
}
}
@Override
public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
if (element.equals("entry")) {
String key = attributes.get("key");
String type = attributes.get("type");
Object value = null;
if ("boolean".equals(type)) {
value = Boolean.valueOf(content);
} else if ("string".equals(type)) {
value = content;
} else if ("number".equals(type)) {
value = parseNumber(content);
} else if ("list".equals(type)) {
value = listHandler.list;
}
if (value != null) {
if (key != null) {
config.put(key, value);
} else {
list.add(value);
}
}
} else {
super.closeElement(element, attributes, content, warnings);
}
}
private Number parseNumber(String str) {
try {
str = str.trim();
if (str.matches("^[+-]?[0-9]+$")) {
BigInteger value = new BigInteger(str, 10);
if (value.equals(BigInteger.valueOf(value.intValue()))) {
return value.intValue();
} else if (value.equals(BigInteger.valueOf(value.longValue()))) {
return value.longValue();
} else {
return value;
}
} else {
BigDecimal value = new BigDecimal(str);
if (value.equals(BigDecimal.valueOf(value.doubleValue()))) {
return value.doubleValue();
} else {
return value;
}
}
} catch (NumberFormatException e) {
return null;
}
}
public Config getConfig() {
return config;
}
}

View File

@ -48,7 +48,7 @@ import net.sf.openrocket.util.Reflection;
class DocumentConfig {
/* Remember to update OpenRocketSaver as well! */
public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6" };
public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7" };
/**
* Divisor used in converting an integer version to the point-represented version.

View File

@ -13,6 +13,7 @@ import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.file.simplesax.SimpleSAX;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -77,12 +78,18 @@ public class OpenRocketLoader extends AbstractRocketLoader {
previousTime = time;
}
}
// Round value
timeSkip = Math.rint(timeSkip * 100) / 100;
doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip);
doc.getDefaultStorageOptions().setExplicitlySet(false);
// Call simulation extensions
for (Simulation sim : doc.getSimulations()) {
for (SimulationExtension ext : sim.getSimulationExtensions()) {
ext.documentLoaded(doc, sim, warnings);
}
}
doc.clearUndo();
log.info("Loading done");
}

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.file.openrocket.importt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
@ -14,6 +15,13 @@ import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.extension.SimulationExtensionProvider;
import net.sf.openrocket.simulation.extension.impl.JavaCode;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.StringUtil;
import com.google.inject.Key;
class SingleSimulationHandler extends AbstractElementHandler {
@ -24,9 +32,10 @@ class SingleSimulationHandler extends AbstractElementHandler {
private String name;
private SimulationConditionsHandler conditionHandler;
private ConfigHandler configHandler;
private FlightDataHandler dataHandler;
private final List<String> listeners = new ArrayList<String>();
private final List<SimulationExtension> extensions = new ArrayList<SimulationExtension>();
public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
this.doc = doc;
@ -47,6 +56,9 @@ class SingleSimulationHandler extends AbstractElementHandler {
} else if (element.equals("conditions")) {
conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
return conditionHandler;
} else if (element.equals("extension")) {
configHandler = new ConfigHandler();
return configHandler;
} else if (element.equals("flightdata")) {
dataHandler = new FlightDataHandler(this, context);
return dataHandler;
@ -71,7 +83,23 @@ class SingleSimulationHandler extends AbstractElementHandler {
warnings.add("Unknown calculator '" + content.trim() + "' specified, ignoring.");
}
} else if (element.equals("listener") && content.trim().length() > 0) {
listeners.add(content.trim());
extensions.add(compatibilityExtension(content.trim()));
} else if (element.equals("extension") && !StringUtil.isEmpty(attributes.get("extensionid"))) {
String id = attributes.get("extensionid");
SimulationExtension extension = null;
Set<SimulationExtensionProvider> extensionProviders = Application.getInjector().getInstance(new Key<Set<SimulationExtensionProvider>>() {
});
for (SimulationExtensionProvider p : extensionProviders) {
if (p.getIds().contains(id)) {
extension = p.getInstance(id);
}
}
if (extension != null) {
extension.setConfig(configHandler.getConfig());
extensions.add(extension);
} else {
warnings.add("Simulation extension with id '" + id + "' not found.");
}
}
}
@ -105,8 +133,16 @@ class SingleSimulationHandler extends AbstractElementHandler {
data = dataHandler.getFlightData();
Simulation simulation = new Simulation(doc.getRocket(), status, name,
conditions, listeners, data);
conditions, extensions, data);
doc.addSimulation(simulation);
}
private SimulationExtension compatibilityExtension(String className) {
JavaCode extension = Application.getInjector().getInstance(JavaCode.class);
extension.setClassName(className);
return extension;
}
}

View File

@ -42,9 +42,8 @@ public class PluginModule extends AbstractModule {
if (c.isInterface())
continue;
for (Class<?> intf : c.getInterfaces()) {
if (interfaces.contains(intf)) {
for (Class<?> intf : interfaces) {
if (intf.isAssignableFrom(c)) {
// Ugly hack to enable dynamic binding... Can this be done type-safely?
Multibinder<Object> binder = (Multibinder<Object>) findBinder(intf);
binder.addBinding().to(c);

View File

@ -12,6 +12,7 @@ import net.sf.openrocket.models.wind.WindModel;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.GeodeticComputationStrategy;
import net.sf.openrocket.util.Monitorable;
import net.sf.openrocket.util.WorldCoordinate;
@ -38,11 +39,14 @@ public class SimulationConditions implements Monitorable, Cloneable {
/** Launch rod direction, 0 = north */
private double launchRodDirection = 0;
// TODO: Depreciate these and use worldCoordinate only.
//private double launchAltitude = 0;
//private double launchLatitude = 45;
//private double launchLongitude = 0;
// Launch site location (lat, lon, alt)
private WorldCoordinate launchSite = new WorldCoordinate(0, 0, 0);
// Launch location in simulation coordinates (normally always 0, air-start would override this)
private Coordinate launchPosition = Coordinate.NUL;
private Coordinate launchVelocity = Coordinate.NUL;
private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
@ -166,6 +170,29 @@ public class SimulationConditions implements Monitorable, Cloneable {
}
public Coordinate getLaunchPosition() {
return launchPosition;
}
public void setLaunchPosition(Coordinate launchPosition) {
if (this.launchPosition.equals(launchPosition))
return;
this.launchPosition = launchPosition;
this.modID++;
}
public Coordinate getLaunchVelocity() {
return launchVelocity;
}
public void setLaunchVelocity(Coordinate launchVelocity) {
if (this.launchVelocity.equals(launchVelocity))
return;
this.launchVelocity = launchVelocity;
this.modID++;
}
public GeodeticComputationStrategy getGeodeticComputation() {
return geodeticComputation;
}

View File

@ -95,8 +95,8 @@ public class SimulationStatus implements Monitorable {
this.time = 0;
this.previousTimeStep = this.simulationConditions.getTimeStep();
this.position = Coordinate.NUL;
this.velocity = Coordinate.NUL;
this.position = this.simulationConditions.getLaunchPosition();
this.velocity = this.simulationConditions.getLaunchVelocity();
this.worldPosition = this.simulationConditions.getLaunchSite();
// Initialize to roll angle with least stability w.r.t. the wind

View File

@ -0,0 +1,122 @@
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;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Config;
import com.google.inject.Inject;
/**
* An abstract implementation of a SimulationExtension.
*/
public abstract class AbstractSimulationExtension extends AbstractChangeSource implements SimulationExtension, Cloneable {
@Inject
protected Translator trans;
protected Config config = new Config();
private final String name;
/**
* Use the current class name as the extension name. You should override
* getName if you use this constructor.
*/
protected AbstractSimulationExtension() {
this(null);
}
/**
* Use the provided name as a static name for this extension.
*/
protected AbstractSimulationExtension(String name) {
if (name != null) {
this.name = name;
} else {
this.name = this.getClass().getSimpleName();
}
}
/**
* {@inheritDoc}
* <p>
* By default, this method returns the canonical name of this class.
*/
@Override
public String getId() {
return this.getClass().getCanonicalName();
}
/**
* {@inheritDoc}
* <p>
* By default, this method returns the name provided to the constructor.
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
* <p>
* By default, this method returns null.
*/
@Override
public String getDescription() {
return null;
}
/**
* {@inheritDoc}
* <p>
* By default, this method returns an empty list.
*/
@Override
public List<FlightDataType> getFlightDataTypes() {
return Collections.emptyList();
}
/**
* {@inheritDoc}
* <p>
* 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() {
try {
AbstractSimulationExtension copy = (AbstractSimulationExtension) super.clone();
copy.config = this.config.clone();
return copy;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
}
@Override
public Config getConfig() {
return config.clone();
}
@Override
public void setConfig(Config config) {
this.config = config.clone();
fireChangeEvent();
}
}

View File

@ -0,0 +1,65 @@
package net.sf.openrocket.simulation.extension;
import java.util.Arrays;
import java.util.List;
import net.sf.openrocket.l10n.Translator;
import com.google.inject.Inject;
import com.google.inject.Injector;
/**
* An abstract implementation of a SimulationExtensionProvider. The constructor is
* provided by the class of the SimulationExtension and the name of the extension.
*/
public abstract class AbstractSimulationExtensionProvider implements SimulationExtensionProvider {
@Inject
private Injector injector;
@Inject
protected Translator trans;
protected final Class<? extends SimulationExtension> extensionClass;
private final String[] name;
/**
* Sole constructor.
*
* @param extensionClass the simulation extension class
* @param name the name returned by getName
*/
protected AbstractSimulationExtensionProvider(Class<? extends SimulationExtension> extensionClass, String... name) {
this.extensionClass = extensionClass;
this.name = name;
}
/**
* By default returns the canonical name of the simulation extension class.
*/
@Override
public List<String> getIds() {
return Arrays.asList(extensionClass.getCanonicalName());
}
/**
* By default returns the provided extension name for the first ID that getIds returns.
*/
@Override
public List<String> getName(String id) {
if (id.equals(getIds().get(0))) {
return Arrays.asList(name);
}
return null;
}
/**
* By default returns a new instance of the simulation extension class instantiated by
* Class.newInstance.
*/
@Override
public SimulationExtension getInstance(String id) {
return injector.getInstance(extensionClass);
}
}

View File

@ -0,0 +1,94 @@
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;
import net.sf.openrocket.util.Config;
public interface SimulationExtension {
/**
* Return the simulation extension ID that is used when storing this
* extension to a file.
*
* @return the extension ID
*/
public String getId();
/**
* Return a short description of this extension. The name may contain
* elements from the extension's configuration, for example
* "Air start (150m)".
*
* @return a short name / description of this extension to be shown in the UI (must not be null)
*/
public String getName();
/**
* Return a longer description text for this extension, if available.
* This description may be shown in the UI as extra information about
* the extension.
*
* @return a longer description about this extension, or null if not available
*/
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
* the simulation conditions or add simulation listeners to it.
*
* @param conditions the simulation conditions to be run
* @param configuration the extension configuration
*/
public void initialize(SimulationConditions conditions) throws SimulationException;
/**
* Return a list of any flight data types this simulation extension creates.
* This should only contain new types created by this extension, not existing
* types that the extension adds to the flight data.
*/
public List<FlightDataType> getFlightDataTypes();
/**
* Return a copy of this simulation extension, with all configuration deep-copied.
*
* @return a new copy of this simulation extension
*/
public SimulationExtension clone();
/**
* Return a Config object describing the current configuration of this simulation
* extension. The extension may keep its configuration in a Config object, or create
* it when requested.
*
* @return the simulation extension configuration.
*/
public Config getConfig();
/**
* Set this simulation extension's configuration. The extension should load all its
* configuration from the provided Config object.
*
* @param config the configuration to set
*/
public void setConfig(Config config);
}

View File

@ -0,0 +1,133 @@
package net.sf.openrocket.simulation.extension;
import java.util.HashMap;
import java.util.List;
import net.sf.openrocket.util.ArrayList;
/**
* A map containing simulation extension configuration. This map can
* store values of type int, long, float, double, boolean, String,
* List and SimulationExtensionConfiguration.
*/
public final class SimulationExtensionConfiguration extends HashMap<String, Object> {
private SimulationExtension extension;
public SimulationExtension getExtension() {
return extension;
}
public void setExtension(SimulationExtension extension) {
this.extension = extension;
}
@Override
public Object put(String key, Object value) {
Class<?> c = value.getClass();
if (c != Long.class && c != Integer.class &&
c != Double.class && c != Float.class &&
c != Boolean.class &&
!(value instanceof SimulationExtensionConfiguration) &&
!(value instanceof List)) {
throw new UnsupportedOperationException("Invalid configuration parameter type: " + c + " key=" + key + " value=" + value);
}
return super.put(key, value);
}
public long getLong(String key, long def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).longValue();
} else {
return def;
}
}
public int getInt(String key, int def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).intValue();
} else {
return def;
}
}
public double getDouble(String key, double def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).doubleValue();
} else {
return def;
}
}
public float getFloat(String key, float def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).floatValue();
} else {
return def;
}
}
public boolean getBoolean(String key, boolean def) {
Object o = get(key);
if (o instanceof Boolean) {
return (Boolean) o;
} else {
return def;
}
}
public String getString(String key, String def) {
Object o = get(key);
if (o instanceof String) {
return (String) o;
} else {
return def;
}
}
/**
* Deep-clone this object.
*/
@Override
public SimulationExtensionConfiguration clone() {
SimulationExtensionConfiguration copy = new SimulationExtensionConfiguration();
copy.extension = this.extension;
for (String key : this.keySet()) {
Object value = this.get(key);
if (value instanceof SimulationExtensionConfiguration) {
copy.put(key, ((SimulationExtensionConfiguration) value).clone());
} else if (value instanceof List) {
copy.put(key, cloneList((List<?>) value));
} else {
copy.put(key, value);
}
}
return copy;
}
private Object cloneList(List<?> original) {
ArrayList<Object> list = new ArrayList<Object>();
for (Object value : original) {
if (value instanceof SimulationExtensionConfiguration) {
list.add(((SimulationExtensionConfiguration) value).clone());
} else if (value instanceof List) {
list.add(cloneList((List<?>) value));
} else {
list.add(value);
}
}
return list;
}
}

View File

@ -0,0 +1,46 @@
package net.sf.openrocket.simulation.extension;
import java.util.List;
import net.sf.openrocket.plugin.Plugin;
@Plugin
public interface SimulationExtensionProvider {
/**
* Return a list of simulation extension ID's that this provider supports.
* The ID is used to identify the plugin when storing files. It should follow
* the conventions of Java package and class naming.
*
* @return a list of ID strings
*/
public List<String> getIds();
/**
* Return the UI name for a simulation extension. The first values
* are nested menus, with the last one the actual entry, for example
* ["Launch conditions", "Air-start"].
*
* If the ID does not represent an extension that should be displayed
* in the UI, this method must return null. For example, if an extension
* has multiple ID's, this method must return the menu name for only one
* of the ID's.
*
* These can be localized, and the system may attempt to localize
* English-language names automatically (mainly for the menus).
*
* @param id the extension ID
* @return the UI name for the extension, or null for no display
*/
public List<String> getName(String id);
/**
* Return a new instance of a simulation extension. This is a new instance
* that should have some default configuration.
*
* @param id the extension ID
* @return a new simulation extension instance
*/
public SimulationExtension getInstance(String id);
}

View File

@ -0,0 +1,58 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
public class AirStart extends AbstractSimulationExtension {
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new AirStartListener());
}
@Override
public String getName() {
String name;
if (getLaunchVelocity() > 0.01) {
name = trans.get("SimulationExtension.airstart.name.altvel");
} else {
name = trans.get("SimulationExtension.airstart.name.alt");
}
name = L10N.replace(name, "{alt}", UnitGroup.UNITS_DISTANCE.toStringUnit(getLaunchAltitude()));
name = L10N.replace(name, "{vel}", UnitGroup.UNITS_VELOCITY.toStringUnit(getLaunchVelocity()));
return name;
}
public double getLaunchAltitude() {
return config.getDouble("launchAltitude", 0.0);
}
public void setLaunchAltitude(double launchAltitude) {
config.put("launchAltitude", launchAltitude);
fireChangeEvent();
}
public double getLaunchVelocity() {
return config.getDouble("launchVelocity", 0.0);
}
public void setLaunchVelocity(double launchVelocity) {
config.put("launchVelocity", launchVelocity);
fireChangeEvent();
}
private class AirStartListener extends AbstractSimulationListener {
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude()));
status.setRocketVelocity(status.getRocketOrientationQuaternion().rotate(new Coordinate(0, 0, getLaunchVelocity())));
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class AirStartProvider extends AbstractSimulationExtensionProvider {
public AirStartProvider() {
super(AirStart.class, "Launch conditions", "Air-start");
}
}

View File

@ -0,0 +1,77 @@
package net.sf.openrocket.simulation.extension.impl;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
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 net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.StringUtil;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class JavaCode extends AbstractSimulationExtension {
@Inject
private Injector injector;
public JavaCode() {
config.put("my_string", "foobar");
config.put("my_int", 123);
config.put("my_long", 123456789012345L);
config.put("my_float", 12.345f);
config.put("my_double", 12.345e99);
config.put("my_bigint", new BigInteger("12345678901234567890", 10));
config.put("my_bool", true);
List<Object> list = new ArrayList<Object>();
list.add(true);
list.add(123);
list.add(123.456);
list.add(Arrays.asList(1, 2, 3));
list.add("foo");
config.put("my_list", list);
}
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
String className = getClassName();
try {
if (!StringUtil.isEmpty(className)) {
Class<?> clazz = Class.forName(className);
if (!SimulationListener.class.isAssignableFrom(clazz)) {
throw new SimulationException("Class " + className + " does not implement SimulationListener");
}
SimulationListener listener = (SimulationListener) injector.getInstance(clazz);
conditions.getSimulationListenerList().add(listener);
}
} catch (ClassNotFoundException e) {
throw new SimulationException("Could not find class " + className);
}
}
@Override
public String getName() {
String name = trans.get("SimulationExtension.javacode.name") + ": ";
String className = getClassName();
if (!StringUtil.isEmpty(className)) {
name = name + className;
} else {
name = name + trans.get("SimulationExtension.javacode.name.none");
}
return name;
}
public String getClassName() {
return config.getString("className", "");
}
public void setClassName(String className) {
config.put("className", className);
fireChangeEvent();
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class JavaCodeProvider extends AbstractSimulationExtensionProvider {
public JavaCodeProvider() {
super(JavaCode.class, "User code", "Java code");
}
}

View File

@ -0,0 +1,112 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.script.Invocable;
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
public String getName() {
String name = trans.get("SimulationExtension.scripting.name");
name = L10N.replace(name, "{language}", getLanguage());
return name;
}
@Override
public String getDescription() {
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 {
if (isEnabled()) {
conditions.getSimulationListenerList().add(getListener());
}
}
public String getScript() {
return config.getString("script", "");
}
public void setScript(String script) {
config.put("script", script);
}
public String getLanguage() {
return config.getString("language", DEFAULT_LANGUAGE);
}
public void setLanguage(String language) {
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();
ScriptEngine engine = manager.getEngineByName(getLanguage());
if (engine == null) {
throw new SimulationException("Your JRE does not support the scripting language '" + getLanguage() + "'");
}
try {
engine.eval(getScript());
} catch (ScriptException e) {
throw new SimulationException("Invalid script: " + e.getMessage());
}
if (!(engine instanceof Invocable)) {
throw new SimulationException("The scripting language '" + getLanguage() + "' does not implement the Invocable interface");
}
return new ScriptingSimulationListener((Invocable) engine);
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class ScriptingProvider extends AbstractSimulationExtensionProvider {
public ScriptingProvider() {
super(ScriptingExtension.class, "User code", "Scripts");
}
}

View File

@ -0,0 +1,227 @@
package net.sf.openrocket.simulation.extension.impl;
import java.util.HashSet;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptException;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorId;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
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;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ScriptingSimulationListener implements SimulationListener, SimulationComputationListener, SimulationEventListener, Cloneable {
private final static Logger logger = LoggerFactory.getLogger(ScriptingSimulationListener.class);
/*
* NOTE: This class is used instead of using the scripting interface API
* so that unimplemented script methods are not called unnecessarily.
*/
private Invocable invocable;
private Set<String> missing = new HashSet<String>();
public ScriptingSimulationListener(Invocable invocable) {
this.invocable = invocable;
}
@Override
public boolean isSystemListener() {
return false;
}
@Override
public SimulationListener clone() {
try {
ScriptingSimulationListener clone = (ScriptingSimulationListener) super.clone();
clone.missing = new HashSet<String>(missing);
return clone;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
}
//// SimulationListener ////
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
invoke(Void.class, null, "startSimulation", status);
}
@Override
public void endSimulation(SimulationStatus status, SimulationException exception) {
try {
invoke(Void.class, null, "endSimulation", status, exception);
} catch (SimulationException e) {
}
}
@Override
public boolean preStep(SimulationStatus status) throws SimulationException {
return invoke(Boolean.class, true, "preStep", status);
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
invoke(Void.class, null, "postStep", status);
}
//// SimulationEventListener ////
@Override
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(Boolean.class, true, "addFlightEvent", status, event);
}
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(Boolean.class, true, "handleFlightEvent", status, event);
}
@Override
public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException {
return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance);
}
@Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
return invoke(Boolean.class, true, "recoveryDeviceDeployment", status, recoveryDevice);
}
//// SimulationComputationListener ////
@Override
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException {
return invoke(AccelerationData.class, null, "preAccelerationCalculation", status);
}
@Override
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException {
return invoke(AerodynamicForces.class, null, "preAerodynamicCalculation", status);
}
@Override
public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException {
return invoke(AtmosphericConditions.class, null, "preAtmosphericModel", status);
}
@Override
public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException {
return invoke(FlightConditions.class, null, "preFlightConditions", status);
}
@Override
public double preGravityModel(SimulationStatus status) throws SimulationException {
return invoke(Double.class, Double.NaN, "preGravityModel", status);
}
@Override
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
return invoke(MassData.class, null, "preMassCalculation", status);
}
@Override
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException {
return invoke(Double.class, Double.NaN, "preSimpleThrustCalculation", status);
}
@Override
public Coordinate preWindModel(SimulationStatus status) throws SimulationException {
return invoke(Coordinate.class, null, "preWindModel", status);
}
@Override
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException {
return invoke(AccelerationData.class, null, "postAccelerationCalculation", status, acceleration);
}
@Override
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException {
return invoke(AerodynamicForces.class, null, "postAerodynamicCalculation", status, forces);
}
@Override
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException {
return invoke(AtmosphericConditions.class, null, "postAtmosphericModel", status, atmosphericConditions);
}
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
return invoke(FlightConditions.class, null, "postFlightConditions", status, flightConditions);
}
@Override
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException {
return invoke(Double.class, Double.NaN, "postGravityModel", status, gravity);
}
@Override
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
return invoke(MassData.class, null, "postMassCalculation", status, massData);
}
@Override
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
return invoke(Double.class, Double.NaN, "postSimpleThrustCalculation", status, thrust);
}
@Override
public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
return invoke(Coordinate.class, null, "postWindModel", status, wind);
}
@SuppressWarnings("unchecked")
private <T> T invoke(Class<T> retType, T def, String method, Object... args) throws SimulationException {
try {
if (!missing.contains(method)) {
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);
// fall-through
} catch (ScriptException e) {
logger.warn("Script exception in " + method + ": " + e, e);
throw new SimulationException("Script failed: " + e.getMessage());
}
return def;
}
}

View File

@ -0,0 +1,158 @@
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();
private static final List<String> DEFAULT_TRUSTED_HASHES = Arrays.asList(
// Roll control script in roll control example file:
"SHA-256:9bf364ce4d4a75f09b29178bf9d6872b232084f73dae20dc7b5b073e54e95a42"
);
/** The name to be chosen from a list of alternatives. If not found, will use the default name. */
private static final List<String> 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<String> getLanguages() {
List<String> langs = new ArrayList<String>();
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);
if (DEFAULT_TRUSTED_HASHES.contains(hash)) {
return true;
}
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;
}
}

View File

@ -8,11 +8,11 @@ import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightDataType;
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.util.BugException;
import net.sf.openrocket.util.Coordinate;
@ -24,20 +24,10 @@ import net.sf.openrocket.util.Coordinate;
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class AbstractSimulationListener implements SimulationListener, SimulationComputationListener,
SimulationEventListener {
SimulationEventListener, Cloneable {
//// SimulationListener ////
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public String[] getMenuPosition() {
return new String[0];
}
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
// No-op
@ -68,14 +58,6 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
return false;
}
/**
* Return an array of any flight data types this listener creates.
*/
@Override
public FlightDataType[] getFlightDataTypes() {
return new FlightDataType[] {};
}
//// SimulationEventListener ////
@ -184,8 +166,12 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
}
@Override
public AbstractSimulationListener clone() throws CloneNotSupportedException {
return (AbstractSimulationListener) super.clone();
public AbstractSimulationListener clone() {
try {
return (AbstractSimulationListener) super.clone();
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
}
}

View File

@ -4,7 +4,6 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
@ -17,20 +16,20 @@ import net.sf.openrocket.util.Coordinate;
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface SimulationComputationListener extends SimulationListener {
//////// Computation/modeling related callbacks ////////
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException;
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration)
throws SimulationException;
throws SimulationException;
public AtmosphericConditions preAtmosphericModel(SimulationStatus status)
throws SimulationException;
throws SimulationException;
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions)
throws SimulationException;
throws SimulationException;
public Coordinate preWindModel(SimulationStatus status) throws SimulationException;
@ -42,29 +41,27 @@ public interface SimulationComputationListener extends SimulationListener {
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException;
public FlightConditions preFlightConditions(SimulationStatus status)
throws SimulationException;
throws SimulationException;
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions)
throws SimulationException;
throws SimulationException;
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status)
throws SimulationException;
throws SimulationException;
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces)
throws SimulationException;
throws SimulationException;
public MassData preMassCalculation(SimulationStatus status) throws SimulationException;
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException;
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException;
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException;
@Override
public FlightDataType[] getFlightDataTypes();
}

View File

@ -4,14 +4,13 @@ import net.sf.openrocket.motor.MotorId;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
public interface SimulationEventListener {
/**
* Called before adding a flight event to the event queue.
*
@ -23,7 +22,7 @@ public interface SimulationEventListener {
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException;
/**
* Called before handling a flight event.
*
@ -57,10 +56,6 @@ public interface SimulationEventListener {
*/
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice)
throws SimulationException;
public FlightDataType[] getFlightDataTypes();
}

View File

@ -1,6 +1,5 @@
package net.sf.openrocket.simulation.listeners;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
@ -10,26 +9,7 @@ import net.sf.openrocket.simulation.exception.SimulationException;
* If the implementation maintains any state, it should be properly cloned.
*
*/
public interface SimulationListener extends Cloneable {
/**
* Get the name of this simulation listener. Ideally this should be localized, as
* it can be displayed in the UI.
*
* @return the name of this simulation listener.
*/
public String getName();
/**
* Get the menu position of this simulation listener. This should be an array
* of localized submenu names in descending order, or an empty array for positioning
* in the base menu.
*
* @return the menu position of this simulation listener.
*/
public String[] getMenuPosition();
public interface SimulationListener {
/**
* Called when starting a simulation.
@ -83,9 +63,7 @@ public interface SimulationListener extends Cloneable {
/**
* Return a list of any flight data types this listener creates.
* Return a deep copy of this simulation listener including its state.
*/
public FlightDataType[] getFlightDataTypes();
public SimulationListener clone() throws CloneNotSupportedException;
public SimulationListener clone();
}

View File

@ -20,20 +20,7 @@ import net.sf.openrocket.util.Coordinate;
public class DampingMoment extends AbstractSimulationListener {
private static final FlightDataType type = FlightDataType.getType("Damping moment coefficient", "Cdm", UnitGroup.UNITS_COEFFICIENT);
private static final FlightDataType[] typeList = {type};
@Override
public String getName(){
return "Damping moment listener";
}
/**
* Return a list of any flight data types this listener creates.
*/
@Override
public FlightDataType[] getFlightDataTypes(){
return typeList;
}
private static final FlightDataType[] typeList = { type };
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
@ -42,11 +29,11 @@ public class DampingMoment extends AbstractSimulationListener {
//status.getFlightData().setValue(type, aerodynamicPart + propulsivePart);
status.getFlightData().setValue(type, calculate(status, flightConditions));
return flightConditions;
}
private double calculate(SimulationStatus status, FlightConditions flightConditions){
private double calculate(SimulationStatus status, FlightConditions flightConditions) {
// Work out the propulsive/jet damping part of the moment.
@ -55,15 +42,15 @@ public class DampingMoment extends AbstractSimulationListener {
List<Double> mpAll = data.get(FlightDataType.TYPE_PROPELLANT_MASS);
List<Double> time = data.get(FlightDataType.TYPE_TIME);
if (mpAll == null || time == null){
if (mpAll == null || time == null) {
return Double.NaN;
}
int len = mpAll.size();
// This isn't as accurate as I would like
double mdot=Double.NaN;
if (len > 2){
double mdot = Double.NaN;
if (len > 2) {
// Using polynomial interpolator for derivative. Doesn't help much
//double[] x = { time.get(len-5), time.get(len-4), time.get(len-3), time.get(len-2), time.get(len-1) };
//double[] y = { mpAll.get(len-5), mpAll.get(len-4), mpAll.get(len-3), mpAll.get(len-2), mpAll.get(len-1) };
@ -71,22 +58,22 @@ public class DampingMoment extends AbstractSimulationListener {
//double[] coeff = interp.interpolator(y);
//double dt = .01;
//mdot = (interp.eval(x[4], coeff) - interp.eval(x[4]-dt, coeff))/dt;
mdot = (mpAll.get(len-1) - mpAll.get(len-2)) / (time.get(len-1) - time.get(len-2));
mdot = (mpAll.get(len - 1) - mpAll.get(len - 2)) / (time.get(len - 1) - time.get(len - 2));
}
double cg = data.getLast(FlightDataType.TYPE_CG_LOCATION);
// find the maximum distance from nose to nozzle.
double nozzleDistance = 0;
for (MotorId id: status.getMotorConfiguration().getMotorIDs()){
for (MotorId id : status.getMotorConfiguration().getMotorIDs()) {
MotorInstanceConfiguration config = status.getMotorConfiguration();
Coordinate position = config.getMotorPosition(id);
double x = position.x + config.getMotorInstance(id).getParentMotor().getLength();
if (x > nozzleDistance){
if (x > nozzleDistance) {
nozzleDistance = x;
}
}
}
// now can get the propulsive part
@ -99,11 +86,12 @@ public class DampingMoment extends AbstractSimulationListener {
// Must go through each component ...
Map<RocketComponent, AerodynamicForces> forces = aerocalc.getForceAnalysis(status.getConfiguration(), flightConditions, null);
for (Map.Entry<RocketComponent, AerodynamicForces> entry : forces.entrySet()){
for (Map.Entry<RocketComponent, AerodynamicForces> entry : forces.entrySet()) {
RocketComponent comp = entry.getKey();
if (!comp.isAerodynamic()) continue;
if (!comp.isAerodynamic())
continue;
//System.out.println(comp.toString());
@ -111,7 +99,7 @@ public class DampingMoment extends AbstractSimulationListener {
double Cp = entry.getValue().getCP().length();
double z = comp.getPositionValue(); //?
aerodynamicPart += CNa*Math.pow(z-Cp, 2);
aerodynamicPart += CNa * Math.pow(z - Cp, 2);
}
double v = flightConditions.getVelocity();
@ -121,7 +109,7 @@ public class DampingMoment extends AbstractSimulationListener {
aerodynamicPart = aerodynamicPart * .5 * rho * v * ar;
return aerodynamicPart + propulsivePart;
}
}

View File

@ -4,6 +4,7 @@ import net.sf.openrocket.database.ComponentPresetDao;
import net.sf.openrocket.database.motor.MotorDatabase;
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.l10n.ClassBasedTranslator;
import net.sf.openrocket.l10n.DebugTranslator;
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
import net.sf.openrocket.l10n.Translator;
@ -33,6 +34,10 @@ public final class Application {
}
private static Translator getBaseTranslator() {
if (injector == null) {
// Occurs in some unit tests
return new DebugTranslator(null);
}
return injector.getInstance(Translator.class);
}

View File

@ -124,6 +124,8 @@ public abstract class Preferences implements ChangeSource {
public abstract void putString(String directory, String key, String value);
public abstract java.util.prefs.Preferences getNode(String nodeName);
/*
* ******************************************************************************************
*/

View File

@ -278,6 +278,7 @@ public class UnitGroup {
UNITS_ROLL = new UnitGroup();
UNITS_ROLL.addUnit(new GeneralUnit(1, "rad/s"));
UNITS_ROLL.addUnit(new GeneralUnit(Math.PI / 180, DEGREE + "/s"));
UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI, "r/s"));
UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI / 60, "rpm"));
UNITS_ROLL.setDefaultUnit(1);

View File

@ -0,0 +1,152 @@
package net.sf.openrocket.util;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
public class Config {
private LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
public void put(String key, String value) {
validateType(value);
map.put(key, value);
}
public void put(String key, Number value) {
validateType(value);
map.put(key, clone(value));
}
public void put(String key, Boolean value) {
validateType(value);
map.put(key, value);
}
public void put(String key, List<?> value) {
validateType(value);
map.put(key, clone(value));
}
public void put(String key, Object value) {
validateType(value);
map.put(key, clone(value));
}
public Object get(String key, Object def) {
return get(key, def, Object.class);
}
public Boolean getBoolean(String key, Boolean def) {
return get(key, def, Boolean.class);
}
public Integer getInt(String key, Integer def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.intValue();
}
}
public Long getLong(String key, Long def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.longValue();
}
}
public Double getDouble(String key, Double def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.doubleValue();
}
}
public String getString(String key, String def) {
return get(key, def, String.class);
}
public List<?> getList(String key, List<?> def) {
return get(key, def, List.class);
}
public boolean containsKey(String key) {
return map.containsKey(key);
}
public Set<String> keySet() {
return Collections.unmodifiableMap(map).keySet();
}
@Override
public Config clone() {
Config copy = new Config();
for (Entry<String, Object> entry : map.entrySet()) {
copy.map.put(entry.getKey(), clone(entry.getValue()));
}
return copy;
}
@SuppressWarnings("unchecked")
private <T> T get(String key, T def, Class<T> type) {
Object value = map.get(key);
if (type.isInstance(value)) {
return (T) value;
} else {
return def;
}
}
private void validateType(Object value) {
if (value == null) {
throw new NullPointerException("Attempting to add null value to Config object");
} else if (value instanceof Boolean) {
// ok
} else if (value instanceof Number) {
// ok
} else if (value instanceof String) {
// ok
} else if (value instanceof List<?>) {
List<?> list = (List<?>) value;
for (Object v : list) {
validateType(v);
}
} else {
throw new IllegalArgumentException("Attempting to add value of type " + value.getClass() + " to Config object, value=" + value);
}
}
private Object clone(Object value) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long ||
value instanceof Float || value instanceof Double || value instanceof Boolean || value instanceof String) {
// immutable
return value;
} else if (value instanceof Number) {
return new BigDecimal(value.toString());
} else if (value instanceof List<?>) {
List<?> list = (List<?>) value;
ArrayList<Object> copy = new ArrayList<Object>(list.size());
for (Object o : list) {
copy.add(clone(o));
}
return copy;
} else {
throw new IllegalStateException("Config contained value = " + value + " type = " + ((value != null) ? value.getClass() : "null"));
}
}
}

View File

@ -48,6 +48,7 @@ import net.sf.openrocket.rocketcomponent.TubeCoupler;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.impl.ScriptingExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.startup.Application;
@ -241,7 +242,7 @@ public class TestRockets {
}
public Rocket makeSmallFlyable() {
public static Rocket makeSmallFlyable() {
double noseconeLength = 0.10, noseconeRadius = 0.01;
double bodytubeLength = 0.20, bodytubeRadius = 0.01, bodytubeThickness = 0.001;
@ -281,8 +282,12 @@ public class TestRockets {
String id = rocket.newFlightConfigurationID();
bodytube.setMotorMount(true);
Motor m = Application.getMotorSetDatabase().findMotors(null, null, "B4", Double.NaN, Double.NaN).get(0);
bodytube.getMotorConfiguration().get(id).setMotor(m);
MotorConfiguration motorConfig = new MotorConfiguration();
ThrustCurveMotor motor = getTestMotor();
motorConfig.setMotor(motor);
motorConfig.setEjectionDelay(5);
bodytube.getMotorConfiguration().set(id, motorConfig);
bodytube.setMotorOverhang(0.005);
rocket.getDefaultConfiguration().setFlightConfigurationID(id);
@ -643,11 +648,7 @@ public class TestRockets {
// create motor config and add a motor to it
MotorConfiguration motorConfig = new MotorConfiguration();
ThrustCurveMotor motor = new ThrustCurveMotor(
Manufacturer.getManufacturer("A"),
"F12X", "Desc", Motor.Type.UNKNOWN, new double[] {},
0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 },
new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestA");
ThrustCurveMotor motor = getTestMotor();
motorConfig.setMotor(motor);
motorConfig.setEjectionDelay(5);
@ -685,11 +686,7 @@ public class TestRockets {
// create motor config and add a motor to it
MotorConfiguration motorConfig = new MotorConfiguration();
ThrustCurveMotor motor = new ThrustCurveMotor(
Manufacturer.getManufacturer("A"),
"F12X", "Desc", Motor.Type.UNKNOWN, new double[] {},
0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 },
new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestA");
ThrustCurveMotor motor = getTestMotor();
motorConfig.setMotor(motor);
motorConfig.setEjectionDelay(5);
@ -918,6 +915,20 @@ public class TestRockets {
return OpenRocketDocumentFactory.createDocumentFromRocket(rocket);
}
public static OpenRocketDocument makeTestRocket_v107_withSimulationExtension(String script) {
Rocket rocket = makeSmallFlyable();
OpenRocketDocument document = OpenRocketDocumentFactory.createDocumentFromRocket(rocket);
Simulation sim = new Simulation(rocket);
ScriptingExtension ext = new ScriptingExtension();
ext.setEnabled(true);
ext.setLanguage("JavaScript");
ext.setScript(script);
sim.getSimulationExtensions().add(ext);
document.addSimulation(sim);
return document;
}
/*
* Create a new test rocket for testing OpenRocketSaver.estimateFileSize()
*/
@ -991,4 +1002,15 @@ public class TestRockets {
}
private static ThrustCurveMotor getTestMotor() {
return new ThrustCurveMotor(
Manufacturer.getManufacturer("A"),
"F12X", "Desc", Motor.Type.UNKNOWN, new double[] {},
0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 },
new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestA");
}
}

View File

@ -4,6 +4,7 @@ import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.prefs.BackingStoreException;
import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.formatting.RocketDescriptorImpl;
@ -62,6 +63,8 @@ public class ServicesForTesting extends AbstractModule {
public static class PreferencesForTesting extends Preferences {
private static java.util.prefs.Preferences root = null;
@Override
public boolean getBoolean(String key, boolean defaultValue) {
// TODO Auto-generated method stub
@ -151,5 +154,28 @@ public class ServicesForTesting extends AbstractModule {
return null;
}
@Override
public java.util.prefs.Preferences getNode(String nodeName) {
return getBaseNode().node(nodeName);
}
private java.util.prefs.Preferences getBaseNode() {
if (root == null) {
final String name = "OpenRocket-unittest-" + System.currentTimeMillis();
root = java.util.prefs.Preferences.userRoot().node(name);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
root.removeNode();
} catch (BackingStoreException e) {
e.printStackTrace();
}
}
});
}
return root;
}
}
}

View File

@ -28,6 +28,8 @@ import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.plugin.PluginModule;
import net.sf.openrocket.simulation.extension.impl.ScriptingExtension;
import net.sf.openrocket.simulation.extension.impl.ScriptingUtil;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.TestRockets;
@ -47,6 +49,10 @@ public class OpenRocketSaverTest {
private OpenRocketSaver saver = new OpenRocketSaver();
private static final String TMP_DIR = "./tmp/";
public static final String SIMULATION_EXTENSION_SCRIPT = "// Test < &\n// >\n// <![CDATA[";
private static Injector injector;
@BeforeClass
public static void setup() {
Module applicationModule = new ServicesForTesting();
@ -61,7 +67,7 @@ public class OpenRocketSaverTest {
}
};
Injector injector = Guice.createInjector(Modules.override(applicationModule).with(dbOverrides), pluginModule);
injector = Guice.createInjector(Modules.override(applicationModule).with(dbOverrides), pluginModule);
Application.setInjector(injector);
File tmpDir = new File("./tmp");
@ -122,6 +128,7 @@ public class OpenRocketSaverTest {
rocketDocs.add(TestRockets.makeTestRocket_v106_withMotorMountIgnitionConfig());
rocketDocs.add(TestRockets.makeTestRocket_v106_withRecoveryDeviceDeploymentConfig());
rocketDocs.add(TestRockets.makeTestRocket_v106_withStageSeparationConfig());
rocketDocs.add(TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT));
rocketDocs.add(TestRockets.makeTestRocket_for_estimateFileSize());
StorageOptions options = new StorageOptions();
@ -135,6 +142,35 @@ public class OpenRocketSaverTest {
}
}
@Test
public void testUntrustedScriptDisabledOnLoad() {
OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT);
StorageOptions options = new StorageOptions();
File file = saveRocket(rocketDoc, options);
OpenRocketDocument rocketDocLoaded = loadRocket(file.getPath());
assertEquals(1, rocketDocLoaded.getSimulations().size());
assertEquals(1, rocketDocLoaded.getSimulations().get(0).getSimulationExtensions().size());
ScriptingExtension ext = (ScriptingExtension) rocketDocLoaded.getSimulations().get(0).getSimulationExtensions().get(0);
assertEquals(false, ext.isEnabled());
assertEquals(SIMULATION_EXTENSION_SCRIPT, ext.getScript());
}
@Test
public void testTrustedScriptEnabledOnLoad() {
OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v107_withSimulationExtension("TESTING");
injector.getInstance(ScriptingUtil.class).setTrustedScript("JavaScript", "TESTING", true);
StorageOptions options = new StorageOptions();
File file = saveRocket(rocketDoc, options);
OpenRocketDocument rocketDocLoaded = loadRocket(file.getPath());
assertEquals(1, rocketDocLoaded.getSimulations().size());
assertEquals(1, rocketDocLoaded.getSimulations().get(0).getSimulationExtensions().size());
ScriptingExtension ext = (ScriptingExtension) rocketDocLoaded.getSimulations().get(0).getSimulationExtensions().get(0);
assertEquals(true, ext.isEnabled());
assertEquals("TESTING", ext.getScript());
}
/*
* Test how accurate estimatedFileSize is.
*
@ -258,6 +294,17 @@ public class OpenRocketSaverTest {
assertEquals(106, getCalculatedFileVersion(rocketDoc));
}
////////////////////////////////
// Tests for File Version 1.7 //
////////////////////////////////
@Test
public void testFileVersion107_withSimulationExtension() {
OpenRocketDocument rocketDoc = TestRockets.makeTestRocket_v107_withSimulationExtension(SIMULATION_EXTENSION_SCRIPT);
assertEquals(107, getCalculatedFileVersion(rocketDoc));
}
/*
* Utility Functions
*/
@ -273,6 +320,7 @@ public class OpenRocketSaverTest {
try {
rocketDoc = loader.load();
} catch (RocketLoadException e) {
e.printStackTrace();
fail("RocketLoadException while loading file " + fileName + " : " + e.getMessage());
}
return rocketDoc;

View File

@ -25,7 +25,7 @@ public class DocumentConfigTest extends BaseTestCase {
public void testAllVersionsTested() {
// Update this after creating new unit tests in OpenRocketSaver for a new OR file version
String[] testedVersionsStr = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6" };
String[] testedVersionsStr = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7" };
List<String> supportedVersions = Arrays.asList(DocumentConfig.SUPPORTED_VERSIONS);
List<String> testedVersions = Arrays.asList(testedVersionsStr);

View File

@ -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"));
}
}

View File

@ -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<Material> 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<String> getComponentFavorites(Type type) {
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -0,0 +1,164 @@
package net.sf.openrocket.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
public class TestConfig {
private Config config = new Config();
@Test
public void testDoubles() {
config.put("double", Math.PI);
config.put("bigdecimal", new BigDecimal(Math.PI));
assertEquals(Math.PI, config.getDouble("double", null), 0);
assertEquals(Math.PI, config.getDouble("bigdecimal", null), 0);
assertEquals(3, (int) config.getInt("double", null));
}
@Test
public void testInts() {
config.put("int", 123);
config.put("biginteger", new BigDecimal(Math.PI));
config.put("bigdecimal", new BigDecimal(Math.PI));
assertEquals(123, (int) config.getInt("int", null));
assertEquals(3, (int) config.getInt("bigdecimal", null));
assertEquals(3, (int) config.getInt("biginteger", null));
}
@Test
public void testDefaultValue() {
assertEquals(true, config.getBoolean("foo", true));
assertEquals(123, (int) config.getInt("foo", 123));
assertEquals(123L, (long) config.getLong("foo", 123L));
assertEquals(1.23, (double) config.getDouble("foo", 1.23), 0);
assertEquals("bar", config.getString("foo", "bar"));
assertEquals(Arrays.asList("foo"), config.getList("foo", Arrays.asList("foo")));
}
@Test
public void testNullDefaultValue() {
assertEquals(null, config.getBoolean("foo", null));
assertEquals(null, config.getInt("foo", null));
assertEquals(null, config.getLong("foo", null));
assertEquals(null, config.getDouble("foo", null));
assertEquals(null, config.getString("foo", null));
assertEquals(null, config.getList("foo", null));
}
@Test
public void testStoringList() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(123);
list.add(Math.PI);
list.add(true);
config.put("list", list);
assertEquals(Arrays.asList("Foo", 123, Math.PI, true), config.getList("list", null));
}
@Test
public void testModifyingStoredList() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(123);
list.add(Math.PI);
list.add(true);
config.put("list", list);
list.add("hello");
assertEquals(Arrays.asList("Foo", 123, Math.PI, true), config.getList("list", null));
}
@Test
public void testModifyingStoredNumber() {
AtomicInteger ai = new AtomicInteger(100);
config.put("ai", ai);
ai.incrementAndGet();
assertEquals(100, (int) config.getInt("ai", null));
}
@Test
public void testClone() {
config.put("string", "foo");
config.put("int", 123);
config.put("double", Math.PI);
AtomicInteger ai = new AtomicInteger(100);
config.put("atomicinteger", ai);
List<Object> list = new ArrayList<Object>();
list.add("Foo");
config.put("list", list);
Config copy = config.clone();
config.put("extra", "foo");
ai.incrementAndGet();
assertFalse(copy.containsKey("extra"));
assertEquals("foo", copy.getString("string", null));
assertEquals(123, (int) copy.getInt("int", null));
assertEquals(100, (int) copy.getInt("atomicinteger", null));
assertEquals(Math.PI, (double) copy.getDouble("double", null), 0);
assertEquals(Arrays.asList("Foo"), copy.getList("list", null));
}
@Test
public void testStoringNullValue() {
try {
config.put("foo", (Boolean) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (String) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (Number) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (List<?>) null);
fail();
} catch (NullPointerException e) {
}
}
@Test
public void testStoringListWithInvalidTypes() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(new Date());
try {
config.put("foo", list);
fail();
} catch (IllegalArgumentException e) {
}
}
@Test
public void testStoringListWithNull() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(new Date());
try {
config.put("foo", list);
fail();
} catch (IllegalArgumentException e) {
}
}
}

View File

@ -1,25 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/jogl/jogl-all.jar"/>
<classpathentry kind="lib" path="lib/iText-5.0.2.jar"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.18.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.0.15.jar"/>
<classpathentry kind="lib" path="lib/OrangeExtensions-1.2.jar"/>
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/OpenRocket Core"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/slf4j-api-1.7.5.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/aopalliance.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/guice-3.0.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/guice-multibindings-3.0.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.inject.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/opencsv-2.3.jar"/>
<classpathentry kind="lib" path="lib/logback-classic-1.0.12.jar"/>
<classpathentry kind="lib" path="lib/logback-core-1.0.12.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/resources"/>
<classpathentry kind="lib" path="resources"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/annotation-detector-3.0.2.jar"/>
<classpathentry kind="lib" path="lib/miglayout-4.0-swing.jar" sourcepath="reference/miglayout-4.0-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/jogl/jogl-all.jar"/>
<classpathentry kind="lib" path="lib/iText-5.0.2.jar"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.18.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.0.15.jar"/>
<classpathentry kind="lib" path="lib/OrangeExtensions-1.2.jar"/>
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/OpenRocket Core"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/slf4j-api-1.7.5.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/aopalliance.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/guice-3.0.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/guice-multibindings-3.0.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.inject.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/opencsv-2.3.jar"/>
<classpathentry kind="lib" path="lib/logback-classic-1.0.12.jar"/>
<classpathentry kind="lib" path="lib/logback-core-1.0.12.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/resources"/>
<classpathentry kind="lib" path="resources"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/annotation-detector-3.0.2.jar"/>
<classpathentry kind="lib" path="lib/miglayout-4.0-swing.jar" sourcepath="reference/miglayout-4.0-sources.jar"/>
<classpathentry kind="lib" path="lib/rsyntaxtextarea-2.5.6.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -105,6 +105,7 @@
<zipfileset src="${core.dir}/lib/slf4j-api-1.7.5.jar"/>
<zipfileset src="${lib.dir}/logback-classic-1.0.12.jar"/>
<zipfileset src="${lib.dir}/logback-core-1.0.12.jar"/>
<zipfileset src="${lib.dir}/rsyntaxtextarea-2.5.6.jar"/>
<!-- JOGL libraries need to be jar-in-jar -->

Binary file not shown.

View File

@ -26,6 +26,7 @@ import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.startup.Application;
@ -91,8 +92,10 @@ public class SimulationEditDialog extends JDialog {
if (simulation.length > 1) {
for (int i = 1; i < simulation.length; i++) {
simulation[i].getOptions().copyConditionsFrom(simulation[0].getOptions());
simulation[i].getSimulationListeners().clear();
simulation[i].getSimulationListeners().addAll(simulation[0].getSimulationListeners());
simulation[i].getSimulationExtensions().clear();
for (SimulationExtension c : simulation[0].getSimulationExtensions()) {
simulation[i].getSimulationExtensions().add(c.clone());
}
}
}
}
@ -167,7 +170,7 @@ public class SimulationEditDialog extends JDialog {
//// Launch conditions
tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), new SimulationConditionsPanel(simulation[0]));
//// Simulation options
tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), new SimulationOptionsPanel(simulation[0]));
tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), new SimulationOptionsPanel(document, simulation[0]));
tabbedPane.setSelectedIndex(0);

View File

@ -1,54 +1,70 @@
package net.sf.openrocket.gui.simulation;
import java.awt.Component;
import java.awt.Color;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.ListCellRenderer;
import javax.swing.MenuElement;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.extension.SimulationExtensionProvider;
import net.sf.openrocket.simulation.extension.SwingSimulationExtensionConfigurator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GeodeticComputationStrategy;
import com.google.inject.Key;
class SimulationOptionsPanel extends JPanel {
private static final Translator trans = Application.getTranslator();
private OpenRocketDocument document;
final Simulation simulation;
SimulationOptionsPanel(final Simulation simulation) {
private JPanel currentExtensions;
SimulationOptionsPanel(OpenRocketDocument document, final Simulation simulation) {
super(new MigLayout("fill"));
this.document = document;
this.simulation = simulation;
final SimulationOptions conditions = simulation.getOptions();
JPanel sub, subsub;
String tip;
JLabel label;
@ -56,7 +72,7 @@ class SimulationOptionsPanel extends JPanel {
JSpinner spin;
UnitSelector unit;
BasicSlider slider;
// // Simulation options
sub = new JPanel(new MigLayout("fill, gap rel unrel",
"[grow][65lp!][30lp!][75lp!]", ""));
@ -64,38 +80,38 @@ class SimulationOptionsPanel extends JPanel {
sub.setBorder(BorderFactory.createTitledBorder(trans
.get("simedtdlg.border.Simopt")));
this.add(sub, "growx, growy, aligny 0");
// Separate panel for computation methods, as they use a different
// layout
subsub = new JPanel(new MigLayout("insets 0, fill", "[grow][min!][min!][]"));
// // Calculation method:
tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
label.setToolTipText(tip);
subsub.add(label, "gapright para");
// // Extended Barrowman
label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
label.setToolTipText(tip);
subsub.add(label, "growx, span 3, wrap");
// Simulation method
tip = trans.get("simedtdlg.lbl.ttip.Simmethod1")
+ trans.get("simedtdlg.lbl.ttip.Simmethod2");
label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
label.setToolTipText(tip);
subsub.add(label, "gapright para");
label = new JLabel("6-DOF Runge-Kutta 4");
label.setToolTipText(tip);
subsub.add(label, "growx, span 3, wrap");
// // Geodetic calculation method:
label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
subsub.add(label, "gapright para");
EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(
conditions, "GeodeticComputation");
final JComboBox gcsCombo = new JComboBox(gcsModel);
@ -110,7 +126,7 @@ class SimulationOptionsPanel extends JPanel {
gcsCombo.addActionListener(gcsTTipListener);
gcsTTipListener.actionPerformed(null);
subsub.add(gcsCombo, "span 3, wrap para");
// // Time step:
label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
@ -121,25 +137,25 @@ class SimulationOptionsPanel extends JPanel {
.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP)
+ ".";
label.setToolTipText(tip);
subsub.add(label,"gapright para");
subsub.add(label, "gapright para");
m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP,
0, 1);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
spin.setToolTipText(tip);
subsub.add(spin, "");
unit = new UnitSelector(m);
unit.setToolTipText(tip);
subsub.add(unit, "");
slider = new BasicSlider(m.getSliderModel(0, 0.2));
slider.setToolTipText(tip);
subsub.add(slider, "w 100");
sub.add(subsub, "spanx, wrap para");
// Reset to default button
JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
// Reset the time step to its default value (
@ -159,141 +175,227 @@ class SimulationOptionsPanel extends JPanel {
GeodeticComputationStrategy.SPHERICAL));
}
});
sub.add(button, "align left");
// Simulation listeners
//// Simulation extensions
sub = new JPanel(new MigLayout("fill, gap 0 0"));
// Simulator listeners
sub.setBorder(BorderFactory.createTitledBorder(trans
.get("simedtdlg.border.Simlist")));
this.add(sub, "growx, growy");
sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.SimExt")));
this.add(sub, "wmin 300lp, growx, growy");
DescriptionArea desc = new DescriptionArea(5);
// <html><i>Simulation listeners</i> is an advanced feature that
// allows user-written code to listen to and interact with the
// simulation.
// // For details on writing simulation listeners, see the OpenRocket
// technical documentation.
desc.setText(trans.get("simedtdlg.txt.longA1")
+ trans.get("simedtdlg.txt.longA2"));
sub.add(desc, "aligny 0, growx, wrap para");
// // Current listeners:
label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
sub.add(label, "spanx, wrap rel");
final ListenerListModel listenerModel = new ListenerListModel();
final JList list = new JList(listenerModel);
list.setCellRenderer(new ListenerCellRenderer());
JScrollPane scroll = new JScrollPane(list);
// scroll.setPreferredSize(new Dimension(1,1));
sub.add(scroll, "height 1px, grow, wrap rel");
// // Add button
button = new JButton(trans.get("simedtdlg.but.add"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String previous = Application.getPreferences().getString(
"previousListenerName", "");
String input = (String) JOptionPane.showInputDialog(
SwingUtilities.getRoot(SimulationOptionsPanel.this),
new Object[] {
// // Type the full Java class name of the
// simulation listener, for example:
"Type the full Java class name of the simulation listener, for example:",
"<html><tt>" + CSVSaveListener.class.getName()
+ "</tt>" },
// // Add simulation listener
trans.get("simedtdlg.lbl.Addsimlist"),
JOptionPane.QUESTION_MESSAGE, null, null, previous);
if (input == null || input.equals(""))
return;
Application.getPreferences().putString("previousListenerName",
input);
simulation.getSimulationListeners().add(input);
listenerModel.fireContentsChanged();
desc.setText(trans.get("simedtdlg.SimExt.desc"));
sub.add(desc, "aligny 0, hmin 100lp, growx, wrap para");
final JButton addExtension = new JButton(trans.get("simedtdlg.SimExt.add"));
final JPopupMenu menu = getExtensionMenu();
addExtension.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
menu.show(addExtension, 5, addExtension.getBounds().height);
}
});
sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
// // Remove button
button = new JButton(trans.get("simedtdlg.but.remove"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int[] selected = list.getSelectedIndices();
Arrays.sort(selected);
for (int i = selected.length - 1; i >= 0; i--) {
simulation.getSimulationListeners().remove(selected[i]);
sub.add(addExtension, "growx, wrap 0");
currentExtensions = new JPanel(new MigLayout("fillx, gap 0 0, ins 0"));
JScrollPane scroll = new JScrollPane(currentExtensions);
// &#$%! scroll pane will not honor "growy"...
sub.add(scroll, "growx, growy, h 100%");
updateCurrentExtensions();
}
private JPopupMenu getExtensionMenu() {
Set<SimulationExtensionProvider> extensions = Application.getInjector().getInstance(new Key<Set<SimulationExtensionProvider>>() {
});
JPopupMenu basemenu = new JPopupMenu();
for (final SimulationExtensionProvider provider : extensions) {
List<String> ids = provider.getIds();
for (final String id : ids) {
List<String> menuItems = provider.getName(id);
if (menuItems != null) {
JComponent menu = findMenu(basemenu, menuItems);
JMenuItem item = new JMenuItem(menuItems.get(menuItems.size() - 1));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
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);
}
listenerModel.fireContentsChanged();
}
}
JMenu copyMenu = null;
for (Simulation sim : document.getSimulations()) {
if (!sim.getSimulationExtensions().isEmpty()) {
JMenu menu = new JMenu(sim.getName());
for (final SimulationExtension ext : sim.getSimulationExtensions()) {
JMenuItem item = new JMenuItem(ext.getName());
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
SimulationExtension e = ext.clone();
simulation.getSimulationExtensions().add(e);
updateCurrentExtensions();
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
if (configurator != null) {
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
}
}
});
menu.add(item);
}
if (copyMenu == null) {
copyMenu = new JMenu(trans.get("simedtdlg.SimExt.copyExtension"));
}
copyMenu.add(menu);
}
}
if (copyMenu != null) {
basemenu.add(copyMenu);
}
return basemenu;
}
private JComponent findMenu(MenuElement menu, List<String> menuItems) {
for (int i = 0; i < menuItems.size() - 1; i++) {
String menuItem = menuItems.get(i);
MenuElement found = null;
for (MenuElement e : menu.getSubElements()) {
if (e instanceof JMenu && ((JMenu) e).getText().equals(menuItem)) {
found = e;
break;
}
}
if (found != null) {
menu = found;
} else {
JMenu m = new JMenu(menuItem);
((JComponent) menu).add(m);
menu = m;
}
}
return (JComponent) menu;
}
private void updateCurrentExtensions() {
currentExtensions.removeAll();
if (simulation.getSimulationExtensions().isEmpty()) {
StyledLabel l = new StyledLabel(trans.get("simedtdlg.SimExt.noExtensions"), Style.ITALIC);
l.setForeground(Color.DARK_GRAY);
currentExtensions.add(l, "growx, pad 5 5 5 5, wrap");
} else {
for (SimulationExtension e : simulation.getSimulationExtensions()) {
currentExtensions.add(new SimulationExtensionPanel(e), "growx, wrap");
}
}
// Both needed:
this.revalidate();
this.repaint();
}
private class SimulationExtensionPanel extends JPanel {
public SimulationExtensionPanel(final SimulationExtension extension) {
super(new MigLayout("fillx, gapx 0"));
this.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
this.add(new JLabel(extension.getName()), "spanx, growx, wrap");
JButton button;
this.add(new JPanel(), "spanx, split, growx, right");
if (findConfigurator(extension) != null) {
button = new JButton(Icons.CONFIGURE);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
findConfigurator(extension).configure(extension, simulation,
SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
updateCurrentExtensions();
}
});
this.add(button, "right");
}
if (extension.getDescription() != null) {
button = new JButton(Icons.HELP);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JDialog dialog = new JDialog(SwingUtilities.windowForComponent(SimulationOptionsPanel.this),
extension.getName(), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
DescriptionArea area = new DescriptionArea(extension.getDescription(), 10, 0);
panel.add(area, "width 400lp, wrap para");
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
panel.add(close, "right");
dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setLocationRelativeTo(SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
dialog.setVisible(true);
}
});
this.add(button, "right");
}
button = new JButton(Icons.DELETE);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Iterator<SimulationExtension> iter = simulation.getSimulationExtensions().iterator();
while (iter.hasNext()) {
// Compare with identity
if (iter.next() == extension) {
iter.remove();
break;
}
}
updateCurrentExtensions();
}
});
this.add(button, "right");
}
}
private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
Set<SwingSimulationExtensionConfigurator> configurators = Application.getInjector().getInstance(new Key<Set<SwingSimulationExtensionConfigurator>>() {
});
sub.add(button, "sizegroup buttons, alignx 50%");
}
private class ListenerCellRenderer extends JLabel implements
ListCellRenderer {
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
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;
for (SwingSimulationExtensionConfigurator c : configurators) {
if (c.support(extension)) {
return c;
}
if (ex == null) {
setIcon(Icons.SIMULATION_LISTENER_OK);
// // Listener instantiated successfully.
setToolTipText("Listener instantiated successfully.");
} else {
setIcon(Icons.SIMULATION_LISTENER_ERROR);
// // <html>Unable to instantiate listener due to exception:<br>
setToolTipText("<html>Unable to instantiate listener due to exception:<br>"
+ ex.toString());
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
return this;
}
return null;
}
private class ListenerListModel extends AbstractListModel {
@Override
public String getElementAt(int index) {
if (index < 0 || index >= getSize())
return null;
return simulation.getSimulationListeners().get(index);
}
@Override
public int getSize() {
return simulation.getSimulationListeners().size();
}
public void fireContentsChanged() {
super.fireContentsChanged(this, 0, getSize());
}
}
}

View File

@ -71,9 +71,6 @@ import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.gui.Resettable;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.startup.Application;
@ -81,6 +78,9 @@ import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Invalidatable;
import net.sf.openrocket.util.MemoryManagement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GUIUtil {
private static final Logger log = LoggerFactory.getLogger(GUIUtil.class);
@ -147,6 +147,7 @@ public class GUIUtil {
installEscapeCloseOperation(dialog);
setWindowIcons(dialog);
addModelNullingListener(dialog);
dialog.setLocationRelativeTo(dialog.getOwner());
dialog.setLocationByPlatform(true);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.pack();

View File

@ -8,13 +8,13 @@ import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Icons {
private static final Logger log = LoggerFactory.getLogger(Icons.class);
@ -78,6 +78,8 @@ public class Icons {
public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
public static final Icon EDIT = loadImageIcon("pix/icons/pencil.png", "Edit");
public static final Icon CONFIGURE = loadImageIcon("pix/icons/configure.png", "Configure");
public static final Icon HELP = loadImageIcon("pix/icons/help-about.png", "Help");
public static final Icon UP = loadImageIcon("pix/icons/up.png", "Up");
public static final Icon DOWN = loadImageIcon("pix/icons/down.png", "Down");

View File

@ -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();

View File

@ -0,0 +1,88 @@
package net.sf.openrocket.simulation.extension;
import java.awt.Dialog.ModalityType;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import com.google.inject.Inject;
public abstract class AbstractSwingSimulationExtensionConfigurator<E extends SimulationExtension> implements SwingSimulationExtensionConfigurator {
@Inject
protected Translator trans;
private final Class<E> extensionClass;
private JDialog dialog;
protected AbstractSwingSimulationExtensionConfigurator(Class<E> extensionClass) {
this.extensionClass = extensionClass;
}
@Override
public boolean support(SimulationExtension extension) {
return extensionClass.isInstance(extension);
}
@SuppressWarnings("unchecked")
@Override
public void configure(SimulationExtension extension, Simulation simulation, Window parent) {
dialog = new JDialog(parent, getTitle(extension, simulation), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
JPanel sub = new JPanel(new MigLayout("fill, ins 0"));
panel.add(getConfigurationComponent((E) extension, simulation, sub), "grow, wrap para");
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
panel.add(close, "right");
dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setVisible(true);
close();
GUIUtil.setNullModels(dialog);
dialog = null;
}
/**
* Return a title for the dialog window. By default uses the extension's name.
*/
protected String getTitle(SimulationExtension extension, Simulation simulation) {
return extension.getName();
}
/**
* Return the dialog currently open.
*/
protected JDialog getDialog() {
return dialog;
}
/**
* Called when the default dialog is closed. By default does nothing.
*/
protected void close() {
}
protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel);
}

View File

@ -0,0 +1,29 @@
package net.sf.openrocket.simulation.extension;
import java.awt.Window;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.plugin.Plugin;
@Plugin
public interface SwingSimulationExtensionConfigurator {
/**
* Test whether this configurator supports configuring an extension.
*
* @param extension the extension to test
* @return true if this configurator can configure the specified extension
*/
public boolean support(SimulationExtension extension);
/**
* Open an application-modal dialog for configuring a simulation extension.
* Close the dialog when ready.
*
* @param extension the extension to configure
* @param simulation the simulation the extension is attached to
* @param parent the parent window for the dialog
*/
public void configure(SimulationExtension extension, Simulation simulation, Window parent);
}

View File

@ -0,0 +1,58 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.unit.UnitGroup;
@Plugin
public class AirStartConfigurator extends AbstractSwingSimulationExtensionConfigurator<AirStart> {
public AirStartConfigurator() {
super(AirStart.class);
}
@Override
protected JComponent getConfigurationComponent(AirStart extension, Simulation simulation, JPanel panel) {
panel.add(new JLabel("Launch altitude:"));
DoubleModel m = new DoubleModel(extension, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
JSpinner spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "w 65lp!");
UnitSelector unit = new UnitSelector(m);
panel.add(unit, "w 25");
BasicSlider slider = new BasicSlider(m.getSliderModel(0, 1000));
panel.add(slider, "w 75lp, wrap");
panel.add(new JLabel("Launch velocity:"));
m = new DoubleModel(extension, "LaunchVelocity", UnitGroup.UNITS_VELOCITY, 0);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "w 65lp!");
unit = new UnitSelector(m);
panel.add(unit, "w 25");
slider = new BasicSlider(m.getSliderModel(0, 150));
panel.add(slider, "w 75lp, wrap");
return panel;
}
}

View File

@ -0,0 +1,47 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
@Plugin
public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfigurator<JavaCode> {
public JavaCodeConfigurator() {
super(JavaCode.class);
}
@Override
protected JComponent getConfigurationComponent(final JavaCode extension, Simulation simulation, JPanel panel) {
panel.add(new JLabel(trans.get("SimulationExtension.javacode.desc")), "wrap para");
panel.add(new JLabel(trans.get("SimulationExtension.javacode.className")), "wrap rel");
final JTextField textField = new JTextField(extension.getClassName());
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
update();
}
public void removeUpdate(DocumentEvent e) {
update();
}
public void insertUpdate(DocumentEvent e) {
update();
}
public void update() {
extension.setClassName(textField.getText());
}
});
panel.add(textField, "growx");
return panel;
}
}

View File

@ -0,0 +1,166 @@
package net.sf.openrocket.simulation.extension.impl;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Set;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.adaptors.BooleanModel;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.fife.ui.rtextarea.RTextScrollPane;
import com.google.inject.Inject;
@Plugin
public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfigurator<ScriptingExtension> {
@Inject
private ScriptingUtil util;
private JComboBox languageSelector;
private RSyntaxTextArea text;
private JCheckBox trusted;
private ScriptingExtension extension;
private Simulation simulation;
public ScriptingConfigurator() {
super(ScriptingExtension.class);
}
@Override
protected JComponent getConfigurationComponent(final ScriptingExtension extension, Simulation simulation, JPanel panel) {
this.extension = extension;
this.simulation = simulation;
panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.language.label"), Style.BOLD), "spanx, split");
String[] languages = util.getLanguages().toArray(new String[0]);
languageSelector = new JComboBox(languages);
languageSelector.setEditable(false);
languageSelector.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setLanguage((String) languageSelector.getSelectedItem());
}
});
panel.add(languageSelector, "wrap para");
text = new RSyntaxTextArea(extension.getScript(), 20, 80);
text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT);
text.setCodeFoldingEnabled(true);
text.setLineWrap(true);
text.setWrapStyleWord(true);
text.setEditable(true);
text.setCurrentLineHighlightColor(new Color(255, 255, 230));
text.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent event) {
}
@Override
public void focusLost(FocusEvent event) {
String str = text.getText();
if (!extension.getScript().equals(str)) {
extension.setScript(str);
}
}
});
RTextScrollPane scroll = new RTextScrollPane(text);
panel.add(scroll, "spanx, grow, wrap para");
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 = "";
}
if (!language.equals(languageSelector.getSelectedItem())) {
languageSelector.setSelectedItem(language);
}
extension.setLanguage(language);
text.setSyntaxEditingStyle(findSyntaxLanguage(language));
getDialog().setTitle(getTitle(extension, simulation));
}
private String findSyntaxLanguage(String language) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName(language);
if (engine != null) {
Set<String> supported = TokenMakerFactory.getDefaultInstance().keySet();
for (String type : engine.getFactory().getMimeTypes()) {
if (supported.contains(type)) {
return type;
}
for (String match : supported) {
if (match.contains("/" + language.toLowerCase())) {
return match;
}
}
}
}
return SyntaxConstants.SYNTAX_STYLE_NONE;
}
}

View File

@ -0,0 +1,38 @@
package net.sf.openrocket.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
public class Scripting {
public static void main(String[] args) {
System.out.println("Scripting APIs:");
ScriptEngineManager manager = new ScriptEngineManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
System.out.println(" engineName=" + factory.getEngineName() +
" engineVersion=" + factory.getEngineVersion() +
" languageName=" + factory.getLanguageName() +
" languageVersion=" + factory.getLanguageVersion() +
" names=" + factory.getNames() +
" mimeTypes=" + factory.getMimeTypes() +
" extensions=" + factory.getExtensions());
}
System.out.println();
System.out.println("RSyntaxTextArea supported syntax languages:");
TokenMakerFactory f = TokenMakerFactory.getDefaultInstance();
List<String> list = new ArrayList<String>(f.keySet());
Collections.sort(list);
for (String type : list) {
System.out.println(" " + type);
}
System.out.println();
}
}