1 package net.sf.openrocket.document;
5 import javax.swing.event.ChangeEvent;
6 import javax.swing.event.ChangeListener;
8 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
9 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
10 import net.sf.openrocket.aerodynamics.WarningSet;
11 import net.sf.openrocket.logging.LogHelper;
12 import net.sf.openrocket.masscalc.BasicMassCalculator;
13 import net.sf.openrocket.masscalc.MassCalculator;
14 import net.sf.openrocket.rocketcomponent.Configuration;
15 import net.sf.openrocket.rocketcomponent.Rocket;
16 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
17 import net.sf.openrocket.simulation.FlightData;
18 import net.sf.openrocket.simulation.GUISimulationConditions;
19 import net.sf.openrocket.simulation.RK4SimulationStepper;
20 import net.sf.openrocket.simulation.SimulationConditions;
21 import net.sf.openrocket.simulation.SimulationEngine;
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;
33 * A class defining a simulation, its conditions and simulated data.
35 * This class is not thread-safe and enforces single-threaded access with a
38 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
40 public class Simulation implements ChangeSource, Cloneable {
41 private static final LogHelper log = Application.getLogger();
43 public static enum Status {
47 /** Loaded from file, status probably up-to-date */
53 /** Imported external data */
56 /** Not yet simulated */
60 private SafetyMutex mutex = SafetyMutex.newInstance();
62 private final Rocket rocket;
64 private String name = "";
66 private Status status = Status.NOT_SIMULATED;
68 /** The conditions to use */
69 // TODO: HIGH: Change to use actual conditions class??
70 private GUISimulationConditions conditions;
72 private ArrayList<String> simulationListeners = new ArrayList<String>();
74 private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
75 private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
76 private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
77 private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
81 /** Listeners for this object */
82 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
85 /** The conditions actually used in the previous simulation, or null */
86 private GUISimulationConditions simulatedConditions = null;
87 private String simulatedMotors = null;
88 private FlightData simulatedData = null;
89 private int simulatedRocketID = -1;
93 * Create a new simulation for the rocket. The initial motor configuration is
94 * taken from the default rocket configuration.
96 * @param rocket the rocket associated with the simulation.
98 public Simulation(Rocket rocket) {
100 this.status = Status.NOT_SIMULATED;
102 conditions = new GUISimulationConditions(rocket);
103 conditions.setMotorConfigurationID(
104 rocket.getDefaultConfiguration().getMotorConfigurationID());
105 conditions.addChangeListener(new ConditionListener());
109 public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
110 List<String> listeners, FlightData data) {
113 throw new IllegalArgumentException("rocket cannot be null");
115 throw new IllegalArgumentException("status cannot be null");
117 throw new IllegalArgumentException("name cannot be null");
118 if (conditions == null)
119 throw new IllegalArgumentException("conditions cannot be null");
121 this.rocket = rocket;
123 if (status == Status.UPTODATE) {
124 this.status = Status.LOADED;
125 } else if (data == null) {
126 this.status = Status.NOT_SIMULATED;
128 this.status = status;
133 this.conditions = conditions;
134 conditions.addChangeListener(new ConditionListener());
136 if (listeners != null) {
137 this.simulationListeners.addAll(listeners);
141 if (data != null && this.status != Status.NOT_SIMULATED) {
142 simulatedData = data;
143 if (this.status == Status.LOADED) {
144 simulatedConditions = conditions.clone();
145 simulatedRocketID = rocket.getModID();
153 * Return the rocket associated with this simulation.
155 * @return the rocket.
157 public Rocket getRocket() {
164 * Return a newly created Configuration for this simulation. The configuration
165 * has the motor ID set and all stages active.
167 * @return a newly created Configuration of the launch conditions.
169 public Configuration getConfiguration() {
171 Configuration c = new Configuration(rocket);
172 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
178 * Returns the simulation conditions attached to this simulation. The conditions
179 * may be modified freely, and the status of the simulation will change to reflect
182 * @return the simulation conditions.
184 public GUISimulationConditions getConditions() {
191 * Get the list of simulation listeners. The returned list is the one used by
192 * this object; changes to it will reflect changes in the simulation.
194 * @return the actual list of simulation listeners.
196 public List<String> getSimulationListeners() {
198 return simulationListeners;
203 * Return the user-defined name of the simulation.
205 * @return the name for the simulation.
207 public String getName() {
213 * Set the user-defined name of the simulation. Setting the name to
214 * null yields an empty name.
216 * @param name the name of the simulation.
218 public void setName(String name) {
219 mutex.lock("setName");
221 if (this.name.equals(name))
231 mutex.unlock("setName");
237 * Returns the status of this simulation. This method examines whether the
238 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
243 public Status getStatus() {
246 if (status == Status.UPTODATE || status == Status.LOADED) {
247 if (rocket.getFunctionalModID() != simulatedRocketID ||
248 !conditions.equals(simulatedConditions))
249 return Status.OUTDATED;
258 public void simulate(SimulationListener... additionalListeners)
259 throws SimulationException {
260 mutex.lock("simulate");
263 if (this.status == Status.EXTERNAL) {
264 throw new SimulationException("Cannot simulate imported simulation.");
267 SimulationEngine simulator;
270 simulator = simulationEngineClass.newInstance();
271 } catch (InstantiationException e) {
272 throw new IllegalStateException("Cannot instantiate simulator.", e);
273 } catch (IllegalAccessException e) {
274 throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
275 } catch (NullPointerException e) {
276 throw new IllegalStateException("Simulator null", e);
279 SimulationConditions simulationConditions = conditions.toSimulationConditions();
280 for (SimulationListener l : additionalListeners) {
281 simulationConditions.getSimulationListenerList().add(l);
284 for (String className : simulationListeners) {
285 SimulationListener l = null;
287 Class<?> c = Class.forName(className);
288 l = (SimulationListener) c.newInstance();
289 } catch (Exception e) {
290 throw new SimulationListenerException("Could not instantiate listener of " +
291 "class: " + className, e);
293 simulationConditions.getSimulationListenerList().add(l);
297 log.debug("Simulation: calling simulator");
298 t1 = System.currentTimeMillis();
299 simulatedData = simulator.simulate(simulationConditions);
300 t2 = System.currentTimeMillis();
301 log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
303 // Set simulated info after simulation, will not be set in case of exception
304 simulatedConditions = conditions.clone();
305 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
306 simulatedRocketID = rocket.getFunctionalModID();
308 status = Status.UPTODATE;
311 mutex.unlock("simulate");
317 * Return the conditions used in the previous simulation, or <code>null</code>
318 * if this simulation has not been run.
320 * @return the conditions used in the previous simulation, or <code>null</code>.
322 public GUISimulationConditions getSimulatedConditions() {
324 return simulatedConditions;
328 * Return the warnings generated in the previous simulation, or
329 * <code>null</code> if this simulation has not been run. This is the same
330 * warning set as contained in the <code>FlightData</code> object.
332 * @return the warnings during the previous simulation, or <code>null</code>.
333 * @see FlightData#getWarningSet()
335 public WarningSet getSimulatedWarnings() {
337 if (simulatedData == null)
339 return simulatedData.getWarningSet();
344 * Return a string describing the motor configuration of the previous simulation,
345 * or <code>null</code> if this simulation has not been run.
347 * @return a description of the motor configuration of the previous simulation, or
349 * @see Rocket#getMotorConfigurationNameOrDescription(String)
351 public String getSimulatedMotorDescription() {
353 return simulatedMotors;
357 * Return the flight data of the previous simulation, or <code>null</code> if
358 * this simulation has not been run.
360 * @return the flight data of the previous simulation, or <code>null</code>.
362 public FlightData getSimulatedData() {
364 return simulatedData;
370 * Returns a copy of this simulation suitable for cut/copy/paste operations.
371 * This excludes any simulated data.
373 * @return a copy of this simulation and its conditions.
375 public Simulation copy() {
379 Simulation copy = (Simulation) super.clone();
381 copy.mutex = SafetyMutex.newInstance();
382 copy.status = Status.NOT_SIMULATED;
383 copy.conditions = this.conditions.clone();
384 copy.simulationListeners = this.simulationListeners.clone();
385 copy.listeners = new ArrayList<ChangeListener>();
386 copy.simulatedConditions = null;
387 copy.simulatedMotors = null;
388 copy.simulatedData = null;
389 copy.simulatedRocketID = -1;
393 } catch (CloneNotSupportedException e) {
394 throw new BugException("Clone not supported, BUG", e);
396 mutex.unlock("copy");
402 * Create a duplicate of this simulation with the specified rocket. The new
403 * simulation is in non-simulated state.
405 * @param newRocket the rocket for the new simulation.
406 * @return a new simulation with the same conditions and properties.
408 public Simulation duplicateSimulation(Rocket newRocket) {
409 mutex.lock("duplicateSimulation");
411 Simulation copy = new Simulation(newRocket);
413 copy.name = this.name;
414 copy.conditions.copyFrom(this.conditions);
415 copy.simulationListeners = this.simulationListeners.clone();
416 copy.simulationStepperClass = this.simulationStepperClass;
417 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
421 mutex.unlock("duplicateSimulation");
428 public void addChangeListener(ChangeListener listener) {
430 listeners.add(listener);
434 public void removeChangeListener(ChangeListener listener) {
436 listeners.remove(listener);
439 protected void fireChangeEvent() {
440 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
441 ChangeEvent e = new ChangeEvent(this);
442 for (ChangeListener l : ls) {
450 private class ConditionListener implements ChangeListener {
452 private Status oldStatus = null;
455 public void stateChanged(ChangeEvent e) {
456 if (getStatus() != oldStatus) {
457 oldStatus = getStatus();