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.BugException;
22 import net.sf.openrocket.util.ChangeSource;
25 public class Simulation implements ChangeSource, Cloneable {
27 public static enum Status {
31 /** Loaded from file, status probably up-to-date */
37 /** Imported external data */
40 /** Not yet simulated */
45 private final Rocket rocket;
47 private String name = "";
49 private Status status = Status.NOT_SIMULATED;
51 /** The conditions to use */
52 private SimulationConditions conditions;
54 private ArrayList<String> simulationListeners = new ArrayList<String>();
56 private Class<? extends FlightSimulator> simulatorClass = RK4Simulator.class;
57 private Class<? extends AerodynamicCalculator> calculatorClass = BarrowmanCalculator.class;
61 /** Listeners for this object */
62 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
65 /** The conditions actually used in the previous simulation, or null */
66 private SimulationConditions simulatedConditions = null;
67 private String simulatedMotors = null;
68 private FlightData simulatedData = null;
69 private int simulatedRocketID = -1;
73 * Create a new simulation for the rocket. The initial motor configuration is
74 * taken from the default rocket configuration.
76 * @param rocket the rocket associated with the simulation.
78 public Simulation(Rocket rocket) {
80 this.status = Status.NOT_SIMULATED;
82 conditions = new SimulationConditions(rocket);
83 conditions.setMotorConfigurationID(
84 rocket.getDefaultConfiguration().getMotorConfigurationID());
85 conditions.addChangeListener(new ConditionListener());
89 public Simulation(Rocket rocket, Status status, String name, SimulationConditions conditions,
90 List<String> listeners, FlightData data) {
93 throw new IllegalArgumentException("rocket cannot be null");
95 throw new IllegalArgumentException("status cannot be null");
97 throw new IllegalArgumentException("name cannot be null");
98 if (conditions == null)
99 throw new IllegalArgumentException("conditions cannot be null");
101 this.rocket = rocket;
103 if (status == Status.UPTODATE) {
104 this.status = Status.LOADED;
105 } else if (data == null) {
106 this.status = Status.NOT_SIMULATED;
108 this.status = status;
113 this.conditions = conditions;
114 conditions.addChangeListener(new ConditionListener());
116 if (listeners != null) {
117 this.simulationListeners.addAll(listeners);
121 if (data != null && this.status != Status.NOT_SIMULATED) {
122 simulatedData = data;
123 if (this.status == Status.LOADED) {
124 simulatedConditions = conditions.clone();
125 simulatedRocketID = rocket.getModID();
135 * Return a newly created Configuration for this simulation. The configuration
136 * has the motor ID set and all stages active.
138 * @return a newly created Configuration of the launch conditions.
140 public Configuration getConfiguration() {
141 Configuration c = new Configuration(rocket);
142 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
148 * Returns the simulation conditions attached to this simulation. The conditions
149 * may be modified freely, and the status of the simulation will change to reflect
152 * @return the simulation conditions.
154 public SimulationConditions getConditions() {
160 * Get the list of simulation listeners. The returned list is the one used by
161 * this object; changes to it will reflect changes in the simulation.
163 * @return the actual list of simulation listeners.
165 public List<String> getSimulationListeners() {
166 return simulationListeners;
171 * Return the user-defined name of the simulation.
173 * @return the name for the simulation.
175 public String getName() {
180 * Set the user-defined name of the simulation. Setting the name to
181 * null yields an empty name.
183 * @param name the name of the simulation.
185 public void setName(String name) {
186 if (this.name.equals(name))
199 * Returns the status of this simulation. This method examines whether the
200 * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
205 public Status getStatus() {
206 if (status == Status.UPTODATE || status == Status.LOADED) {
207 if (rocket.getFunctionalModID() != simulatedRocketID ||
208 !conditions.equals(simulatedConditions))
209 return Status.OUTDATED;
218 public void simulate(SimulationListener ... additionalListeners)
219 throws SimulationException {
221 if (this.status == Status.EXTERNAL) {
222 throw new SimulationException("Cannot simulate imported simulation.");
224 Configuration configuration;
225 AerodynamicCalculator calculator;
226 FlightSimulator simulator;
229 calculator = calculatorClass.newInstance();
230 simulator = simulatorClass.newInstance();
231 } catch (InstantiationException e) {
232 throw new IllegalStateException("Cannot instantiate calculator/simulator.",e);
233 } catch (IllegalAccessException e) {
234 throw new IllegalStateException("Cannot access calc/sim instance?! BUG!",e);
235 } catch (NullPointerException e) {
236 throw new IllegalStateException("Calculator or simulator null",e);
239 configuration = this.getConfiguration();
240 calculator.setConfiguration(configuration);
241 simulator.setCalculator(calculator);
243 for (SimulationListener l: additionalListeners) {
244 simulator.addSimulationListener(l);
247 for (String className: simulationListeners) {
248 SimulationListener l = null;
250 Class<?> c = Class.forName(className);
251 l = (SimulationListener)c.newInstance();
252 } catch (Exception e) {
253 throw new SimulationListenerException("Could not instantiate listener of " +
254 "class: " + className, e);
256 simulator.addSimulationListener(l);
260 System.out.println("Simulation: calling simulator");
261 t1 = System.currentTimeMillis();
262 simulatedData = simulator.simulate(conditions);
263 t2 = System.currentTimeMillis();
264 System.out.println("Simulation: returning from simulator, " +
265 "simulation took "+(t2-t1)+"ms");
267 // Set simulated info after simulation, will not be set in case of exception
268 simulatedConditions = conditions.clone();
269 simulatedMotors = configuration.getMotorConfigurationDescription();
270 simulatedRocketID = rocket.getFunctionalModID();
272 status = Status.UPTODATE;
278 * Return the conditions used in the previous simulation, or <code>null</code>
279 * if this simulation has not been run.
281 * @return the conditions used in the previous simulation, or <code>null</code>.
283 public SimulationConditions getSimulatedConditions() {
284 return simulatedConditions;
288 * Return the warnings generated in the previous simulation, or
289 * <code>null</code> if this simulation has not been run. This is the same
290 * warning set as contained in the <code>FlightData</code> object.
292 * @return the warnings during the previous simulation, or <code>null</code>.
293 * @see FlightData#getWarningSet()
295 public WarningSet getSimulatedWarnings() {
296 if (simulatedData == null)
298 return simulatedData.getWarningSet();
303 * Return a string describing the motor configuration of the previous simulation,
304 * or <code>null</code> if this simulation has not been run.
306 * @return a description of the motor configuration of the previous simulation, or
308 * @see Rocket#getMotorConfigurationNameOrDescription(String)
310 public String getSimulatedMotorDescription() {
311 return simulatedMotors;
315 * Return the flight data of the previous simulation, or <code>null</code> if
316 * this simulation has not been run.
318 * @return the flight data of the previous simulation, or <code>null</code>.
320 public FlightData getSimulatedData() {
321 return simulatedData;
327 * Returns a copy of this simulation suitable for cut/copy/paste operations.
328 * This excludes any simulated data.
330 * @return a copy of this simulation and its conditions.
332 @SuppressWarnings("unchecked")
333 public Simulation copy() {
336 Simulation copy = (Simulation)super.clone();
338 copy.status = Status.NOT_SIMULATED;
339 copy.conditions = this.conditions.clone();
340 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
341 copy.listeners = new ArrayList<ChangeListener>();
342 copy.simulatedConditions = null;
343 copy.simulatedMotors = null;
344 copy.simulatedData = null;
345 copy.simulatedRocketID = -1;
350 } catch (CloneNotSupportedException e) {
351 throw new BugException("Clone not supported, BUG", e);
357 * Create a duplicate of this simulation with the specified rocket. The new
358 * simulation is in non-simulated state.
360 * @param newRocket the rocket for the new simulation.
361 * @return a new simulation with the same conditions and properties.
363 @SuppressWarnings("unchecked")
364 public Simulation duplicateSimulation(Rocket newRocket) {
365 Simulation copy = new Simulation(newRocket);
367 copy.name = this.name;
368 copy.conditions.copyFrom(this.conditions);
369 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
370 copy.simulatorClass = this.simulatorClass;
371 copy.calculatorClass = this.calculatorClass;
379 public void addChangeListener(ChangeListener listener) {
380 listeners.add(listener);
384 public void removeChangeListener(ChangeListener listener) {
385 listeners.remove(listener);
388 protected void fireChangeEvent() {
389 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
390 ChangeEvent e = new ChangeEvent(this);
391 for (ChangeListener l: ls) {
399 private class ConditionListener implements ChangeListener {
401 private Status oldStatus = null;
404 public void stateChanged(ChangeEvent e) {
405 if (getStatus() != oldStatus) {
406 oldStatus = getStatus();