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