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.FlightData;
17 import net.sf.openrocket.simulation.RK4SimulationStepper;
18 import net.sf.openrocket.simulation.SimulationConditions;
19 import net.sf.openrocket.simulation.SimulationEngine;
20 import net.sf.openrocket.simulation.SimulationOptions;
21 import net.sf.openrocket.simulation.SimulationStepper;
22 import net.sf.openrocket.simulation.exception.SimulationException;
23 import net.sf.openrocket.simulation.exception.SimulationListenerException;
24 import net.sf.openrocket.simulation.listeners.SimulationListener;
25 import net.sf.openrocket.startup.Application;
26 import net.sf.openrocket.util.ArrayList;
27 import net.sf.openrocket.util.BugException;
28 import net.sf.openrocket.util.ChangeSource;
29 import net.sf.openrocket.util.SafetyMutex;
30 import net.sf.openrocket.util.StateChangeListener;
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 SimulationOptions options;
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;
79 /** Listeners for this object */
80 private List<EventListener> listeners = new ArrayList<EventListener>();
83 /** The conditions actually used in the previous simulation, or null */
84 private SimulationOptions simulatedConditions = null;
85 private String simulatedMotors = null;
86 private FlightData simulatedData = null;
87 private int simulatedRocketID = -1;
91 * Create a new simulation for the rocket. Parent document should also be provided.
92 * The initial motor configuration is taken from the default rocket configuration.
94 * @param rocket the rocket associated with the simulation.
96 public Simulation(Rocket rocket) {
98 this.status = Status.NOT_SIMULATED;
100 options = new SimulationOptions(rocket);
101 options.setMotorConfigurationID(
102 rocket.getDefaultConfiguration().getMotorConfigurationID());
103 options.addChangeListener(new ConditionListener());
107 public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
108 List<String> listeners, FlightData data) {
111 throw new IllegalArgumentException("rocket cannot be null");
113 throw new IllegalArgumentException("status cannot be null");
115 throw new IllegalArgumentException("name cannot be null");
117 throw new IllegalArgumentException("options cannot be null");
119 this.rocket = rocket;
121 if (status == Status.UPTODATE) {
122 this.status = Status.LOADED;
123 } else if (data == null) {
124 this.status = Status.NOT_SIMULATED;
126 this.status = status;
131 this.options = options;
132 options.addChangeListener(new ConditionListener());
134 if (listeners != null) {
135 this.simulationListeners.addAll(listeners);
139 if (data != null && this.status != Status.NOT_SIMULATED) {
140 simulatedData = data;
141 if (this.status == Status.LOADED) {
142 simulatedConditions = options.clone();
143 simulatedRocketID = rocket.getModID();
150 * Return the rocket associated with this simulation.
152 * @return the rocket.
154 public Rocket getRocket() {
161 * Return a newly created Configuration for this simulation. The configuration
162 * has the motor ID set and all stages active.
164 * @return a newly created Configuration of the launch conditions.
166 public Configuration getConfiguration() {
168 Configuration c = new Configuration(rocket);
169 c.setMotorConfigurationID(options.getMotorConfigurationID());
175 * Returns the simulation options attached to this simulation. The options
176 * may be modified freely, and the status of the simulation will change to reflect
179 * @return the simulation conditions.
181 public SimulationOptions getOptions() {
188 * Get the list of simulation listeners. The returned list is the one used by
189 * this object; changes to it will reflect changes in the simulation.
191 * @return the actual list of simulation listeners.
193 public List<String> getSimulationListeners() {
195 return simulationListeners;
200 * Return the user-defined name of the simulation.
202 * @return the name for the simulation.
204 public String getName() {
210 * Set the user-defined name of the simulation. Setting the name to
211 * null yields an empty name.
213 * @param name the name of the simulation.
215 public void setName(String name) {
216 mutex.lock("setName");
218 if (this.name.equals(name))
228 mutex.unlock("setName");
234 * Returns the status of this simulation. This method examines whether the
235 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
240 public Status getStatus() {
243 if (status == Status.UPTODATE || status == Status.LOADED) {
244 if (rocket.getFunctionalModID() != simulatedRocketID ||
245 !options.equals(simulatedConditions))
246 return Status.OUTDATED;
255 * Simulate the flight.
257 * @param additionalListeners additional simulation listeners (those defined by the simulation are used in any case)
258 * @throws SimulationException if a problem occurs during simulation
260 public void simulate(SimulationListener... additionalListeners)
261 throws SimulationException {
262 mutex.lock("simulate");
265 if (this.status == Status.EXTERNAL) {
266 throw new SimulationException("Cannot simulate imported simulation.");
269 SimulationEngine simulator;
272 simulator = simulationEngineClass.newInstance();
273 } catch (InstantiationException e) {
274 throw new IllegalStateException("Cannot instantiate simulator.", e);
275 } catch (IllegalAccessException e) {
276 throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
279 SimulationConditions simulationConditions = options.toSimulationConditions();
280 simulationConditions.setSimulation(this);
281 for (SimulationListener l : additionalListeners) {
282 simulationConditions.getSimulationListenerList().add(l);
285 for (String className : simulationListeners) {
286 SimulationListener l = null;
288 Class<?> c = Class.forName(className);
289 l = (SimulationListener) c.newInstance();
290 } catch (Exception e) {
291 throw new SimulationListenerException("Could not instantiate listener of " +
292 "class: " + className, e);
294 simulationConditions.getSimulationListenerList().add(l);
298 log.debug("Simulation: calling simulator");
299 t1 = System.currentTimeMillis();
300 simulatedData = simulator.simulate(simulationConditions);
301 t2 = System.currentTimeMillis();
302 log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
304 // Set simulated info after simulation, will not be set in case of exception
305 simulatedConditions = options.clone();
306 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
307 simulatedRocketID = rocket.getFunctionalModID();
309 status = Status.UPTODATE;
312 mutex.unlock("simulate");
318 * Return the conditions used in the previous simulation, or <code>null</code>
319 * if this simulation has not been run.
321 * @return the conditions used in the previous simulation, or <code>null</code>.
323 public SimulationOptions getSimulatedConditions() {
325 return simulatedConditions;
329 * Return the warnings generated in the previous simulation, or
330 * <code>null</code> if this simulation has not been run. This is the same
331 * warning set as contained in the <code>FlightData</code> object.
333 * @return the warnings during the previous simulation, or <code>null</code>.
334 * @see FlightData#getWarningSet()
336 public WarningSet getSimulatedWarnings() {
338 if (simulatedData == null)
340 return simulatedData.getWarningSet();
345 * Return a string describing the motor configuration of the previous simulation,
346 * or <code>null</code> if this simulation has not been run.
348 * @return a description of the motor configuration of the previous simulation, or
350 * @see Rocket#getMotorConfigurationNameOrDescription(String)
352 public String getSimulatedMotorDescription() {
354 return simulatedMotors;
358 * Return the flight data of the previous simulation, or <code>null</code> if
359 * this simulation has not been run.
361 * @return the flight data of the previous simulation, or <code>null</code>.
363 public FlightData getSimulatedData() {
365 return simulatedData;
371 * Returns a copy of this simulation suitable for cut/copy/paste operations.
372 * The rocket refers to the same instance as the original simulation.
373 * This excludes any simulated data.
375 * @return a copy of this simulation and its conditions.
377 public Simulation copy() {
381 Simulation copy = (Simulation) super.clone();
383 copy.mutex = SafetyMutex.newInstance();
384 copy.status = Status.NOT_SIMULATED;
385 copy.options = this.options.clone();
386 copy.simulationListeners = this.simulationListeners.clone();
387 copy.listeners = new ArrayList<EventListener>();
388 copy.simulatedConditions = null;
389 copy.simulatedMotors = null;
390 copy.simulatedData = null;
391 copy.simulatedRocketID = -1;
395 } catch (CloneNotSupportedException e) {
396 throw new BugException("Clone not supported, BUG", e);
398 mutex.unlock("copy");
404 * Create a duplicate of this simulation with the specified rocket. The new
405 * simulation is in non-simulated state.
407 * @param newRocket the rocket for the new simulation.
408 * @return a new simulation with the same conditions and properties.
410 public Simulation duplicateSimulation(Rocket newRocket) {
411 mutex.lock("duplicateSimulation");
413 Simulation copy = new Simulation(newRocket);
415 copy.name = this.name;
416 copy.options.copyFrom(this.options);
417 copy.simulationListeners = this.simulationListeners.clone();
418 copy.simulationStepperClass = this.simulationStepperClass;
419 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
423 mutex.unlock("duplicateSimulation");
430 public void addChangeListener(EventListener listener) {
432 listeners.add(listener);
436 public void removeChangeListener(EventListener listener) {
438 listeners.remove(listener);
441 protected void fireChangeEvent() {
442 EventObject e = new EventObject(this);
443 // Copy the list before iterating to prevent concurrent modification exceptions.
444 EventListener[] ls = listeners.toArray(new EventListener[0]);
445 for (EventListener l : ls) {
446 if ( l instanceof StateChangeListener ) {
447 ((StateChangeListener)l).stateChanged(e);
455 private class ConditionListener implements StateChangeListener {
457 private Status oldStatus = null;
460 public void stateChanged(EventObject e) {
461 if (getStatus() != oldStatus) {
462 oldStatus = getStatus();