74d3ab28ed517b03f9d2163f95b999f47054ffde
[debian/openrocket] / core / src / net / sf / openrocket / document / Simulation.java
1 package net.sf.openrocket.document;
2
3 import java.util.EventListener;
4 import java.util.EventObject;
5 import java.util.List;
6
7 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
8 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
9 import net.sf.openrocket.aerodynamics.WarningSet;
10 import net.sf.openrocket.logging.LogHelper;
11 import net.sf.openrocket.masscalc.BasicMassCalculator;
12 import net.sf.openrocket.masscalc.MassCalculator;
13 import net.sf.openrocket.rocketcomponent.Configuration;
14 import net.sf.openrocket.rocketcomponent.Rocket;
15 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
16 import net.sf.openrocket.simulation.FlightData;
17 import net.sf.openrocket.simulation.RK4SimulationStepper;
18 import net.sf.openrocket.simulation.SimulationConditions;
19 import net.sf.openrocket.simulation.SimulationEngine;
20 import net.sf.openrocket.simulation.SimulationOptions;
21 import net.sf.openrocket.simulation.SimulationStepper;
22 import net.sf.openrocket.simulation.exception.SimulationException;
23 import net.sf.openrocket.simulation.exception.SimulationListenerException;
24 import net.sf.openrocket.simulation.listeners.SimulationListener;
25 import net.sf.openrocket.startup.Application;
26 import net.sf.openrocket.util.ArrayList;
27 import net.sf.openrocket.util.BugException;
28 import net.sf.openrocket.util.ChangeSource;
29 import net.sf.openrocket.util.SafetyMutex;
30 import net.sf.openrocket.util.StateChangeListener;
31
32 /**
33  * A class defining a simulation, its conditions and simulated data.
34  * <p>
35  * This class is not thread-safe and enforces single-threaded access with a
36  * SafetyMutex.
37  * 
38  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
39  */
40 public class Simulation implements ChangeSource, Cloneable {
41         private static final LogHelper log = Application.getLogger();
42         
43         public static enum Status {
44                 /** Up-to-date */
45                 UPTODATE,
46
47                 /** Loaded from file, status probably up-to-date */
48                 LOADED,
49
50                 /** Data outdated */
51                 OUTDATED,
52
53                 /** Imported external data */
54                 EXTERNAL,
55
56                 /** Not yet simulated */
57                 NOT_SIMULATED
58         }
59         
60         private SafetyMutex mutex = SafetyMutex.newInstance();
61         
62         private final Rocket rocket;
63         
64         private String name = "";
65         
66         private Status status = Status.NOT_SIMULATED;
67         
68         /** The conditions to use */
69         // TODO: HIGH: Change to use actual conditions class??
70         private SimulationOptions options;
71         
72         private ArrayList<String> simulationListeners = new ArrayList<String>();
73         
74         private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
75         private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
76         private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
77         private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
78         
79
80
81         /** Listeners for this object */
82         private List<EventListener> listeners = new ArrayList<EventListener>();
83         
84
85         /** The conditions actually used in the previous simulation, or null */
86         private SimulationOptions simulatedConditions = null;
87         private String simulatedMotors = null;
88         private FlightData simulatedData = null;
89         private int simulatedRocketID = -1;
90         
91         
92         /**
93          * Create a new simulation for the rocket.  The initial motor configuration is
94          * taken from the default rocket configuration.
95          * 
96          * @param rocket        the rocket associated with the simulation.
97          */
98         public Simulation(Rocket rocket) {
99                 this.rocket = rocket;
100                 this.status = Status.NOT_SIMULATED;
101                 
102                 options = new SimulationOptions(rocket);
103                 options.setMotorConfigurationID(
104                                 rocket.getDefaultConfiguration().getMotorConfigurationID());
105                 options.addChangeListener(new ConditionListener());
106         }
107         
108         
109         public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
110                         List<String> listeners, FlightData data) {
111                 
112                 if (rocket == null)
113                         throw new IllegalArgumentException("rocket cannot be null");
114                 if (status == null)
115                         throw new IllegalArgumentException("status cannot be null");
116                 if (name == null)
117                         throw new IllegalArgumentException("name cannot be null");
118                 if (options == null)
119                         throw new IllegalArgumentException("options cannot be null");
120                 
121                 this.rocket = rocket;
122                 
123                 if (status == Status.UPTODATE) {
124                         this.status = Status.LOADED;
125                 } else if (data == null) {
126                         this.status = Status.NOT_SIMULATED;
127                 } else {
128                         this.status = status;
129                 }
130                 
131                 this.name = name;
132                 
133                 this.options = options;
134                 options.addChangeListener(new ConditionListener());
135                 
136                 if (listeners != null) {
137                         this.simulationListeners.addAll(listeners);
138                 }
139                 
140
141                 if (data != null && this.status != Status.NOT_SIMULATED) {
142                         simulatedData = data;
143                         if (this.status == Status.LOADED) {
144                                 simulatedConditions = options.clone();
145                                 simulatedRocketID = rocket.getModID();
146                         }
147                 }
148                 
149         }
150         
151         
152         /**
153          * Return the rocket associated with this simulation.
154          * 
155          * @return      the rocket.
156          */
157         public Rocket getRocket() {
158                 mutex.verify();
159                 return rocket;
160         }
161         
162         
163         /**
164          * Return a newly created Configuration for this simulation.  The configuration
165          * has the motor ID set and all stages active.
166          * 
167          * @return      a newly created Configuration of the launch conditions.
168          */
169         public Configuration getConfiguration() {
170                 mutex.verify();
171                 Configuration c = new Configuration(rocket);
172                 c.setMotorConfigurationID(options.getMotorConfigurationID());
173                 c.setAllStages();
174                 return c;
175         }
176         
177         /**
178          * Returns the simulation options attached to this simulation.  The options
179          * may be modified freely, and the status of the simulation will change to reflect
180          * the changes.
181          * 
182          * @return the simulation conditions.
183          */
184         public SimulationOptions getOptions() {
185                 mutex.verify();
186                 return options;
187         }
188         
189         
190         /**
191          * Get the list of simulation listeners.  The returned list is the one used by
192          * this object; changes to it will reflect changes in the simulation.
193          * 
194          * @return      the actual list of simulation listeners.
195          */
196         public List<String> getSimulationListeners() {
197                 mutex.verify();
198                 return simulationListeners;
199         }
200         
201         
202         /**
203          * Return the user-defined name of the simulation.
204          * 
205          * @return      the name for the simulation.
206          */
207         public String getName() {
208                 mutex.verify();
209                 return name;
210         }
211         
212         /**
213          * Set the user-defined name of the simulation.  Setting the name to
214          * null yields an empty name.
215          * 
216          * @param name  the name of the simulation.
217          */
218         public void setName(String name) {
219                 mutex.lock("setName");
220                 try {
221                         if (this.name.equals(name))
222                                 return;
223                         
224                         if (name == null)
225                                 this.name = "";
226                         else
227                                 this.name = name;
228                         
229                         fireChangeEvent();
230                 } finally {
231                         mutex.unlock("setName");
232                 }
233         }
234         
235         
236         /**
237          * Returns the status of this simulation.  This method examines whether the
238          * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
239          * 
240          * @return the status
241          * @see Status
242          */
243         public Status getStatus() {
244                 mutex.verify();
245                 
246                 if (status == Status.UPTODATE || status == Status.LOADED) {
247                         if (rocket.getFunctionalModID() != simulatedRocketID ||
248                                         !options.equals(simulatedConditions))
249                                 return Status.OUTDATED;
250                 }
251                 
252                 return status;
253         }
254         
255         
256
257         /**
258          * Simulate the flight.
259          * 
260          * @param additionalListeners   additional simulation listeners (those defined by the simulation are used in any case)
261          * @throws SimulationException  if a problem occurs during simulation
262          */
263         public void simulate(SimulationListener... additionalListeners)
264                                                 throws SimulationException {
265                 mutex.lock("simulate");
266                 try {
267                         
268                         if (this.status == Status.EXTERNAL) {
269                                 throw new SimulationException("Cannot simulate imported simulation.");
270                         }
271                         
272                         SimulationEngine simulator;
273                         
274                         try {
275                                 simulator = simulationEngineClass.newInstance();
276                         } catch (InstantiationException e) {
277                                 throw new IllegalStateException("Cannot instantiate simulator.", e);
278                         } catch (IllegalAccessException e) {
279                                 throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
280                         }
281                         
282                         SimulationConditions simulationConditions = options.toSimulationConditions();
283                         for (SimulationListener l : additionalListeners) {
284                                 simulationConditions.getSimulationListenerList().add(l);
285                         }
286                         
287                         for (String className : simulationListeners) {
288                                 SimulationListener l = null;
289                                 try {
290                                         Class<?> c = Class.forName(className);
291                                         l = (SimulationListener) c.newInstance();
292                                 } catch (Exception e) {
293                                         throw new SimulationListenerException("Could not instantiate listener of " +
294                                                         "class: " + className, e);
295                                 }
296                                 simulationConditions.getSimulationListenerList().add(l);
297                         }
298                         
299                         long t1, t2;
300                         log.debug("Simulation: calling simulator");
301                         t1 = System.currentTimeMillis();
302                         simulatedData = simulator.simulate(simulationConditions);
303                         t2 = System.currentTimeMillis();
304                         log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
305                         
306                         // Set simulated info after simulation, will not be set in case of exception
307                         simulatedConditions = options.clone();
308                         simulatedMotors = getConfiguration().getMotorConfigurationDescription();
309                         simulatedRocketID = rocket.getFunctionalModID();
310                         
311                         status = Status.UPTODATE;
312                         fireChangeEvent();
313                 } finally {
314                         mutex.unlock("simulate");
315                 }
316         }
317         
318         
319         /**
320          * Return the conditions used in the previous simulation, or <code>null</code>
321          * if this simulation has not been run.
322          * 
323          * @return      the conditions used in the previous simulation, or <code>null</code>.
324          */
325         public SimulationOptions getSimulatedConditions() {
326                 mutex.verify();
327                 return simulatedConditions;
328         }
329         
330         /**
331          * Return the warnings generated in the previous simulation, or
332          * <code>null</code> if this simulation has not been run.  This is the same
333          * warning set as contained in the <code>FlightData</code> object.
334          * 
335          * @return      the warnings during the previous simulation, or <code>null</code>.
336          * @see         FlightData#getWarningSet()
337          */
338         public WarningSet getSimulatedWarnings() {
339                 mutex.verify();
340                 if (simulatedData == null)
341                         return null;
342                 return simulatedData.getWarningSet();
343         }
344         
345         
346         /**
347          * Return a string describing the motor configuration of the previous simulation,
348          * or <code>null</code> if this simulation has not been run.
349          * 
350          * @return      a description of the motor configuration of the previous simulation, or
351          *                      <code>null</code>.
352          * @see         Rocket#getMotorConfigurationNameOrDescription(String)
353          */
354         public String getSimulatedMotorDescription() {
355                 mutex.verify();
356                 return simulatedMotors;
357         }
358         
359         /**
360          * Return the flight data of the previous simulation, or <code>null</code> if
361          * this simulation has not been run.
362          * 
363          * @return      the flight data of the previous simulation, or <code>null</code>.
364          */
365         public FlightData getSimulatedData() {
366                 mutex.verify();
367                 return simulatedData;
368         }
369         
370         
371
372         /**
373          * Returns a copy of this simulation suitable for cut/copy/paste operations.  
374          * The rocket refers to the same instance as the original simulation.
375          * This excludes any simulated data.
376          *  
377          * @return      a copy of this simulation and its conditions.
378          */
379         public Simulation copy() {
380                 mutex.lock("copy");
381                 try {
382                         
383                         Simulation copy = (Simulation) super.clone();
384                         
385                         copy.mutex = SafetyMutex.newInstance();
386                         copy.status = Status.NOT_SIMULATED;
387                         copy.options = this.options.clone();
388                         copy.simulationListeners = this.simulationListeners.clone();
389                         copy.listeners = new ArrayList<EventListener>();
390                         copy.simulatedConditions = null;
391                         copy.simulatedMotors = null;
392                         copy.simulatedData = null;
393                         copy.simulatedRocketID = -1;
394                         
395                         return copy;
396                         
397                 } catch (CloneNotSupportedException e) {
398                         throw new BugException("Clone not supported, BUG", e);
399                 } finally {
400                         mutex.unlock("copy");
401                 }
402         }
403         
404         
405         /**
406          * Create a duplicate of this simulation with the specified rocket.  The new
407          * simulation is in non-simulated state.
408          * 
409          * @param newRocket             the rocket for the new simulation.
410          * @return                              a new simulation with the same conditions and properties.
411          */
412         public Simulation duplicateSimulation(Rocket newRocket) {
413                 mutex.lock("duplicateSimulation");
414                 try {
415                         Simulation copy = new Simulation(newRocket);
416                         
417                         copy.name = this.name;
418                         copy.options.copyFrom(this.options);
419                         copy.simulationListeners = this.simulationListeners.clone();
420                         copy.simulationStepperClass = this.simulationStepperClass;
421                         copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
422                         
423                         return copy;
424                 } finally {
425                         mutex.unlock("duplicateSimulation");
426                 }
427         }
428         
429         
430
431         @Override
432         public void addChangeListener(EventListener listener) {
433                 mutex.verify();
434                 listeners.add(listener);
435         }
436         
437         @Override
438         public void removeChangeListener(EventListener listener) {
439                 mutex.verify();
440                 listeners.remove(listener);
441         }
442         
443         protected void fireChangeEvent() {
444                 EventObject e = new EventObject(this);
445                 // Copy the list before iterating to prevent concurrent modification exceptions.
446                 EventListener[] ls = listeners.toArray(new EventListener[0]);
447                 for (EventListener l : ls) {
448                         if ( l instanceof StateChangeListener ) {
449                                 ((StateChangeListener)l).stateChanged(e);
450                         }
451                 }
452         }
453         
454         
455
456
457         private class ConditionListener implements StateChangeListener {
458                 
459                 private Status oldStatus = null;
460                 
461                 @Override
462                 public void stateChanged(EventObject e) {
463                         if (getStatus() != oldStatus) {
464                                 oldStatus = getStatus();
465                                 fireChangeEvent();
466                         }
467                 }
468         }
469 }