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.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.util.BugException;
27 import net.sf.openrocket.util.ChangeSource;
30 public class Simulation implements ChangeSource, Cloneable {
32 public static enum Status {
36 /** Loaded from file, status probably up-to-date */
42 /** Imported external data */
45 /** Not yet simulated */
50 private final Rocket rocket;
52 private String name = "";
54 private Status status = Status.NOT_SIMULATED;
56 /** The conditions to use */
57 // TODO: HIGH: Change to use actual conditions class??
58 private GUISimulationConditions conditions;
60 private ArrayList<String> simulationListeners = new ArrayList<String>();
62 private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
63 private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
64 private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
65 private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
69 /** Listeners for this object */
70 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
73 /** The conditions actually used in the previous simulation, or null */
74 private GUISimulationConditions simulatedConditions = null;
75 private String simulatedMotors = null;
76 private FlightData simulatedData = null;
77 private int simulatedRocketID = -1;
81 * Create a new simulation for the rocket. The initial motor configuration is
82 * taken from the default rocket configuration.
84 * @param rocket the rocket associated with the simulation.
86 public Simulation(Rocket rocket) {
88 this.status = Status.NOT_SIMULATED;
90 conditions = new GUISimulationConditions(rocket);
91 conditions.setMotorConfigurationID(
92 rocket.getDefaultConfiguration().getMotorConfigurationID());
93 conditions.addChangeListener(new ConditionListener());
97 public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
98 List<String> listeners, FlightData data) {
101 throw new IllegalArgumentException("rocket cannot be null");
103 throw new IllegalArgumentException("status cannot be null");
105 throw new IllegalArgumentException("name cannot be null");
106 if (conditions == null)
107 throw new IllegalArgumentException("conditions cannot be null");
109 this.rocket = rocket;
111 if (status == Status.UPTODATE) {
112 this.status = Status.LOADED;
113 } else if (data == null) {
114 this.status = Status.NOT_SIMULATED;
116 this.status = status;
121 this.conditions = conditions;
122 conditions.addChangeListener(new ConditionListener());
124 if (listeners != null) {
125 this.simulationListeners.addAll(listeners);
129 if (data != null && this.status != Status.NOT_SIMULATED) {
130 simulatedData = data;
131 if (this.status == Status.LOADED) {
132 simulatedConditions = conditions.clone();
133 simulatedRocketID = rocket.getModID();
141 * Return the rocket associated with this simulation.
143 * @return the rocket.
145 public Rocket getRocket() {
151 * Return a newly created Configuration for this simulation. The configuration
152 * has the motor ID set and all stages active.
154 * @return a newly created Configuration of the launch conditions.
156 public Configuration getConfiguration() {
157 Configuration c = new Configuration(rocket);
158 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
164 * Returns the simulation conditions attached to this simulation. The conditions
165 * may be modified freely, and the status of the simulation will change to reflect
168 * @return the simulation conditions.
170 public GUISimulationConditions getConditions() {
176 * Get the list of simulation listeners. The returned list is the one used by
177 * this object; changes to it will reflect changes in the simulation.
179 * @return the actual list of simulation listeners.
181 public List<String> getSimulationListeners() {
182 return simulationListeners;
187 * Return the user-defined name of the simulation.
189 * @return the name for the simulation.
191 public String getName() {
196 * Set the user-defined name of the simulation. Setting the name to
197 * null yields an empty name.
199 * @param name the name of the simulation.
201 public void setName(String name) {
202 if (this.name.equals(name))
215 * Returns the status of this simulation. This method examines whether the
216 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
221 public Status getStatus() {
222 if (status == Status.UPTODATE || status == Status.LOADED) {
223 if (rocket.getFunctionalModID() != simulatedRocketID ||
224 !conditions.equals(simulatedConditions))
225 return Status.OUTDATED;
234 public void simulate(SimulationListener... additionalListeners)
235 throws SimulationException {
237 if (this.status == Status.EXTERNAL) {
238 throw new SimulationException("Cannot simulate imported simulation.");
241 AerodynamicCalculator aerodynamicCalculator;
242 SimulationEngine simulator;
243 SimulationStepper stepper;
244 MassCalculator massCalculator;
247 aerodynamicCalculator = aerodynamicCalculatorClass.newInstance();
248 simulator = simulationEngineClass.newInstance();
249 stepper = simulationStepperClass.newInstance();
250 massCalculator = massCalculatorClass.newInstance();
251 } catch (InstantiationException e) {
252 throw new IllegalStateException("Cannot instantiate calculator/simulator.", e);
253 } catch (IllegalAccessException e) {
254 throw new IllegalStateException("Cannot access calc/sim instance?! BUG!", e);
255 } catch (NullPointerException e) {
256 throw new IllegalStateException("Calculator or simulator null", e);
259 SimulationConditions simulationConditions = conditions.toSimulationConditions();
260 for (SimulationListener l : additionalListeners) {
261 simulationConditions.getSimulationListenerList().add(l);
264 for (String className : simulationListeners) {
265 SimulationListener l = null;
267 Class<?> c = Class.forName(className);
268 l = (SimulationListener) c.newInstance();
269 } catch (Exception e) {
270 throw new SimulationListenerException("Could not instantiate listener of " +
271 "class: " + className, e);
273 simulationConditions.getSimulationListenerList().add(l);
277 System.out.println("Simulation: calling simulator");
278 t1 = System.currentTimeMillis();
279 simulatedData = simulator.simulate(simulationConditions);
280 t2 = System.currentTimeMillis();
281 System.out.println("Simulation: returning from simulator, " +
282 "simulation took " + (t2 - t1) + "ms");
284 // Set simulated info after simulation, will not be set in case of exception
285 simulatedConditions = conditions.clone();
286 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
287 simulatedRocketID = rocket.getFunctionalModID();
289 status = Status.UPTODATE;
295 * Return the conditions used in the previous simulation, or <code>null</code>
296 * if this simulation has not been run.
298 * @return the conditions used in the previous simulation, or <code>null</code>.
300 public GUISimulationConditions getSimulatedConditions() {
301 return simulatedConditions;
305 * Return the warnings generated in the previous simulation, or
306 * <code>null</code> if this simulation has not been run. This is the same
307 * warning set as contained in the <code>FlightData</code> object.
309 * @return the warnings during the previous simulation, or <code>null</code>.
310 * @see FlightData#getWarningSet()
312 public WarningSet getSimulatedWarnings() {
313 if (simulatedData == null)
315 return simulatedData.getWarningSet();
320 * Return a string describing the motor configuration of the previous simulation,
321 * or <code>null</code> if this simulation has not been run.
323 * @return a description of the motor configuration of the previous simulation, or
325 * @see Rocket#getMotorConfigurationNameOrDescription(String)
327 public String getSimulatedMotorDescription() {
328 return simulatedMotors;
332 * Return the flight data of the previous simulation, or <code>null</code> if
333 * this simulation has not been run.
335 * @return the flight data of the previous simulation, or <code>null</code>.
337 public FlightData getSimulatedData() {
338 return simulatedData;
344 * Returns a copy of this simulation suitable for cut/copy/paste operations.
345 * This excludes any simulated data.
347 * @return a copy of this simulation and its conditions.
349 @SuppressWarnings("unchecked")
350 public Simulation copy() {
353 Simulation copy = (Simulation) super.clone();
355 copy.status = Status.NOT_SIMULATED;
356 copy.conditions = this.conditions.clone();
357 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
358 copy.listeners = new ArrayList<ChangeListener>();
359 copy.simulatedConditions = null;
360 copy.simulatedMotors = null;
361 copy.simulatedData = null;
362 copy.simulatedRocketID = -1;
367 } catch (CloneNotSupportedException e) {
368 throw new BugException("Clone not supported, BUG", e);
374 * Create a duplicate of this simulation with the specified rocket. The new
375 * simulation is in non-simulated state.
377 * @param newRocket the rocket for the new simulation.
378 * @return a new simulation with the same conditions and properties.
380 @SuppressWarnings("unchecked")
381 public Simulation duplicateSimulation(Rocket newRocket) {
382 Simulation copy = new Simulation(newRocket);
384 copy.name = this.name;
385 copy.conditions.copyFrom(this.conditions);
386 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
387 copy.simulationStepperClass = this.simulationStepperClass;
388 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
396 public void addChangeListener(ChangeListener listener) {
397 listeners.add(listener);
401 public void removeChangeListener(ChangeListener listener) {
402 listeners.remove(listener);
405 protected void fireChangeEvent() {
406 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
407 ChangeEvent e = new ChangeEvent(this);
408 for (ChangeListener l : ls) {
416 private class ConditionListener implements ChangeListener {
418 private Status oldStatus = null;
421 public void stateChanged(ChangeEvent e) {
422 if (getStatus() != oldStatus) {
423 oldStatus = getStatus();