1 package net.sf.openrocket.document;
3 import java.util.ArrayList;
6 import javax.swing.event.ChangeEvent;
7 import javax.swing.event.ChangeListener;
9 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
10 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
11 import net.sf.openrocket.aerodynamics.WarningSet;
12 import net.sf.openrocket.logging.LogHelper;
13 import net.sf.openrocket.masscalc.BasicMassCalculator;
14 import net.sf.openrocket.masscalc.MassCalculator;
15 import net.sf.openrocket.rocketcomponent.Configuration;
16 import net.sf.openrocket.rocketcomponent.Rocket;
17 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
18 import net.sf.openrocket.simulation.FlightData;
19 import net.sf.openrocket.simulation.GUISimulationConditions;
20 import net.sf.openrocket.simulation.RK4SimulationStepper;
21 import net.sf.openrocket.simulation.SimulationConditions;
22 import net.sf.openrocket.simulation.SimulationEngine;
23 import net.sf.openrocket.simulation.SimulationStepper;
24 import net.sf.openrocket.simulation.exception.SimulationException;
25 import net.sf.openrocket.simulation.exception.SimulationListenerException;
26 import net.sf.openrocket.simulation.listeners.SimulationListener;
27 import net.sf.openrocket.startup.Application;
28 import net.sf.openrocket.util.BugException;
29 import net.sf.openrocket.util.ChangeSource;
32 public class Simulation implements ChangeSource, Cloneable {
33 private static final LogHelper log = Application.getLogger();
35 public static enum Status {
39 /** Loaded from file, status probably up-to-date */
45 /** Imported external data */
48 /** Not yet simulated */
53 private final Rocket rocket;
55 private String name = "";
57 private Status status = Status.NOT_SIMULATED;
59 /** The conditions to use */
60 // TODO: HIGH: Change to use actual conditions class??
61 private GUISimulationConditions conditions;
63 private ArrayList<String> simulationListeners = new ArrayList<String>();
65 private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
66 private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
67 private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
68 private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
72 /** Listeners for this object */
73 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
76 /** The conditions actually used in the previous simulation, or null */
77 private GUISimulationConditions simulatedConditions = null;
78 private String simulatedMotors = null;
79 private FlightData simulatedData = null;
80 private int simulatedRocketID = -1;
84 * Create a new simulation for the rocket. The initial motor configuration is
85 * taken from the default rocket configuration.
87 * @param rocket the rocket associated with the simulation.
89 public Simulation(Rocket rocket) {
91 this.status = Status.NOT_SIMULATED;
93 conditions = new GUISimulationConditions(rocket);
94 conditions.setMotorConfigurationID(
95 rocket.getDefaultConfiguration().getMotorConfigurationID());
96 conditions.addChangeListener(new ConditionListener());
100 public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
101 List<String> listeners, FlightData data) {
104 throw new IllegalArgumentException("rocket cannot be null");
106 throw new IllegalArgumentException("status cannot be null");
108 throw new IllegalArgumentException("name cannot be null");
109 if (conditions == null)
110 throw new IllegalArgumentException("conditions cannot be null");
112 this.rocket = rocket;
114 if (status == Status.UPTODATE) {
115 this.status = Status.LOADED;
116 } else if (data == null) {
117 this.status = Status.NOT_SIMULATED;
119 this.status = status;
124 this.conditions = conditions;
125 conditions.addChangeListener(new ConditionListener());
127 if (listeners != null) {
128 this.simulationListeners.addAll(listeners);
132 if (data != null && this.status != Status.NOT_SIMULATED) {
133 simulatedData = data;
134 if (this.status == Status.LOADED) {
135 simulatedConditions = conditions.clone();
136 simulatedRocketID = rocket.getModID();
144 * Return the rocket associated with this simulation.
146 * @return the rocket.
148 public Rocket getRocket() {
154 * Return a newly created Configuration for this simulation. The configuration
155 * has the motor ID set and all stages active.
157 * @return a newly created Configuration of the launch conditions.
159 public Configuration getConfiguration() {
160 Configuration c = new Configuration(rocket);
161 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
167 * Returns the simulation conditions attached to this simulation. The conditions
168 * may be modified freely, and the status of the simulation will change to reflect
171 * @return the simulation conditions.
173 public GUISimulationConditions getConditions() {
179 * Get the list of simulation listeners. The returned list is the one used by
180 * this object; changes to it will reflect changes in the simulation.
182 * @return the actual list of simulation listeners.
184 public List<String> getSimulationListeners() {
185 return simulationListeners;
190 * Return the user-defined name of the simulation.
192 * @return the name for the simulation.
194 public String getName() {
199 * Set the user-defined name of the simulation. Setting the name to
200 * null yields an empty name.
202 * @param name the name of the simulation.
204 public void setName(String name) {
205 if (this.name.equals(name))
218 * Returns the status of this simulation. This method examines whether the
219 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
224 public Status getStatus() {
225 if (status == Status.UPTODATE || status == Status.LOADED) {
226 if (rocket.getFunctionalModID() != simulatedRocketID ||
227 !conditions.equals(simulatedConditions))
228 return Status.OUTDATED;
237 public void simulate(SimulationListener... additionalListeners)
238 throws SimulationException {
240 if (this.status == Status.EXTERNAL) {
241 throw new SimulationException("Cannot simulate imported simulation.");
244 SimulationEngine simulator;
247 simulator = simulationEngineClass.newInstance();
248 } catch (InstantiationException e) {
249 throw new IllegalStateException("Cannot instantiate simulator.", e);
250 } catch (IllegalAccessException e) {
251 throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
252 } catch (NullPointerException e) {
253 throw new IllegalStateException("Simulator null", e);
256 SimulationConditions simulationConditions = conditions.toSimulationConditions();
257 for (SimulationListener l : additionalListeners) {
258 simulationConditions.getSimulationListenerList().add(l);
261 for (String className : simulationListeners) {
262 SimulationListener l = null;
264 Class<?> c = Class.forName(className);
265 l = (SimulationListener) c.newInstance();
266 } catch (Exception e) {
267 throw new SimulationListenerException("Could not instantiate listener of " +
268 "class: " + className, e);
270 simulationConditions.getSimulationListenerList().add(l);
274 log.debug("Simulation: calling simulator");
275 t1 = System.currentTimeMillis();
276 simulatedData = simulator.simulate(simulationConditions);
277 t2 = System.currentTimeMillis();
278 log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
280 // Set simulated info after simulation, will not be set in case of exception
281 simulatedConditions = conditions.clone();
282 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
283 simulatedRocketID = rocket.getFunctionalModID();
285 status = Status.UPTODATE;
291 * Return the conditions used in the previous simulation, or <code>null</code>
292 * if this simulation has not been run.
294 * @return the conditions used in the previous simulation, or <code>null</code>.
296 public GUISimulationConditions getSimulatedConditions() {
297 return simulatedConditions;
301 * Return the warnings generated in the previous simulation, or
302 * <code>null</code> if this simulation has not been run. This is the same
303 * warning set as contained in the <code>FlightData</code> object.
305 * @return the warnings during the previous simulation, or <code>null</code>.
306 * @see FlightData#getWarningSet()
308 public WarningSet getSimulatedWarnings() {
309 if (simulatedData == null)
311 return simulatedData.getWarningSet();
316 * Return a string describing the motor configuration of the previous simulation,
317 * or <code>null</code> if this simulation has not been run.
319 * @return a description of the motor configuration of the previous simulation, or
321 * @see Rocket#getMotorConfigurationNameOrDescription(String)
323 public String getSimulatedMotorDescription() {
324 return simulatedMotors;
328 * Return the flight data of the previous simulation, or <code>null</code> if
329 * this simulation has not been run.
331 * @return the flight data of the previous simulation, or <code>null</code>.
333 public FlightData getSimulatedData() {
334 return simulatedData;
340 * Returns a copy of this simulation suitable for cut/copy/paste operations.
341 * This excludes any simulated data.
343 * @return a copy of this simulation and its conditions.
345 @SuppressWarnings("unchecked")
346 public Simulation copy() {
349 Simulation copy = (Simulation) super.clone();
351 copy.status = Status.NOT_SIMULATED;
352 copy.conditions = this.conditions.clone();
353 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
354 copy.listeners = new ArrayList<ChangeListener>();
355 copy.simulatedConditions = null;
356 copy.simulatedMotors = null;
357 copy.simulatedData = null;
358 copy.simulatedRocketID = -1;
363 } catch (CloneNotSupportedException e) {
364 throw new BugException("Clone not supported, BUG", e);
370 * Create a duplicate of this simulation with the specified rocket. The new
371 * simulation is in non-simulated state.
373 * @param newRocket the rocket for the new simulation.
374 * @return a new simulation with the same conditions and properties.
376 @SuppressWarnings("unchecked")
377 public Simulation duplicateSimulation(Rocket newRocket) {
378 Simulation copy = new Simulation(newRocket);
380 copy.name = this.name;
381 copy.conditions.copyFrom(this.conditions);
382 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
383 copy.simulationStepperClass = this.simulationStepperClass;
384 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
392 public void addChangeListener(ChangeListener listener) {
393 listeners.add(listener);
397 public void removeChangeListener(ChangeListener listener) {
398 listeners.remove(listener);
401 protected void fireChangeEvent() {
402 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
403 ChangeEvent e = new ChangeEvent(this);
404 for (ChangeListener l : ls) {
412 private class ConditionListener implements ChangeListener {
414 private Status oldStatus = null;
417 public void stateChanged(ChangeEvent e) {
418 if (getStatus() != oldStatus) {
419 oldStatus = getStatus();