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