updates
[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.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;
22
23
24 public class Simulation implements ChangeSource, Cloneable {
25         
26         public static enum Status {
27                 /** Up-to-date */
28                 UPTODATE,
29                 
30                 /** Loaded from file, status probably up-to-date */
31                 LOADED,
32                 
33                 /** Data outdated */
34                 OUTDATED,
35                 
36                 /** Imported external data */
37                 EXTERNAL,
38                 
39                 /** Not yet simulated */
40                 NOT_SIMULATED
41         }
42         
43
44         private final Rocket rocket;
45         
46         private String name = "";
47
48         private Status status = Status.NOT_SIMULATED;
49         
50         /** The conditions to use */
51         private SimulationConditions conditions;
52         
53         private ArrayList<String> simulationListeners = new ArrayList<String>();
54         
55         private Class<? extends FlightSimulator> simulatorClass = RK4Simulator.class;
56         private Class<? extends AerodynamicCalculator> calculatorClass = BarrowmanCalculator.class;
57
58         
59         
60         /** Listeners for this object */
61         private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
62         
63         
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;
69         
70         
71         /**
72          * Create a new simulation for the rocket.  The initial motor configuration is
73          * taken from the default rocket configuration.
74          * 
75          * @param rocket        the rocket associated with the simulation.
76          */
77         public Simulation(Rocket rocket) {
78                 this.rocket = rocket;
79                 this.status = Status.NOT_SIMULATED;
80                 
81                 conditions = new SimulationConditions(rocket);
82                 conditions.setMotorConfigurationID(
83                                 rocket.getDefaultConfiguration().getMotorConfigurationID());
84                 conditions.addChangeListener(new ConditionListener());
85         }
86         
87         
88         public Simulation(Rocket rocket, Status status, String name, SimulationConditions conditions,
89                         List<String> listeners, FlightData data) {
90                 
91                 if (rocket == null) 
92                         throw new IllegalArgumentException("rocket cannot be null");
93                 if (status == null) 
94                         throw new IllegalArgumentException("status cannot be null");
95                 if (name == null) 
96                         throw new IllegalArgumentException("name cannot be null");
97                 if (conditions == null) 
98                         throw new IllegalArgumentException("conditions cannot be null");
99                 
100                 this.rocket = rocket;
101                 
102                 if (status == Status.UPTODATE) {
103                         this.status = Status.LOADED;
104                 } else if (data == null) {
105                         this.status = Status.NOT_SIMULATED;
106                 } else {
107                         this.status = status;
108                 }
109                 
110                 this.name = name;
111                 
112                 this.conditions = conditions;
113                 conditions.addChangeListener(new ConditionListener());
114                 
115                 if (listeners != null) {
116                         this.simulationListeners.addAll(listeners);
117                 }
118                 
119                 
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();
125                         }
126                 }
127                 
128         }
129         
130         
131         
132
133         /**
134          * Return a newly created Configuration for this simulation.  The configuration
135          * has the motor ID set and all stages active.
136          * 
137          * @return      a newly created Configuration of the launch conditions.
138          */
139         public Configuration getConfiguration() {
140                 Configuration c = new Configuration(rocket);
141                 c.setMotorConfigurationID(conditions.getMotorConfigurationID());
142                 c.setAllStages();
143                 return c;
144         }
145         
146         /**
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
149          * the changes.
150          * 
151          * @return the simulation conditions.
152          */
153         public SimulationConditions getConditions() {
154                 return conditions;
155         }
156
157         
158         /**
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.
161          * 
162          * @return      the actual list of simulation listeners.
163          */
164         public List<String> getSimulationListeners() {
165                 return simulationListeners;
166         }
167         
168         
169         /**
170          * Return the user-defined name of the simulation.
171          * 
172          * @return      the name for the simulation.
173          */
174         public String getName() {
175                 return name;
176         }
177         
178         /**
179          * Set the user-defined name of the simulation.  Setting the name to
180          * null yields an empty name.
181          * 
182          * @param name  the name of the simulation.
183          */
184         public void setName(String name) {
185                 if (this.name.equals(name))
186                         return;
187                 
188                 if (name == null)
189                         this.name = "";
190                 else
191                         this.name = name;
192                 
193                 fireChangeEvent();
194         }
195
196
197         /**
198          * Returns the status of this simulation.  This method examines whether the
199          * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
200          * 
201          * @return the status
202          * @see Status
203          */
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;
209                 }
210                 
211                 return status;
212         }
213
214         
215         
216         
217         public void simulate(SimulationListener ... additionalListeners) 
218                                                 throws SimulationException {
219                 
220                 if (this.status == Status.EXTERNAL) {
221                         throw new SimulationException("Cannot simulate imported simulation.");
222                 }
223                 Configuration configuration;
224                 AerodynamicCalculator calculator;
225                 FlightSimulator simulator;
226         
227                 try {
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);
236                 }
237
238                 configuration = this.getConfiguration();
239                 calculator.setConfiguration(configuration);
240                 simulator.setCalculator(calculator);
241                 
242                 for (SimulationListener l: additionalListeners) {
243                         simulator.addSimulationListener(l);
244                 }
245                 
246                 for (String className: simulationListeners) {
247                         SimulationListener l = null;
248                         try {
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);
254                         }
255                         simulator.addSimulationListener(l);
256                 }
257                 
258                 long t1, t2;
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");
265                 
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();
270
271                 status = Status.UPTODATE;
272                 fireChangeEvent();
273         }
274
275         
276         /**
277          * Return the conditions used in the previous simulation, or <code>null</code>
278          * if this simulation has not been run.
279          * 
280          * @return      the conditions used in the previous simulation, or <code>null</code>.
281          */
282         public SimulationConditions getSimulatedConditions() {
283                 return simulatedConditions;
284         }
285         
286         /**
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.
290          * 
291          * @return      the warnings during the previous simulation, or <code>null</code>.
292          * @see         FlightData#getWarningSet()
293          */
294         public WarningSet getSimulatedWarnings() {
295                 if (simulatedData == null)
296                         return null;
297                 return simulatedData.getWarningSet();
298         }
299         
300         
301         /**
302          * Return a string describing the motor configuration of the previous simulation,
303          * or <code>null</code> if this simulation has not been run.
304          * 
305          * @return      a description of the motor configuration of the previous simulation, or
306          *                      <code>null</code>.
307          * @see         Rocket#getMotorConfigurationNameOrDescription(String)
308          */
309         public String getSimulatedMotorDescription() {
310                 return simulatedMotors;
311         }
312         
313         /**
314          * Return the flight data of the previous simulation, or <code>null</code> if
315          * this simulation has not been run.
316          * 
317          * @return      the flight data of the previous simulation, or <code>null</code>.
318          */
319         public FlightData getSimulatedData() {
320                 return simulatedData;
321         }
322         
323         
324         
325         /**
326          * Returns a copy of this simulation suitable for cut/copy/paste operations.  
327          * This excludes any simulated data.
328          *  
329          * @return      a copy of this simulation and its conditions.
330          */
331         @SuppressWarnings("unchecked")
332         public Simulation copy() {
333                 try {
334
335                         Simulation copy = (Simulation)super.clone();
336
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;
345
346                         return copy;
347
348                 
349                 } catch (CloneNotSupportedException e) {
350                         throw new RuntimeException("Clone not supported, BUG", e);
351                 }
352         }
353         
354         
355         /**
356          * Create a duplicate of this simulation with the specified rocket.  The new
357          * simulation is in non-simulated state.
358          * 
359          * @param newRocket             the rocket for the new simulation.
360          * @return                              a new simulation with the same conditions and properties.
361          */
362         @SuppressWarnings("unchecked")
363         public Simulation duplicateSimulation(Rocket newRocket) {
364                 Simulation copy = new Simulation(newRocket);
365                 
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;
371
372                 return copy;
373         }
374         
375         
376
377         @Override
378         public void addChangeListener(ChangeListener listener) {
379                 listeners.add(listener);
380         }
381
382         @Override
383         public void removeChangeListener(ChangeListener listener) {
384                 listeners.remove(listener);
385         }
386         
387         protected void fireChangeEvent() {
388                 ChangeListener[] ls = listeners.toArray(new ChangeListener[0]);
389                 ChangeEvent e = new ChangeEvent(this);
390                 for (ChangeListener l: ls) {
391                         l.stateChanged(e);
392                 }
393         }
394         
395
396
397         
398         private class ConditionListener implements ChangeListener {
399
400                 private Status oldStatus = null;
401                 
402                 @Override
403                 public void stateChanged(ChangeEvent e) {
404                         if (getStatus() != oldStatus) {
405                                 oldStatus = getStatus();
406                                 fireChangeEvent();
407                         }
408                 }
409         }
410 }