d7afa7c8ba3a33dd257f3d8a221945f533167ae1
[debian/openrocket] / src / net / sf / openrocket / document / Simulation.java
1 package net.sf.openrocket.document;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import javax.swing.event.ChangeEvent;
7 import javax.swing.event.ChangeListener;
8
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;
28
29
30 public class Simulation implements ChangeSource, Cloneable {
31         
32         public static enum Status {
33                 /** Up-to-date */
34                 UPTODATE,
35
36                 /** Loaded from file, status probably up-to-date */
37                 LOADED,
38
39                 /** Data outdated */
40                 OUTDATED,
41
42                 /** Imported external data */
43                 EXTERNAL,
44
45                 /** Not yet simulated */
46                 NOT_SIMULATED
47         }
48         
49         
50         private final Rocket rocket;
51         
52         private String name = "";
53         
54         private Status status = Status.NOT_SIMULATED;
55         
56         /** The conditions to use */
57         // TODO: HIGH: Change to use actual conditions class??
58         private GUISimulationConditions conditions;
59         
60         private ArrayList<String> simulationListeners = new ArrayList<String>();
61         
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;
66         
67
68
69         /** Listeners for this object */
70         private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
71         
72
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;
78         
79         
80         /**
81          * Create a new simulation for the rocket.  The initial motor configuration is
82          * taken from the default rocket configuration.
83          * 
84          * @param rocket        the rocket associated with the simulation.
85          */
86         public Simulation(Rocket rocket) {
87                 this.rocket = rocket;
88                 this.status = Status.NOT_SIMULATED;
89                 
90                 conditions = new GUISimulationConditions(rocket);
91                 conditions.setMotorConfigurationID(
92                                 rocket.getDefaultConfiguration().getMotorConfigurationID());
93                 conditions.addChangeListener(new ConditionListener());
94         }
95         
96         
97         public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
98                         List<String> listeners, FlightData data) {
99                 
100                 if (rocket == null)
101                         throw new IllegalArgumentException("rocket cannot be null");
102                 if (status == null)
103                         throw new IllegalArgumentException("status cannot be null");
104                 if (name == null)
105                         throw new IllegalArgumentException("name cannot be null");
106                 if (conditions == null)
107                         throw new IllegalArgumentException("conditions cannot be null");
108                 
109                 this.rocket = rocket;
110                 
111                 if (status == Status.UPTODATE) {
112                         this.status = Status.LOADED;
113                 } else if (data == null) {
114                         this.status = Status.NOT_SIMULATED;
115                 } else {
116                         this.status = status;
117                 }
118                 
119                 this.name = name;
120                 
121                 this.conditions = conditions;
122                 conditions.addChangeListener(new ConditionListener());
123                 
124                 if (listeners != null) {
125                         this.simulationListeners.addAll(listeners);
126                 }
127                 
128
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();
134                         }
135                 }
136                 
137         }
138         
139         
140         /**
141          * Return the rocket associated with this simulation.
142          * 
143          * @return      the rocket.
144          */
145         public Rocket getRocket() {
146                 return rocket;
147         }
148         
149         
150         /**
151          * Return a newly created Configuration for this simulation.  The configuration
152          * has the motor ID set and all stages active.
153          * 
154          * @return      a newly created Configuration of the launch conditions.
155          */
156         public Configuration getConfiguration() {
157                 Configuration c = new Configuration(rocket);
158                 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
159                 c.setAllStages();
160                 return c;
161         }
162         
163         /**
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
166          * the changes.
167          * 
168          * @return the simulation conditions.
169          */
170         public GUISimulationConditions getConditions() {
171                 return conditions;
172         }
173         
174         
175         /**
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.
178          * 
179          * @return      the actual list of simulation listeners.
180          */
181         public List<String> getSimulationListeners() {
182                 return simulationListeners;
183         }
184         
185         
186         /**
187          * Return the user-defined name of the simulation.
188          * 
189          * @return      the name for the simulation.
190          */
191         public String getName() {
192                 return name;
193         }
194         
195         /**
196          * Set the user-defined name of the simulation.  Setting the name to
197          * null yields an empty name.
198          * 
199          * @param name  the name of the simulation.
200          */
201         public void setName(String name) {
202                 if (this.name.equals(name))
203                         return;
204                 
205                 if (name == null)
206                         this.name = "";
207                 else
208                         this.name = name;
209                 
210                 fireChangeEvent();
211         }
212         
213         
214         /**
215          * Returns the status of this simulation.  This method examines whether the
216          * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
217          * 
218          * @return the status
219          * @see Status
220          */
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;
226                 }
227                 
228                 return status;
229         }
230         
231         
232
233
234         public void simulate(SimulationListener... additionalListeners)
235                                                 throws SimulationException {
236                 
237                 if (this.status == Status.EXTERNAL) {
238                         throw new SimulationException("Cannot simulate imported simulation.");
239                 }
240                 
241                 AerodynamicCalculator aerodynamicCalculator;
242                 SimulationEngine simulator;
243                 SimulationStepper stepper;
244                 MassCalculator massCalculator;
245                 
246                 try {
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);
257                 }
258                 
259                 SimulationConditions simulationConditions = conditions.toSimulationConditions();
260                 for (SimulationListener l : additionalListeners) {
261                         simulationConditions.getSimulationListenerList().add(l);
262                 }
263                 
264                 for (String className : simulationListeners) {
265                         SimulationListener l = null;
266                         try {
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);
272                         }
273                         simulationConditions.getSimulationListenerList().add(l);
274                 }
275                 
276                 long t1, t2;
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");
283                 
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();
288                 
289                 status = Status.UPTODATE;
290                 fireChangeEvent();
291         }
292         
293         
294         /**
295          * Return the conditions used in the previous simulation, or <code>null</code>
296          * if this simulation has not been run.
297          * 
298          * @return      the conditions used in the previous simulation, or <code>null</code>.
299          */
300         public GUISimulationConditions getSimulatedConditions() {
301                 return simulatedConditions;
302         }
303         
304         /**
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.
308          * 
309          * @return      the warnings during the previous simulation, or <code>null</code>.
310          * @see         FlightData#getWarningSet()
311          */
312         public WarningSet getSimulatedWarnings() {
313                 if (simulatedData == null)
314                         return null;
315                 return simulatedData.getWarningSet();
316         }
317         
318         
319         /**
320          * Return a string describing the motor configuration of the previous simulation,
321          * or <code>null</code> if this simulation has not been run.
322          * 
323          * @return      a description of the motor configuration of the previous simulation, or
324          *                      <code>null</code>.
325          * @see         Rocket#getMotorConfigurationNameOrDescription(String)
326          */
327         public String getSimulatedMotorDescription() {
328                 return simulatedMotors;
329         }
330         
331         /**
332          * Return the flight data of the previous simulation, or <code>null</code> if
333          * this simulation has not been run.
334          * 
335          * @return      the flight data of the previous simulation, or <code>null</code>.
336          */
337         public FlightData getSimulatedData() {
338                 return simulatedData;
339         }
340         
341         
342
343         /**
344          * Returns a copy of this simulation suitable for cut/copy/paste operations.  
345          * This excludes any simulated data.
346          *  
347          * @return      a copy of this simulation and its conditions.
348          */
349         @SuppressWarnings("unchecked")
350         public Simulation copy() {
351                 try {
352                         
353                         Simulation copy = (Simulation) super.clone();
354                         
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;
363                         
364                         return copy;
365                         
366
367                 } catch (CloneNotSupportedException e) {
368                         throw new BugException("Clone not supported, BUG", e);
369                 }
370         }
371         
372         
373         /**
374          * Create a duplicate of this simulation with the specified rocket.  The new
375          * simulation is in non-simulated state.
376          * 
377          * @param newRocket             the rocket for the new simulation.
378          * @return                              a new simulation with the same conditions and properties.
379          */
380         @SuppressWarnings("unchecked")
381         public Simulation duplicateSimulation(Rocket newRocket) {
382                 Simulation copy = new Simulation(newRocket);
383                 
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;
389                 
390                 return copy;
391         }
392         
393         
394
395         @Override
396         public void addChangeListener(ChangeListener listener) {
397                 listeners.add(listener);
398         }
399         
400         @Override
401         public void removeChangeListener(ChangeListener listener) {
402                 listeners.remove(listener);
403         }
404         
405         protected void fireChangeEvent() {
406                 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
407                 ChangeEvent e = new ChangeEvent(this);
408                 for (ChangeListener l : ls) {
409                         l.stateChanged(e);
410                 }
411         }
412         
413         
414
415
416         private class ConditionListener implements ChangeListener {
417                 
418                 private Status oldStatus = null;
419                 
420                 @Override
421                 public void stateChanged(ChangeEvent e) {
422                         if (getStatus() != oldStatus) {
423                                 oldStatus = getStatus();
424                                 fireChangeEvent();
425                         }
426                 }
427         }
428 }