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