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.rocketcomponent.Configuration;
13 import net.sf.openrocket.rocketcomponent.Rocket;
14 import net.sf.openrocket.simulation.FlightData;
15 import net.sf.openrocket.simulation.FlightSimulator;
16 import net.sf.openrocket.simulation.RK4Simulator;
17 import net.sf.openrocket.simulation.SimulationConditions;
18 import net.sf.openrocket.simulation.SimulationListener;
19 import net.sf.openrocket.simulation.exception.SimulationException;
20 import net.sf.openrocket.simulation.exception.SimulationListenerException;
21 import net.sf.openrocket.util.ChangeSource;
24 public class Simulation implements ChangeSource, Cloneable {
26 public static enum Status {
30 /** Loaded from file, status probably up-to-date */
36 /** Imported external data */
39 /** Not yet simulated */
44 private final Rocket rocket;
46 private String name = "";
48 private Status status = Status.NOT_SIMULATED;
50 /** The conditions to use */
51 private SimulationConditions conditions;
53 private ArrayList<String> simulationListeners = new ArrayList<String>();
55 private Class<? extends FlightSimulator> simulatorClass = RK4Simulator.class;
56 private Class<? extends AerodynamicCalculator> calculatorClass = BarrowmanCalculator.class;
60 /** Listeners for this object */
61 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
64 /** The conditions actually used in the previous simulation, or null */
65 private SimulationConditions simulatedConditions = null;
66 private String simulatedMotors = null;
67 private FlightData simulatedData = null;
68 private int simulatedRocketID = -1;
72 * Create a new simulation for the rocket. The initial motor configuration is
73 * taken from the default rocket configuration.
75 * @param rocket the rocket associated with the simulation.
77 public Simulation(Rocket rocket) {
79 this.status = Status.NOT_SIMULATED;
81 conditions = new SimulationConditions(rocket);
82 conditions.setMotorConfigurationID(
83 rocket.getDefaultConfiguration().getMotorConfigurationID());
84 conditions.addChangeListener(new ConditionListener());
88 public Simulation(Rocket rocket, Status status, String name, SimulationConditions conditions,
89 List<String> listeners, FlightData data) {
92 throw new IllegalArgumentException("rocket cannot be null");
94 throw new IllegalArgumentException("status cannot be null");
96 throw new IllegalArgumentException("name cannot be null");
97 if (conditions == null)
98 throw new IllegalArgumentException("conditions cannot be null");
100 this.rocket = rocket;
102 if (status == Status.UPTODATE) {
103 this.status = Status.LOADED;
104 } else if (data == null) {
105 this.status = Status.NOT_SIMULATED;
107 this.status = status;
112 this.conditions = conditions;
113 conditions.addChangeListener(new ConditionListener());
115 if (listeners != null) {
116 this.simulationListeners.addAll(listeners);
120 if (data != null && this.status != Status.NOT_SIMULATED) {
121 simulatedData = data;
122 if (this.status == Status.LOADED) {
123 simulatedConditions = conditions.clone();
124 simulatedRocketID = rocket.getModID();
134 * Return a newly created Configuration for this simulation. The configuration
135 * has the motor ID set and all stages active.
137 * @return a newly created Configuration of the launch conditions.
139 public Configuration getConfiguration() {
140 Configuration c = new Configuration(rocket);
141 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
147 * Returns the simulation conditions attached to this simulation. The conditions
148 * may be modified freely, and the status of the simulation will change to reflect
151 * @return the simulation conditions.
153 public SimulationConditions getConditions() {
159 * Get the list of simulation listeners. The returned list is the one used by
160 * this object; changes to it will reflect changes in the simulation.
162 * @return the actual list of simulation listeners.
164 public List<String> getSimulationListeners() {
165 return simulationListeners;
170 * Return the user-defined name of the simulation.
172 * @return the name for the simulation.
174 public String getName() {
179 * Set the user-defined name of the simulation. Setting the name to
180 * null yields an empty name.
182 * @param name the name of the simulation.
184 public void setName(String name) {
185 if (this.name.equals(name))
198 * Returns the status of this simulation. This method examines whether the
199 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
204 public Status getStatus() {
205 if (status == Status.UPTODATE || status == Status.LOADED) {
206 if (rocket.getFunctionalModID() != simulatedRocketID ||
207 !conditions.equals(simulatedConditions))
208 return Status.OUTDATED;
217 public void simulate(SimulationListener ... additionalListeners)
218 throws SimulationException {
220 if (this.status == Status.EXTERNAL) {
221 throw new SimulationException("Cannot simulate imported simulation.");
223 Configuration configuration;
224 AerodynamicCalculator calculator;
225 FlightSimulator simulator;
228 calculator = calculatorClass.newInstance();
229 simulator = simulatorClass.newInstance();
230 } catch (InstantiationException e) {
231 throw new IllegalStateException("Cannot instantiate calculator/simulator.",e);
232 } catch (IllegalAccessException e) {
233 throw new IllegalStateException("Cannot access calc/sim instance?! BUG!",e);
234 } catch (NullPointerException e) {
235 throw new IllegalStateException("Calculator or simulator null",e);
238 configuration = this.getConfiguration();
239 calculator.setConfiguration(configuration);
240 simulator.setCalculator(calculator);
242 for (SimulationListener l: additionalListeners) {
243 simulator.addSimulationListener(l);
246 for (String className: simulationListeners) {
247 SimulationListener l = null;
249 Class<?> c = Class.forName(className);
250 l = (SimulationListener)c.newInstance();
251 } catch (Exception e) {
252 throw new SimulationListenerException("Could not instantiate listener of " +
253 "class: " + className);
255 simulator.addSimulationListener(l);
259 System.out.println("Simulation: calling simulator");
260 t1 = System.currentTimeMillis();
261 simulatedData = simulator.simulate(conditions);
262 t2 = System.currentTimeMillis();
263 System.out.println("Simulation: returning from simulator, " +
264 "simulation took "+(t2-t1)+"ms");
266 // Set simulated info after simulation, will not be set in case of exception
267 simulatedConditions = conditions.clone();
268 simulatedMotors = configuration.getMotorConfigurationDescription();
269 simulatedRocketID = rocket.getFunctionalModID();
271 status = Status.UPTODATE;
277 * Return the conditions used in the previous simulation, or <code>null</code>
278 * if this simulation has not been run.
280 * @return the conditions used in the previous simulation, or <code>null</code>.
282 public SimulationConditions getSimulatedConditions() {
283 return simulatedConditions;
287 * Return the warnings generated in the previous simulation, or
288 * <code>null</code> if this simulation has not been run. This is the same
289 * warning set as contained in the <code>FlightData</code> object.
291 * @return the warnings during the previous simulation, or <code>null</code>.
292 * @see FlightData#getWarningSet()
294 public WarningSet getSimulatedWarnings() {
295 if (simulatedData == null)
297 return simulatedData.getWarningSet();
302 * Return a string describing the motor configuration of the previous simulation,
303 * or <code>null</code> if this simulation has not been run.
305 * @return a description of the motor configuration of the previous simulation, or
307 * @see Rocket#getMotorConfigurationNameOrDescription(String)
309 public String getSimulatedMotorDescription() {
310 return simulatedMotors;
314 * Return the flight data of the previous simulation, or <code>null</code> if
315 * this simulation has not been run.
317 * @return the flight data of the previous simulation, or <code>null</code>.
319 public FlightData getSimulatedData() {
320 return simulatedData;
326 * Returns a copy of this simulation suitable for cut/copy/paste operations.
327 * This excludes any simulated data.
329 * @return a copy of this simulation and its conditions.
331 @SuppressWarnings("unchecked")
332 public Simulation copy() {
335 Simulation copy = (Simulation)super.clone();
337 copy.status = Status.NOT_SIMULATED;
338 copy.conditions = this.conditions.clone();
339 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
340 copy.listeners = new ArrayList<ChangeListener>();
341 copy.simulatedConditions = null;
342 copy.simulatedMotors = null;
343 copy.simulatedData = null;
344 copy.simulatedRocketID = -1;
349 } catch (CloneNotSupportedException e) {
350 throw new RuntimeException("Clone not supported, BUG", e);
356 * Create a duplicate of this simulation with the specified rocket. The new
357 * simulation is in non-simulated state.
359 * @param newRocket the rocket for the new simulation.
360 * @return a new simulation with the same conditions and properties.
362 @SuppressWarnings("unchecked")
363 public Simulation duplicateSimulation(Rocket newRocket) {
364 Simulation copy = new Simulation(newRocket);
366 copy.name = this.name;
367 copy.conditions.copyFrom(this.conditions);
368 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
369 copy.simulatorClass = this.simulatorClass;
370 copy.calculatorClass = this.calculatorClass;
378 public void addChangeListener(ChangeListener listener) {
379 listeners.add(listener);
383 public void removeChangeListener(ChangeListener listener) {
384 listeners.remove(listener);
387 protected void fireChangeEvent() {
388 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
389 ChangeEvent e = new ChangeEvent(this);
390 for (ChangeListener l: ls) {
398 private class ConditionListener implements ChangeListener {
400 private Status oldStatus = null;
403 public void stateChanged(ChangeEvent e) {
404 if (getStatus() != oldStatus) {
405 oldStatus = getStatus();