DGP - get margins
[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
142         /**
143          * Return a newly created Configuration for this simulation.  The configuration
144          * has the motor ID set and all stages active.
145          * 
146          * @return      a newly created Configuration of the launch conditions.
147          */
148         public Configuration getConfiguration() {
149                 Configuration c = new Configuration(rocket);
150                 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
151                 c.setAllStages();
152                 return c;
153         }
154         
155         /**
156          * Returns the simulation conditions attached to this simulation.  The conditions
157          * may be modified freely, and the status of the simulation will change to reflect
158          * the changes.
159          * 
160          * @return the simulation conditions.
161          */
162         public GUISimulationConditions getConditions() {
163                 return conditions;
164         }
165         
166         
167         /**
168          * Get the list of simulation listeners.  The returned list is the one used by
169          * this object; changes to it will reflect changes in the simulation.
170          * 
171          * @return      the actual list of simulation listeners.
172          */
173         public List<String> getSimulationListeners() {
174                 return simulationListeners;
175         }
176         
177         
178         /**
179          * Return the user-defined name of the simulation.
180          * 
181          * @return      the name for the simulation.
182          */
183         public String getName() {
184                 return name;
185         }
186         
187         /**
188          * Set the user-defined name of the simulation.  Setting the name to
189          * null yields an empty name.
190          * 
191          * @param name  the name of the simulation.
192          */
193         public void setName(String name) {
194                 if (this.name.equals(name))
195                         return;
196                 
197                 if (name == null)
198                         this.name = "";
199                 else
200                         this.name = name;
201                 
202                 fireChangeEvent();
203         }
204         
205         
206         /**
207          * Returns the status of this simulation.  This method examines whether the
208          * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
209          * 
210          * @return the status
211          * @see Status
212          */
213         public Status getStatus() {
214                 if (status == Status.UPTODATE || status == Status.LOADED) {
215                         if (rocket.getFunctionalModID() != simulatedRocketID ||
216                                         !conditions.equals(simulatedConditions))
217                                 return Status.OUTDATED;
218                 }
219                 
220                 return status;
221         }
222         
223         
224
225
226         public void simulate(SimulationListener... additionalListeners)
227                                                 throws SimulationException {
228                 
229                 if (this.status == Status.EXTERNAL) {
230                         throw new SimulationException("Cannot simulate imported simulation.");
231                 }
232                 
233                 AerodynamicCalculator aerodynamicCalculator;
234                 SimulationEngine simulator;
235                 SimulationStepper stepper;
236                 MassCalculator massCalculator;
237                 
238                 try {
239                         aerodynamicCalculator = aerodynamicCalculatorClass.newInstance();
240                         simulator = simulationEngineClass.newInstance();
241                         stepper = simulationStepperClass.newInstance();
242                         massCalculator = massCalculatorClass.newInstance();
243                 } catch (InstantiationException e) {
244                         throw new IllegalStateException("Cannot instantiate calculator/simulator.", e);
245                 } catch (IllegalAccessException e) {
246                         throw new IllegalStateException("Cannot access calc/sim instance?! BUG!", e);
247                 } catch (NullPointerException e) {
248                         throw new IllegalStateException("Calculator or simulator null", e);
249                 }
250                 
251                 SimulationConditions simulationConditions = conditions.toSimulationConditions();
252                 for (SimulationListener l : additionalListeners) {
253                         simulationConditions.getSimulationListenerList().add(l);
254                 }
255                 
256                 for (String className : simulationListeners) {
257                         SimulationListener l = null;
258                         try {
259                                 Class<?> c = Class.forName(className);
260                                 l = (SimulationListener) c.newInstance();
261                         } catch (Exception e) {
262                                 throw new SimulationListenerException("Could not instantiate listener of " +
263                                                 "class: " + className, e);
264                         }
265                         simulationConditions.getSimulationListenerList().add(l);
266                 }
267                 
268                 long t1, t2;
269                 System.out.println("Simulation: calling simulator");
270                 t1 = System.currentTimeMillis();
271                 simulatedData = simulator.simulate(simulationConditions);
272                 t2 = System.currentTimeMillis();
273                 System.out.println("Simulation: returning from simulator, " +
274                                 "simulation took " + (t2 - t1) + "ms");
275                 
276                 // Set simulated info after simulation, will not be set in case of exception
277                 simulatedConditions = conditions.clone();
278                 simulatedMotors = getConfiguration().getMotorConfigurationDescription();
279                 simulatedRocketID = rocket.getFunctionalModID();
280                 
281                 status = Status.UPTODATE;
282                 fireChangeEvent();
283         }
284         
285         
286         /**
287          * Return the conditions used in the previous simulation, or <code>null</code>
288          * if this simulation has not been run.
289          * 
290          * @return      the conditions used in the previous simulation, or <code>null</code>.
291          */
292         public GUISimulationConditions getSimulatedConditions() {
293                 return simulatedConditions;
294         }
295         
296         /**
297          * Return the warnings generated in the previous simulation, or
298          * <code>null</code> if this simulation has not been run.  This is the same
299          * warning set as contained in the <code>FlightData</code> object.
300          * 
301          * @return      the warnings during the previous simulation, or <code>null</code>.
302          * @see         FlightData#getWarningSet()
303          */
304         public WarningSet getSimulatedWarnings() {
305                 if (simulatedData == null)
306                         return null;
307                 return simulatedData.getWarningSet();
308         }
309         
310         
311         /**
312          * Return a string describing the motor configuration of the previous simulation,
313          * or <code>null</code> if this simulation has not been run.
314          * 
315          * @return      a description of the motor configuration of the previous simulation, or
316          *                      <code>null</code>.
317          * @see         Rocket#getMotorConfigurationNameOrDescription(String)
318          */
319         public String getSimulatedMotorDescription() {
320                 return simulatedMotors;
321         }
322         
323         /**
324          * Return the flight data of the previous simulation, or <code>null</code> if
325          * this simulation has not been run.
326          * 
327          * @return      the flight data of the previous simulation, or <code>null</code>.
328          */
329         public FlightData getSimulatedData() {
330                 return simulatedData;
331         }
332         
333         
334
335         /**
336          * Returns a copy of this simulation suitable for cut/copy/paste operations.  
337          * This excludes any simulated data.
338          *  
339          * @return      a copy of this simulation and its conditions.
340          */
341         @SuppressWarnings("unchecked")
342         public Simulation copy() {
343                 try {
344                         
345                         Simulation copy = (Simulation) super.clone();
346                         
347                         copy.status = Status.NOT_SIMULATED;
348                         copy.conditions = this.conditions.clone();
349                         copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
350                         copy.listeners = new ArrayList<ChangeListener>();
351                         copy.simulatedConditions = null;
352                         copy.simulatedMotors = null;
353                         copy.simulatedData = null;
354                         copy.simulatedRocketID = -1;
355                         
356                         return copy;
357                         
358
359                 } catch (CloneNotSupportedException e) {
360                         throw new BugException("Clone not supported, BUG", e);
361                 }
362         }
363         
364         
365         /**
366          * Create a duplicate of this simulation with the specified rocket.  The new
367          * simulation is in non-simulated state.
368          * 
369          * @param newRocket             the rocket for the new simulation.
370          * @return                              a new simulation with the same conditions and properties.
371          */
372         @SuppressWarnings("unchecked")
373         public Simulation duplicateSimulation(Rocket newRocket) {
374                 Simulation copy = new Simulation(newRocket);
375                 
376                 copy.name = this.name;
377                 copy.conditions.copyFrom(this.conditions);
378                 copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
379                 copy.simulationStepperClass = this.simulationStepperClass;
380                 copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
381                 
382                 return copy;
383         }
384         
385         
386
387         @Override
388         public void addChangeListener(ChangeListener listener) {
389                 listeners.add(listener);
390         }
391         
392         @Override
393         public void removeChangeListener(ChangeListener listener) {
394                 listeners.remove(listener);
395         }
396         
397         protected void fireChangeEvent() {
398                 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
399                 ChangeEvent e = new ChangeEvent(this);
400                 for (ChangeListener l : ls) {
401                         l.stateChanged(e);
402                 }
403         }
404         
405         
406
407
408         private class ConditionListener implements ChangeListener {
409                 
410                 private Status oldStatus = null;
411                 
412                 @Override
413                 public void stateChanged(ChangeEvent e) {
414                         if (getStatus() != oldStatus) {
415                                 oldStatus = getStatus();
416                                 fireChangeEvent();
417                         }
418                 }
419         }
420 }