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