1 package net.sf.openrocket.document;
3 import java.util.EventListener;
4 import java.util.EventObject;
7 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
8 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
9 import net.sf.openrocket.aerodynamics.WarningSet;
10 import net.sf.openrocket.logging.LogHelper;
11 import net.sf.openrocket.masscalc.BasicMassCalculator;
12 import net.sf.openrocket.masscalc.MassCalculator;
13 import net.sf.openrocket.rocketcomponent.Configuration;
14 import net.sf.openrocket.rocketcomponent.Rocket;
15 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
16 import net.sf.openrocket.simulation.CustomExpression;
17 import net.sf.openrocket.simulation.FlightData;
18 import net.sf.openrocket.simulation.RK4SimulationStepper;
19 import net.sf.openrocket.simulation.SimulationConditions;
20 import net.sf.openrocket.simulation.SimulationEngine;
21 import net.sf.openrocket.simulation.SimulationOptions;
22 import net.sf.openrocket.simulation.SimulationStepper;
23 import net.sf.openrocket.simulation.exception.SimulationException;
24 import net.sf.openrocket.simulation.exception.SimulationListenerException;
25 import net.sf.openrocket.simulation.listeners.SimulationListener;
26 import net.sf.openrocket.startup.Application;
27 import net.sf.openrocket.util.ArrayList;
28 import net.sf.openrocket.util.BugException;
29 import net.sf.openrocket.util.ChangeSource;
30 import net.sf.openrocket.util.SafetyMutex;
31 import net.sf.openrocket.util.StateChangeListener;
34 * A class defining a simulation, its conditions and simulated data.
36 * This class is not thread-safe and enforces single-threaded access with a
39 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
41 public class Simulation implements ChangeSource, Cloneable {
42 private static final LogHelper log = Application.getLogger();
44 public static enum Status {
48 /** Loaded from file, status probably up-to-date */
54 /** Imported external data */
57 /** Not yet simulated */
61 private SafetyMutex mutex = SafetyMutex.newInstance();
63 private final Rocket rocket;
64 private final OpenRocketDocument document;
66 private String name = "";
68 private Status status = Status.NOT_SIMULATED;
70 /** The conditions to use */
71 // TODO: HIGH: Change to use actual conditions class??
72 private SimulationOptions options;
74 private ArrayList<String> simulationListeners = new ArrayList<String>();
75 private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
77 private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
78 private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
79 private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
80 private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
82 /** Listeners for this object */
83 private List<EventListener> listeners = new ArrayList<EventListener>();
86 /** The conditions actually used in the previous simulation, or null */
87 private SimulationOptions simulatedConditions = null;
88 private String simulatedMotors = null;
89 private FlightData simulatedData = null;
90 private int simulatedRocketID = -1;
94 * Create a new simulation for the rocket. Parent document should also be provided.
95 * The initial motor configuration is taken from the default rocket configuration.
97 * @param rocket the rocket associated with the simulation.
99 public Simulation(OpenRocketDocument doc, Rocket rocket) {
100 // It may seem silly to pass in the document and rocket, since usually when called we
101 // use doc.getRocket, but I guess there is some reason; when cloning a simulation + rocket we don't need
102 // to make a duplicate of the undo data etc stored in the document. --Richard
104 this.rocket = rocket;
105 this.status = Status.NOT_SIMULATED;
107 options = new SimulationOptions(rocket);
108 options.setMotorConfigurationID(
109 rocket.getDefaultConfiguration().getMotorConfigurationID());
110 options.addChangeListener(new ConditionListener());
114 public Simulation(OpenRocketDocument doc, Rocket rocket, Status status, String name, SimulationOptions options,
115 List<String> listeners, FlightData data) {
118 throw new IllegalArgumentException("rocket cannot be null");
120 throw new IllegalArgumentException("status cannot be null");
122 throw new IllegalArgumentException("name cannot be null");
124 throw new IllegalArgumentException("options cannot be null");
126 this.rocket = rocket;
129 if (status == Status.UPTODATE) {
130 this.status = Status.LOADED;
131 } else if (data == null) {
132 this.status = Status.NOT_SIMULATED;
134 this.status = status;
139 this.options = options;
140 options.addChangeListener(new ConditionListener());
142 if (listeners != null) {
143 this.simulationListeners.addAll(listeners);
147 if (data != null && this.status != Status.NOT_SIMULATED) {
148 simulatedData = data;
149 if (this.status == Status.LOADED) {
150 simulatedConditions = options.clone();
151 simulatedRocketID = rocket.getModID();
158 * Return the parent document for this simulation
160 public OpenRocketDocument getDocument(){
164 public void addCustomExpression(CustomExpression expression){
165 this.status = Simulation.Status.OUTDATED;
166 log.debug("Simulation must be run again to update custom expression.");
167 customExpressions.add(expression);
170 public void removeCustomExpression(CustomExpression expression){
171 customExpressions.remove(expression);
174 public ArrayList<CustomExpression> getCustomExpressions(){
175 return customExpressions;
180 * Return the rocket associated with this simulation.
182 * @return the rocket.
184 public Rocket getRocket() {
191 * Return a newly created Configuration for this simulation. The configuration
192 * has the motor ID set and all stages active.
194 * @return a newly created Configuration of the launch conditions.
196 public Configuration getConfiguration() {
198 Configuration c = new Configuration(rocket);
199 c.setMotorConfigurationID(options.getMotorConfigurationID());
205 * Returns the simulation options attached to this simulation. The options
206 * may be modified freely, and the status of the simulation will change to reflect
209 * @return the simulation conditions.
211 public SimulationOptions getOptions() {
218 * Get the list of simulation listeners. The returned list is the one used by
219 * this object; changes to it will reflect changes in the simulation.
221 * @return the actual list of simulation listeners.
223 public List<String> getSimulationListeners() {
225 return simulationListeners;
230 * Return the user-defined name of the simulation.
232 * @return the name for the simulation.
234 public String getName() {
240 * Set the user-defined name of the simulation. Setting the name to
241 * null yields an empty name.
243 * @param name the name of the simulation.
245 public void setName(String name) {
246 mutex.lock("setName");
248 if (this.name.equals(name))
258 mutex.unlock("setName");
264 * Returns the status of this simulation. This method examines whether the
265 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
270 public Status getStatus() {
273 if (status == Status.UPTODATE || status == Status.LOADED) {
274 if (rocket.getFunctionalModID() != simulatedRocketID ||
275 !options.equals(simulatedConditions))
276 return Status.OUTDATED;
285 * Simulate the flight.
287 * @param additionalListeners additional simulation listeners (those defined by the simulation are used in any case)
288 * @throws SimulationException if a problem occurs during simulation
290 public void simulate(SimulationListener... additionalListeners)
291 throws SimulationException {
292 mutex.lock("simulate");
295 if (this.status == Status.EXTERNAL) {
296 throw new SimulationException("Cannot simulate imported simulation.");
299 SimulationEngine simulator;
302 simulator = simulationEngineClass.newInstance();
303 } catch (InstantiationException e) {
304 throw new IllegalStateException("Cannot instantiate simulator.", e);
305 } catch (IllegalAccessException e) {
306 throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
309 SimulationConditions simulationConditions = options.toSimulationConditions();
310 simulationConditions.setSimulation(this);
311 for (SimulationListener l : additionalListeners) {
312 simulationConditions.getSimulationListenerList().add(l);
315 for (String className : simulationListeners) {
316 SimulationListener l = null;
318 Class<?> c = Class.forName(className);
319 l = (SimulationListener) c.newInstance();
320 } catch (Exception e) {
321 throw new SimulationListenerException("Could not instantiate listener of " +
322 "class: " + className, e);
324 simulationConditions.getSimulationListenerList().add(l);
328 log.debug("Simulation: calling simulator");
329 t1 = System.currentTimeMillis();
330 simulatedData = simulator.simulate(simulationConditions);
331 t2 = System.currentTimeMillis();
332 log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
334 // Set simulated info after simulation, will not be set in case of exception
335 simulatedConditions = options.clone();
336 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
337 simulatedRocketID = rocket.getFunctionalModID();
339 status = Status.UPTODATE;
342 mutex.unlock("simulate");
348 * Return the conditions used in the previous simulation, or <code>null</code>
349 * if this simulation has not been run.
351 * @return the conditions used in the previous simulation, or <code>null</code>.
353 public SimulationOptions getSimulatedConditions() {
355 return simulatedConditions;
359 * Return the warnings generated in the previous simulation, or
360 * <code>null</code> if this simulation has not been run. This is the same
361 * warning set as contained in the <code>FlightData</code> object.
363 * @return the warnings during the previous simulation, or <code>null</code>.
364 * @see FlightData#getWarningSet()
366 public WarningSet getSimulatedWarnings() {
368 if (simulatedData == null)
370 return simulatedData.getWarningSet();
375 * Return a string describing the motor configuration of the previous simulation,
376 * or <code>null</code> if this simulation has not been run.
378 * @return a description of the motor configuration of the previous simulation, or
380 * @see Rocket#getMotorConfigurationNameOrDescription(String)
382 public String getSimulatedMotorDescription() {
384 return simulatedMotors;
388 * Return the flight data of the previous simulation, or <code>null</code> if
389 * this simulation has not been run.
391 * @return the flight data of the previous simulation, or <code>null</code>.
393 public FlightData getSimulatedData() {
395 return simulatedData;
401 * Returns a copy of this simulation suitable for cut/copy/paste operations.
402 * The rocket refers to the same instance as the original simulation.
403 * This excludes any simulated data.
405 * @return a copy of this simulation and its conditions.
407 public Simulation copy() {
411 Simulation copy = (Simulation) super.clone();
413 copy.mutex = SafetyMutex.newInstance();
414 copy.status = Status.NOT_SIMULATED;
415 copy.options = this.options.clone();
416 copy.simulationListeners = this.simulationListeners.clone();
417 copy.listeners = new ArrayList<EventListener>();
418 copy.simulatedConditions = null;
419 copy.simulatedMotors = null;
420 copy.simulatedData = null;
421 copy.simulatedRocketID = -1;
425 } catch (CloneNotSupportedException e) {
426 throw new BugException("Clone not supported, BUG", e);
428 mutex.unlock("copy");
434 * Create a duplicate of this simulation with the specified rocket. The new
435 * simulation is in non-simulated state.
437 * @param newRocket the rocket for the new simulation.
438 * @return a new simulation with the same conditions and properties.
440 public Simulation duplicateSimulation(Rocket newRocket) {
441 mutex.lock("duplicateSimulation");
443 Simulation copy = new Simulation(document, newRocket);
445 copy.name = this.name;
446 copy.options.copyFrom(this.options);
447 copy.simulationListeners = this.simulationListeners.clone();
448 copy.simulationStepperClass = this.simulationStepperClass;
449 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
453 mutex.unlock("duplicateSimulation");
460 public void addChangeListener(EventListener listener) {
462 listeners.add(listener);
466 public void removeChangeListener(EventListener listener) {
468 listeners.remove(listener);
471 protected void fireChangeEvent() {
472 EventObject e = new EventObject(this);
473 // Copy the list before iterating to prevent concurrent modification exceptions.
474 EventListener[] ls = listeners.toArray(new EventListener[0]);
475 for (EventListener l : ls) {
476 if ( l instanceof StateChangeListener ) {
477 ((StateChangeListener)l).stateChanged(e);
485 private class ConditionListener implements StateChangeListener {
487 private Status oldStatus = null;
490 public void stateChanged(EventObject e) {
491 if (getStatus() != oldStatus) {
492 oldStatus = getStatus();