- Implemented a DampingMoment simulation listener example
[debian/openrocket] / core / src / net / sf / openrocket / file / openrocket / importt / OpenRocketLoader.java
1 package net.sf.openrocket.file.openrocket.importt;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.lang.reflect.Constructor;
6 import java.lang.reflect.InvocationTargetException;
7 import java.util.ArrayList;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 import net.sf.openrocket.aerodynamics.Warning;
15 import net.sf.openrocket.aerodynamics.WarningSet;
16 import net.sf.openrocket.database.Databases;
17 import net.sf.openrocket.document.OpenRocketDocument;
18 import net.sf.openrocket.document.Simulation;
19 import net.sf.openrocket.document.Simulation.Status;
20 import net.sf.openrocket.document.StorageOptions;
21 import net.sf.openrocket.file.AbstractRocketLoader;
22 import net.sf.openrocket.file.MotorFinder;
23 import net.sf.openrocket.file.RocketLoadException;
24 import net.sf.openrocket.file.simplesax.AbstractElementHandler;
25 import net.sf.openrocket.file.simplesax.ElementHandler;
26 import net.sf.openrocket.file.simplesax.PlainTextHandler;
27 import net.sf.openrocket.file.simplesax.SimpleSAX;
28 import net.sf.openrocket.logging.LogHelper;
29 import net.sf.openrocket.material.Material;
30 import net.sf.openrocket.motor.Motor;
31 import net.sf.openrocket.preset.ComponentPreset;
32 import net.sf.openrocket.rocketcomponent.BodyComponent;
33 import net.sf.openrocket.rocketcomponent.BodyTube;
34 import net.sf.openrocket.rocketcomponent.Bulkhead;
35 import net.sf.openrocket.rocketcomponent.CenteringRing;
36 import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
37 import net.sf.openrocket.rocketcomponent.Clusterable;
38 import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
39 import net.sf.openrocket.rocketcomponent.EngineBlock;
40 import net.sf.openrocket.rocketcomponent.ExternalComponent;
41 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
42 import net.sf.openrocket.rocketcomponent.FinSet;
43 import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
44 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
45 import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
46 import net.sf.openrocket.rocketcomponent.InnerTube;
47 import net.sf.openrocket.rocketcomponent.InternalComponent;
48 import net.sf.openrocket.rocketcomponent.LaunchLug;
49 import net.sf.openrocket.rocketcomponent.MassComponent;
50 import net.sf.openrocket.rocketcomponent.MassObject;
51 import net.sf.openrocket.rocketcomponent.MotorMount;
52 import net.sf.openrocket.rocketcomponent.NoseCone;
53 import net.sf.openrocket.rocketcomponent.Parachute;
54 import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
55 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
56 import net.sf.openrocket.rocketcomponent.ReferenceType;
57 import net.sf.openrocket.rocketcomponent.RingComponent;
58 import net.sf.openrocket.rocketcomponent.Rocket;
59 import net.sf.openrocket.rocketcomponent.RocketComponent;
60 import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
61 import net.sf.openrocket.rocketcomponent.ShockCord;
62 import net.sf.openrocket.rocketcomponent.Stage;
63 import net.sf.openrocket.rocketcomponent.Streamer;
64 import net.sf.openrocket.rocketcomponent.StructuralComponent;
65 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
66 import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
67 import net.sf.openrocket.rocketcomponent.Transition;
68 import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
69 import net.sf.openrocket.rocketcomponent.TubeCoupler;
70 import net.sf.openrocket.simulation.FlightData;
71 import net.sf.openrocket.simulation.FlightDataBranch;
72 import net.sf.openrocket.simulation.FlightDataType;
73 import net.sf.openrocket.simulation.FlightEvent;
74 import net.sf.openrocket.simulation.FlightEvent.Type;
75 import net.sf.openrocket.simulation.SimulationOptions;
76 import net.sf.openrocket.simulation.customexpression.CustomExpression;
77 import net.sf.openrocket.startup.Application;
78 import net.sf.openrocket.unit.UnitGroup;
79 import net.sf.openrocket.util.BugException;
80 import net.sf.openrocket.util.Color;
81 import net.sf.openrocket.util.Coordinate;
82 import net.sf.openrocket.util.GeodeticComputationStrategy;
83 import net.sf.openrocket.util.LineStyle;
84 import net.sf.openrocket.util.Reflection;
85
86 import org.xml.sax.InputSource;
87 import org.xml.sax.SAXException;
88
89
90 /**
91  * Class that loads a rocket definition from an OpenRocket rocket file.
92  * <p>
93  * This class uses SAX to read the XML file format.  The 
94  * #loadFromStream(InputStream) method simply sets the system up and 
95  * starts the parsing, while the actual logic is in the private inner class
96  * <code>OpenRocketHandler</code>.
97  * 
98  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
99  */
100 public class OpenRocketLoader extends AbstractRocketLoader {
101         private static final LogHelper log = Application.getLogger();
102         
103         
104         @Override
105         public OpenRocketDocument loadFromStream(InputStream source, MotorFinder motorFinder) throws RocketLoadException,
106                         IOException {
107                 log.info("Loading .ork file");
108                 DocumentLoadingContext context = new DocumentLoadingContext();
109                 context.setMotorFinder(motorFinder);
110                 
111                 InputSource xmlSource = new InputSource(source);
112                 OpenRocketHandler handler = new OpenRocketHandler(context);
113                 
114                 
115                 try {
116                         SimpleSAX.readXML(xmlSource, handler, warnings);
117                 } catch (SAXException e) {
118                         log.warn("Malformed XML in input");
119                         throw new RocketLoadException("Malformed XML in input.", e);
120                 }
121                 
122                 
123                 OpenRocketDocument doc = handler.getDocument();
124                 doc.getDefaultConfiguration().setAllStages();
125                 
126                 // Deduce suitable time skip
127                 double timeSkip = StorageOptions.SIMULATION_DATA_NONE;
128                 for (Simulation s : doc.getSimulations()) {
129                         if (s.getStatus() == Simulation.Status.EXTERNAL ||
130                                         s.getStatus() == Simulation.Status.NOT_SIMULATED)
131                                 continue;
132                         if (s.getSimulatedData() == null)
133                                 continue;
134                         if (s.getSimulatedData().getBranchCount() == 0)
135                                 continue;
136                         FlightDataBranch branch = s.getSimulatedData().getBranch(0);
137                         if (branch == null)
138                                 continue;
139                         List<Double> list = branch.get(FlightDataType.TYPE_TIME);
140                         if (list == null)
141                                 continue;
142                         
143                         double previousTime = Double.NaN;
144                         for (double time : list) {
145                                 if (time - previousTime < timeSkip)
146                                         timeSkip = time - previousTime;
147                                 previousTime = time;
148                         }
149                 }
150                 // Round value
151                 timeSkip = Math.rint(timeSkip * 100) / 100;
152                 
153                 doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip);
154                 doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed
155                 doc.getDefaultStorageOptions().setExplicitlySet(false);
156                 
157                 doc.clearUndo();
158                 log.info("Loading done");
159                 return doc;
160         }
161         
162 }
163
164
165
166 class DocumentConfig {
167         
168         /* Remember to update OpenRocketSaver as well! */
169         public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5" };
170         
171         /**
172          * Divisor used in converting an integer version to the point-represented version.
173          * The integer version divided by this value is the major version and the remainder is
174          * the minor version.  For example 101 corresponds to file version "1.1".
175          */
176         public static final int FILE_VERSION_DIVISOR = 100;
177         
178         
179         ////////  Component constructors
180         static final HashMap<String, Constructor<? extends RocketComponent>> constructors = new HashMap<String, Constructor<? extends RocketComponent>>();
181         static {
182                 try {
183                         // External components
184                         constructors.put("bodytube", BodyTube.class.getConstructor(new Class<?>[0]));
185                         constructors.put("transition", Transition.class.getConstructor(new Class<?>[0]));
186                         constructors.put("nosecone", NoseCone.class.getConstructor(new Class<?>[0]));
187                         constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
188                         constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
189                         constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
190                         constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
191                         
192                         // Internal components
193                         constructors.put("engineblock", EngineBlock.class.getConstructor(new Class<?>[0]));
194                         constructors.put("innertube", InnerTube.class.getConstructor(new Class<?>[0]));
195                         constructors.put("tubecoupler", TubeCoupler.class.getConstructor(new Class<?>[0]));
196                         constructors.put("bulkhead", Bulkhead.class.getConstructor(new Class<?>[0]));
197                         constructors.put("centeringring", CenteringRing.class.getConstructor(new Class<?>[0]));
198                         
199                         constructors.put("masscomponent", MassComponent.class.getConstructor(new Class<?>[0]));
200                         constructors.put("shockcord", ShockCord.class.getConstructor(new Class<?>[0]));
201                         constructors.put("parachute", Parachute.class.getConstructor(new Class<?>[0]));
202                         constructors.put("streamer", Streamer.class.getConstructor(new Class<?>[0]));
203                         
204                         // Other
205                         constructors.put("stage", Stage.class.getConstructor(new Class<?>[0]));
206                         
207                 } catch (NoSuchMethodException e) {
208                         throw new BugException(
209                                         "Error in constructing the 'constructors' HashMap.");
210                 }
211         }
212         
213         
214         ////////  Parameter setters
215         /*
216          * The keys are of the form Class:param, where Class is the class name and param
217          * the element name.  Setters are searched for in descending class order.
218          * A setter of null means setting the parameter is not allowed.
219          */
220         static final HashMap<String, Setter> setters = new HashMap<String, Setter>();
221         static {
222                 // RocketComponent
223                 setters.put("RocketComponent:name", new StringSetter(
224                                 Reflection.findMethod(RocketComponent.class, "setName", String.class)));
225                 setters.put("RocketComponent:color", new ColorSetter(
226                                 Reflection.findMethod(RocketComponent.class, "setColor", Color.class)));
227                 setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
228                                 Reflection.findMethod(RocketComponent.class, "setLineStyle", LineStyle.class),
229                                 LineStyle.class));
230                 setters.put("RocketComponent:position", new PositionSetter());
231                 setters.put("RocketComponent:overridemass", new OverrideSetter(
232                                 Reflection.findMethod(RocketComponent.class, "setOverrideMass", double.class),
233                                 Reflection.findMethod(RocketComponent.class, "setMassOverridden", boolean.class)));
234                 setters.put("RocketComponent:overridecg", new OverrideSetter(
235                                 Reflection.findMethod(RocketComponent.class, "setOverrideCGX", double.class),
236                                 Reflection.findMethod(RocketComponent.class, "setCGOverridden", boolean.class)));
237                 setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
238                                 Reflection.findMethod(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
239                 setters.put("RocketComponent:comment", new StringSetter(
240                                 Reflection.findMethod(RocketComponent.class, "setComment", String.class)));
241                 setters.put("RocketComponent:preset", new ComponentPresetSetter(
242                                 Reflection.findMethod(RocketComponent.class, "loadPreset", ComponentPreset.class)));
243                 
244                 // ExternalComponent
245                 setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
246                                 Reflection.findMethod(ExternalComponent.class, "setFinish", Finish.class),
247                                 Finish.class));
248                 setters.put("ExternalComponent:material", new MaterialSetter(
249                                 Reflection.findMethod(ExternalComponent.class, "setMaterial", Material.class),
250                                 Material.Type.BULK));
251                 
252                 // BodyComponent
253                 setters.put("BodyComponent:length", new DoubleSetter(
254                                 Reflection.findMethod(BodyComponent.class, "setLength", double.class)));
255                 
256                 // SymmetricComponent
257                 setters.put("SymmetricComponent:thickness", new DoubleSetter(
258                                 Reflection.findMethod(SymmetricComponent.class, "setThickness", double.class),
259                                 "filled",
260                                 Reflection.findMethod(SymmetricComponent.class, "setFilled", boolean.class)));
261                 
262                 // BodyTube
263                 setters.put("BodyTube:radius", new DoubleSetter(
264                                 Reflection.findMethod(BodyTube.class, "setOuterRadius", double.class),
265                                 "auto",
266                                 Reflection.findMethod(BodyTube.class, "setOuterRadiusAutomatic", boolean.class)));
267                 
268                 // Transition
269                 setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
270                                 Reflection.findMethod(Transition.class, "setType", Transition.Shape.class),
271                                 Transition.Shape.class));
272                 setters.put("Transition:shapeclipped", new BooleanSetter(
273                                 Reflection.findMethod(Transition.class, "setClipped", boolean.class)));
274                 setters.put("Transition:shapeparameter", new DoubleSetter(
275                                 Reflection.findMethod(Transition.class, "setShapeParameter", double.class)));
276                 
277                 setters.put("Transition:foreradius", new DoubleSetter(
278                                 Reflection.findMethod(Transition.class, "setForeRadius", double.class),
279                                 "auto",
280                                 Reflection.findMethod(Transition.class, "setForeRadiusAutomatic", boolean.class)));
281                 setters.put("Transition:aftradius", new DoubleSetter(
282                                 Reflection.findMethod(Transition.class, "setAftRadius", double.class),
283                                 "auto",
284                                 Reflection.findMethod(Transition.class, "setAftRadiusAutomatic", boolean.class)));
285                 
286                 setters.put("Transition:foreshoulderradius", new DoubleSetter(
287                                 Reflection.findMethod(Transition.class, "setForeShoulderRadius", double.class)));
288                 setters.put("Transition:foreshoulderlength", new DoubleSetter(
289                                 Reflection.findMethod(Transition.class, "setForeShoulderLength", double.class)));
290                 setters.put("Transition:foreshoulderthickness", new DoubleSetter(
291                                 Reflection.findMethod(Transition.class, "setForeShoulderThickness", double.class)));
292                 setters.put("Transition:foreshouldercapped", new BooleanSetter(
293                                 Reflection.findMethod(Transition.class, "setForeShoulderCapped", boolean.class)));
294                 
295                 setters.put("Transition:aftshoulderradius", new DoubleSetter(
296                                 Reflection.findMethod(Transition.class, "setAftShoulderRadius", double.class)));
297                 setters.put("Transition:aftshoulderlength", new DoubleSetter(
298                                 Reflection.findMethod(Transition.class, "setAftShoulderLength", double.class)));
299                 setters.put("Transition:aftshoulderthickness", new DoubleSetter(
300                                 Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class)));
301                 setters.put("Transition:aftshouldercapped", new BooleanSetter(
302                                 Reflection.findMethod(Transition.class, "setAftShoulderCapped", boolean.class)));
303                 
304                 // NoseCone - disable disallowed elements
305                 setters.put("NoseCone:foreradius", null);
306                 setters.put("NoseCone:foreshoulderradius", null);
307                 setters.put("NoseCone:foreshoulderlength", null);
308                 setters.put("NoseCone:foreshoulderthickness", null);
309                 setters.put("NoseCone:foreshouldercapped", null);
310                 
311                 // FinSet
312                 setters.put("FinSet:fincount", new IntSetter(
313                                 Reflection.findMethod(FinSet.class, "setFinCount", int.class)));
314                 setters.put("FinSet:rotation", new DoubleSetter(
315                                 Reflection.findMethod(FinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
316                 setters.put("FinSet:thickness", new DoubleSetter(
317                                 Reflection.findMethod(FinSet.class, "setThickness", double.class)));
318                 setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
319                                 Reflection.findMethod(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
320                                 FinSet.CrossSection.class));
321                 setters.put("FinSet:cant", new DoubleSetter(
322                                 Reflection.findMethod(FinSet.class, "setCantAngle", double.class), Math.PI / 180.0));
323                 setters.put("FinSet:tabheight", new DoubleSetter(
324                                 Reflection.findMethod(FinSet.class, "setTabHeight", double.class)));
325                 setters.put("FinSet:tablength", new DoubleSetter(
326                                 Reflection.findMethod(FinSet.class, "setTabLength", double.class)));
327                 setters.put("FinSet:tabposition", new FinTabPositionSetter());
328                 
329                 // TrapezoidFinSet
330                 setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
331                                 Reflection.findMethod(TrapezoidFinSet.class, "setRootChord", double.class)));
332                 setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
333                                 Reflection.findMethod(TrapezoidFinSet.class, "setTipChord", double.class)));
334                 setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
335                                 Reflection.findMethod(TrapezoidFinSet.class, "setSweep", double.class)));
336                 setters.put("TrapezoidFinSet:height", new DoubleSetter(
337                                 Reflection.findMethod(TrapezoidFinSet.class, "setHeight", double.class)));
338                 
339                 // EllipticalFinSet
340                 setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
341                                 Reflection.findMethod(EllipticalFinSet.class, "setLength", double.class)));
342                 setters.put("EllipticalFinSet:height", new DoubleSetter(
343                                 Reflection.findMethod(EllipticalFinSet.class, "setHeight", double.class)));
344                 
345                 // FreeformFinSet points handled as a special handler
346                 
347                 // LaunchLug
348                 setters.put("LaunchLug:radius", new DoubleSetter(
349                                 Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
350                 setters.put("LaunchLug:length", new DoubleSetter(
351                                 Reflection.findMethod(LaunchLug.class, "setLength", double.class)));
352                 setters.put("LaunchLug:thickness", new DoubleSetter(
353                                 Reflection.findMethod(LaunchLug.class, "setThickness", double.class)));
354                 setters.put("LaunchLug:radialdirection", new DoubleSetter(
355                                 Reflection.findMethod(LaunchLug.class, "setRadialDirection", double.class),
356                                 Math.PI / 180.0));
357                 
358                 // InternalComponent - nothing
359                 
360                 // StructuralComponent
361                 setters.put("StructuralComponent:material", new MaterialSetter(
362                                 Reflection.findMethod(StructuralComponent.class, "setMaterial", Material.class),
363                                 Material.Type.BULK));
364                 
365                 // RingComponent
366                 setters.put("RingComponent:length", new DoubleSetter(
367                                 Reflection.findMethod(RingComponent.class, "setLength", double.class)));
368                 setters.put("RingComponent:radialposition", new DoubleSetter(
369                                 Reflection.findMethod(RingComponent.class, "setRadialPosition", double.class)));
370                 setters.put("RingComponent:radialdirection", new DoubleSetter(
371                                 Reflection.findMethod(RingComponent.class, "setRadialDirection", double.class),
372                                 Math.PI / 180.0));
373                 
374                 // ThicknessRingComponent - radius on separate components due to differing automatics
375                 setters.put("ThicknessRingComponent:thickness", new DoubleSetter(
376                                 Reflection.findMethod(ThicknessRingComponent.class, "setThickness", double.class)));
377                 
378                 // EngineBlock
379                 setters.put("EngineBlock:outerradius", new DoubleSetter(
380                                 Reflection.findMethod(EngineBlock.class, "setOuterRadius", double.class),
381                                 "auto",
382                                 Reflection.findMethod(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
383                 
384                 // TubeCoupler
385                 setters.put("TubeCoupler:outerradius", new DoubleSetter(
386                                 Reflection.findMethod(TubeCoupler.class, "setOuterRadius", double.class),
387                                 "auto",
388                                 Reflection.findMethod(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
389                 
390                 // InnerTube
391                 setters.put("InnerTube:outerradius", new DoubleSetter(
392                                 Reflection.findMethod(InnerTube.class, "setOuterRadius", double.class)));
393                 setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
394                 setters.put("InnerTube:clusterscale", new DoubleSetter(
395                                 Reflection.findMethod(InnerTube.class, "setClusterScale", double.class)));
396                 setters.put("InnerTube:clusterrotation", new DoubleSetter(
397                                 Reflection.findMethod(InnerTube.class, "setClusterRotation", double.class),
398                                 Math.PI / 180.0));
399                 
400                 // RadiusRingComponent
401                 
402                 // Bulkhead
403                 setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
404                                 Reflection.findMethod(RadiusRingComponent.class, "setInnerRadius", double.class)));
405                 setters.put("Bulkhead:outerradius", new DoubleSetter(
406                                 Reflection.findMethod(Bulkhead.class, "setOuterRadius", double.class),
407                                 "auto",
408                                 Reflection.findMethod(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
409                 
410                 // CenteringRing
411                 setters.put("CenteringRing:innerradius", new DoubleSetter(
412                                 Reflection.findMethod(CenteringRing.class, "setInnerRadius", double.class),
413                                 "auto",
414                                 Reflection.findMethod(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
415                 setters.put("CenteringRing:outerradius", new DoubleSetter(
416                                 Reflection.findMethod(CenteringRing.class, "setOuterRadius", double.class),
417                                 "auto",
418                                 Reflection.findMethod(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
419                 
420                 
421                 // MassObject
422                 setters.put("MassObject:packedlength", new DoubleSetter(
423                                 Reflection.findMethod(MassObject.class, "setLength", double.class)));
424                 setters.put("MassObject:packedradius", new DoubleSetter(
425                                 Reflection.findMethod(MassObject.class, "setRadius", double.class)));
426                 setters.put("MassObject:radialposition", new DoubleSetter(
427                                 Reflection.findMethod(MassObject.class, "setRadialPosition", double.class)));
428                 setters.put("MassObject:radialdirection", new DoubleSetter(
429                                 Reflection.findMethod(MassObject.class, "setRadialDirection", double.class),
430                                 Math.PI / 180.0));
431                 
432                 // MassComponent
433                 setters.put("MassComponent:mass", new DoubleSetter(
434                                 Reflection.findMethod(MassComponent.class, "setComponentMass", double.class)));
435                 
436                 // ShockCord
437                 setters.put("ShockCord:cordlength", new DoubleSetter(
438                                 Reflection.findMethod(ShockCord.class, "setCordLength", double.class)));
439                 setters.put("ShockCord:material", new MaterialSetter(
440                                 Reflection.findMethod(ShockCord.class, "setMaterial", Material.class),
441                                 Material.Type.LINE));
442                 
443                 // RecoveryDevice
444                 setters.put("RecoveryDevice:cd", new DoubleSetter(
445                                 Reflection.findMethod(RecoveryDevice.class, "setCD", double.class),
446                                 "auto",
447                                 Reflection.findMethod(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
448                 setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
449                                 Reflection.findMethod(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
450                                 RecoveryDevice.DeployEvent.class));
451                 setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
452                                 Reflection.findMethod(RecoveryDevice.class, "setDeployAltitude", double.class)));
453                 setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
454                                 Reflection.findMethod(RecoveryDevice.class, "setDeployDelay", double.class)));
455                 setters.put("RecoveryDevice:material", new MaterialSetter(
456                                 Reflection.findMethod(RecoveryDevice.class, "setMaterial", Material.class),
457                                 Material.Type.SURFACE));
458                 
459                 // Parachute
460                 setters.put("Parachute:diameter", new DoubleSetter(
461                                 Reflection.findMethod(Parachute.class, "setDiameter", double.class)));
462                 setters.put("Parachute:linecount", new IntSetter(
463                                 Reflection.findMethod(Parachute.class, "setLineCount", int.class)));
464                 setters.put("Parachute:linelength", new DoubleSetter(
465                                 Reflection.findMethod(Parachute.class, "setLineLength", double.class)));
466                 setters.put("Parachute:linematerial", new MaterialSetter(
467                                 Reflection.findMethod(Parachute.class, "setLineMaterial", Material.class),
468                                 Material.Type.LINE));
469                 
470                 // Streamer
471                 setters.put("Streamer:striplength", new DoubleSetter(
472                                 Reflection.findMethod(Streamer.class, "setStripLength", double.class)));
473                 setters.put("Streamer:stripwidth", new DoubleSetter(
474                                 Reflection.findMethod(Streamer.class, "setStripWidth", double.class)));
475                 
476                 // Rocket
477                 // <motorconfiguration> handled by separate handler
478                 setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
479                                 Reflection.findMethod(Rocket.class, "setReferenceType", ReferenceType.class),
480                                 ReferenceType.class));
481                 setters.put("Rocket:customreference", new DoubleSetter(
482                                 Reflection.findMethod(Rocket.class, "setCustomReferenceLength", double.class)));
483                 setters.put("Rocket:designer", new StringSetter(
484                                 Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
485                 setters.put("Rocket:revision", new StringSetter(
486                                 Reflection.findMethod(Rocket.class, "setRevision", String.class)));
487                 
488                 // Stage
489                 setters.put("Stage:separationevent", new EnumSetter<Stage.SeparationEvent>(
490                                 Reflection.findMethod(Stage.class, "setSeparationEvent", Stage.SeparationEvent.class),
491                                 Stage.SeparationEvent.class));
492                 setters.put("Stage:separationdelay", new DoubleSetter(
493                                 Reflection.findMethod(Stage.class, "setSeparationDelay", double.class)));
494                 
495         }
496         
497         
498         /**
499          * Search for a enum value that has the corresponding name as an XML value.  The current
500          * conversion from enum name to XML value is to lowercase the name and strip out all 
501          * underscore characters.  This method returns a match to these criteria, or <code>null</code>
502          * if no such enum exists.
503          * 
504          * @param <T>                   then enum type.
505          * @param name                  the XML value, null ok.
506          * @param enumClass             the class of the enum.
507          * @return                              the found enum value, or <code>null</code>.
508          */
509         public static <T extends Enum<T>> Enum<T> findEnum(String name,
510                         Class<? extends Enum<T>> enumClass) {
511                 
512                 if (name == null)
513                         return null;
514                 name = name.trim();
515                 for (Enum<T> e : enumClass.getEnumConstants()) {
516                         if (e.name().toLowerCase(Locale.ENGLISH).replace("_", "").equals(name)) {
517                                 return e;
518                         }
519                 }
520                 return null;
521         }
522         
523         
524         /**
525          * Convert a string to a double including formatting specifications of the OpenRocket
526          * file format.  This accepts all formatting that is valid for 
527          * <code>Double.parseDouble(s)</code> and a few others as well ("Inf", "-Inf").
528          * 
529          * @param s             the string to parse.
530          * @return              the numerical value.
531          * @throws NumberFormatException        the the string cannot be parsed.
532          */
533         public static double stringToDouble(String s) throws NumberFormatException {
534                 if (s == null)
535                         throw new NumberFormatException("null string");
536                 if (s.equalsIgnoreCase("NaN"))
537                         return Double.NaN;
538                 if (s.equalsIgnoreCase("Inf"))
539                         return Double.POSITIVE_INFINITY;
540                 if (s.equalsIgnoreCase("-Inf"))
541                         return Double.NEGATIVE_INFINITY;
542                 return Double.parseDouble(s);
543         }
544 }
545
546
547
548
549
550 /**
551  * The starting point of the handlers.  Accepts a single <openrocket> element and hands
552  * the contents to be read by a OpenRocketContentsHandler.
553  */
554 class OpenRocketHandler extends AbstractElementHandler {
555         private final DocumentLoadingContext context;
556         private OpenRocketContentHandler handler = null;
557         
558         public OpenRocketHandler(DocumentLoadingContext context) {
559                 this.context = context;
560         }
561         
562         /**
563          * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
564          * has not been read yet.
565          * 
566          * @return      the document read, or null.
567          */
568         public OpenRocketDocument getDocument() {
569                 return handler.getDocument();
570         }
571         
572         @Override
573         public ElementHandler openElement(String element, HashMap<String, String> attributes,
574                         WarningSet warnings) {
575                 
576                 // Check for unknown elements
577                 if (!element.equals("openrocket")) {
578                         warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
579                         return null;
580                 }
581                 
582                 // Check for first call
583                 if (handler != null) {
584                         warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
585                                         + "ones."));
586                         return null;
587                 }
588                 
589                 // Check version number
590                 String version = null;
591                 String creator = attributes.remove("creator");
592                 String docVersion = attributes.remove("version");
593                 for (String v : DocumentConfig.SUPPORTED_VERSIONS) {
594                         if (v.equals(docVersion)) {
595                                 version = v;
596                                 break;
597                         }
598                 }
599                 if (version == null) {
600                         String str = "Unsupported document version";
601                         if (docVersion != null)
602                                 str += " " + docVersion;
603                         if (creator != null && !creator.trim().equals(""))
604                                 str += " (written using '" + creator.trim() + "')";
605                         str += ", attempting to read file anyway.";
606                         warnings.add(str);
607                 }
608                 
609                 context.setFileVersion(parseVersion(docVersion));
610                 
611                 handler = new OpenRocketContentHandler(context);
612                 return handler;
613         }
614         
615         
616         private int parseVersion(String docVersion) {
617                 if (docVersion == null)
618                         return 0;
619                 
620                 Matcher m = Pattern.compile("^([0-9]+)\\.([0-9]+)$").matcher(docVersion);
621                 if (m.matches()) {
622                         int major = Integer.parseInt(m.group(1));
623                         int minor = Integer.parseInt(m.group(2));
624                         return major * DocumentConfig.FILE_VERSION_DIVISOR + minor;
625                 } else {
626                         return 0;
627                 }
628         }
629         
630         @Override
631         public void closeElement(String element, HashMap<String, String> attributes,
632                         String content, WarningSet warnings) throws SAXException {
633                 attributes.remove("version");
634                 attributes.remove("creator");
635                 super.closeElement(element, attributes, content, warnings);
636         }
637         
638         
639 }
640
641
642 /**
643  * Handles the content of the <openrocket> tag.
644  */
645 class OpenRocketContentHandler extends AbstractElementHandler {
646         private final DocumentLoadingContext context;
647         private final OpenRocketDocument doc;
648         private final Rocket rocket;
649         
650         private boolean rocketDefined = false;
651         private boolean simulationsDefined = false;
652         private boolean datatypesDefined = false;
653         
654         public OpenRocketContentHandler(DocumentLoadingContext context) {
655                 this.context = context;
656                 this.rocket = new Rocket();
657                 this.doc = new OpenRocketDocument(rocket);
658         }
659         
660         public OpenRocketDocument getDocument() {
661                 if (!rocketDefined)
662                         return null;
663                 return doc;
664         }
665         
666         @Override
667         public ElementHandler openElement(String element, HashMap<String, String> attributes,
668                         WarningSet warnings) {
669                 
670                 if (element.equals("rocket")) {
671                         if (rocketDefined) {
672                                 warnings.add(Warning
673                                                 .fromString("Multiple rocket designs within one document, "
674                                                                 + "ignoring later ones."));
675                                 return null;
676                         }
677                         rocketDefined = true;
678                         return new ComponentParameterHandler(rocket, context);
679                 }
680                 
681                 if (element.equals("datatypes")) {
682                         if (datatypesDefined) {
683                                 warnings.add(Warning.fromString("Multiple datatype blocks. Ignoring later ones."));
684                                 return null;
685                         }
686                         datatypesDefined = true;
687                         return new DatatypeHandler(this, context);
688                 }
689                 
690                 if (element.equals("simulations")) {
691                         if (simulationsDefined) {
692                                 warnings.add(Warning
693                                                 .fromString("Multiple simulation definitions within one document, "
694                                                                 + "ignoring later ones."));
695                                 return null;
696                         }
697                         simulationsDefined = true;
698                         return new SimulationsHandler(doc, context);
699                 }
700                 
701                 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
702                 
703                 return null;
704         }
705 }
706
707 class DatatypeHandler extends AbstractElementHandler {
708         private final DocumentLoadingContext context;
709         private final OpenRocketContentHandler contentHandler;
710         private CustomExpressionHandler customExpressionHandler = null;
711         
712         public DatatypeHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
713                 this.context = context;
714                 this.contentHandler = contentHandler;
715         }
716         
717         @Override
718         public ElementHandler openElement(String element,
719                         HashMap<String, String> attributes, WarningSet warnings)
720                         throws SAXException {
721                 
722                 if (element.equals("type") && attributes.get("source").equals("customexpression")) {
723                         customExpressionHandler = new CustomExpressionHandler(contentHandler, context);
724                         return customExpressionHandler;
725                 }
726                 else {
727                         warnings.add(Warning.fromString("Unknown datatype " + element + " defined, ignoring"));
728                 }
729                 
730                 return this;
731         }
732         
733         @Override
734         public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
735                 attributes.remove("source");
736                 super.closeElement(element, attributes, content, warnings);
737                 
738                 if (customExpressionHandler != null) {
739                         contentHandler.getDocument().addCustomExpression(customExpressionHandler.currentExpression);
740                 }
741                 
742         }
743         
744 }
745
746 class CustomExpressionHandler extends AbstractElementHandler {
747         private final DocumentLoadingContext context;
748         private final OpenRocketContentHandler contentHandler;
749         public CustomExpression currentExpression;
750         
751         public CustomExpressionHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
752                 this.context = context;
753                 this.contentHandler = contentHandler;
754                 currentExpression = new CustomExpression(contentHandler.getDocument());
755                 
756         }
757         
758         @Override
759         public ElementHandler openElement(String element,
760                         HashMap<String, String> attributes, WarningSet warnings)
761                         throws SAXException {
762                 
763                 return this;
764         }
765         
766         @Override
767         public void closeElement(String element, HashMap<String, String> attributes,
768                         String content, WarningSet warnings) throws SAXException {
769                 
770                 if (element.equals("type")) {
771                         contentHandler.getDocument().addCustomExpression(currentExpression);
772                 }
773                 
774                 if (element.equals("name")) {
775                         currentExpression.setName(content);
776                 }
777                 
778                 if (element.equals("symbol")) {
779                         currentExpression.setSymbol(content);
780                 }
781                 
782                 if (element.equals("unit") && attributes.get("unittype").equals("auto")) {
783                         currentExpression.setUnit(content);
784                 }
785                 
786                 if (element.equals("expression")) {
787                         currentExpression.setExpression(content);
788                 }
789         }
790 }
791
792 /**
793  * A handler that creates components from the corresponding elements.  The control of the
794  * contents is passed on to ComponentParameterHandler.
795  */
796 class ComponentHandler extends AbstractElementHandler {
797         private final DocumentLoadingContext context;
798         private final RocketComponent parent;
799         
800         public ComponentHandler(RocketComponent parent, DocumentLoadingContext context) {
801                 this.parent = parent;
802                 this.context = context;
803         }
804         
805         @Override
806         public ElementHandler openElement(String element, HashMap<String, String> attributes,
807                         WarningSet warnings) {
808                 
809                 // Attempt to construct new component
810                 Constructor<? extends RocketComponent> constructor = DocumentConfig.constructors
811                                 .get(element);
812                 if (constructor == null) {
813                         warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
814                         return null;
815                 }
816                 
817                 RocketComponent c;
818                 try {
819                         c = constructor.newInstance();
820                 } catch (InstantiationException e) {
821                         throw new BugException("Error constructing component.", e);
822                 } catch (IllegalAccessException e) {
823                         throw new BugException("Error constructing component.", e);
824                 } catch (InvocationTargetException e) {
825                         throw Reflection.handleWrappedException(e);
826                 }
827                 
828                 parent.addChild(c);
829                 
830                 return new ComponentParameterHandler(c, context);
831         }
832 }
833
834
835 /**
836  * A handler that populates the parameters of a previously constructed rocket component.
837  * This uses the setters, or delegates the handling to another handler for specific
838  * elements.
839  */
840 class ComponentParameterHandler extends AbstractElementHandler {
841         private final DocumentLoadingContext context;
842         private final RocketComponent component;
843         
844         public ComponentParameterHandler(RocketComponent c, DocumentLoadingContext context) {
845                 this.component = c;
846                 this.context = context;
847         }
848         
849         @Override
850         public ElementHandler openElement(String element, HashMap<String, String> attributes,
851                         WarningSet warnings) {
852                 
853                 // Check for specific elements that contain other elements
854                 if (element.equals("subcomponents")) {
855                         return new ComponentHandler(component, context);
856                 }
857                 if (element.equals("motormount")) {
858                         if (!(component instanceof MotorMount)) {
859                                 warnings.add(Warning.fromString("Illegal component defined as motor mount."));
860                                 return null;
861                         }
862                         return new MotorMountHandler((MotorMount) component, context);
863                 }
864                 if (element.equals("finpoints")) {
865                         if (!(component instanceof FreeformFinSet)) {
866                                 warnings.add(Warning.fromString("Illegal component defined for fin points."));
867                                 return null;
868                         }
869                         return new FinSetPointHandler((FreeformFinSet) component, context);
870                 }
871                 if (element.equals("motorconfiguration")) {
872                         if (!(component instanceof Rocket)) {
873                                 warnings.add(Warning.fromString("Illegal component defined for motor configuration."));
874                                 return null;
875                         }
876                         return new MotorConfigurationHandler((Rocket) component, context);
877                 }
878                 
879                 
880                 return PlainTextHandler.INSTANCE;
881         }
882         
883         @Override
884         public void closeElement(String element, HashMap<String, String> attributes,
885                         String content, WarningSet warnings) {
886                 
887                 if (element.equals("subcomponents") || element.equals("motormount") ||
888                                 element.equals("finpoints") || element.equals("motorconfiguration")) {
889                         return;
890                 }
891                 
892                 // Search for the correct setter class
893                 
894                 Class<?> c;
895                 for (c = component.getClass(); c != null; c = c.getSuperclass()) {
896                         String setterKey = c.getSimpleName() + ":" + element;
897                         Setter s = DocumentConfig.setters.get(setterKey);
898                         if (s != null) {
899                                 // Setter found
900                                 s.set(component, content, attributes, warnings);
901                                 break;
902                         }
903                         if (DocumentConfig.setters.containsKey(setterKey)) {
904                                 // Key exists but is null -> invalid parameter
905                                 c = null;
906                                 break;
907                         }
908                 }
909                 if (c == null) {
910                         warnings.add(Warning.fromString("Unknown parameter type '" + element + "' for "
911                                         + component.getComponentName() + ", ignoring."));
912                 }
913         }
914 }
915
916
917 /**
918  * A handler that reads the <point> specifications within the freeformfinset's
919  * <finpoints> elements.
920  */
921 class FinSetPointHandler extends AbstractElementHandler {
922         @SuppressWarnings("unused")
923         private final DocumentLoadingContext context;
924         private final FreeformFinSet finset;
925         private final ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
926         
927         public FinSetPointHandler(FreeformFinSet finset, DocumentLoadingContext context) {
928                 this.finset = finset;
929                 this.context = context;
930         }
931         
932         @Override
933         public ElementHandler openElement(String element, HashMap<String, String> attributes,
934                         WarningSet warnings) {
935                 return PlainTextHandler.INSTANCE;
936         }
937         
938         
939         @Override
940         public void closeElement(String element, HashMap<String, String> attributes,
941                         String content, WarningSet warnings) throws SAXException {
942                 
943                 String strx = attributes.remove("x");
944                 String stry = attributes.remove("y");
945                 if (strx == null || stry == null) {
946                         warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
947                         return;
948                 }
949                 try {
950                         double x = Double.parseDouble(strx);
951                         double y = Double.parseDouble(stry);
952                         coordinates.add(new Coordinate(x, y));
953                 } catch (NumberFormatException e) {
954                         warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
955                         return;
956                 }
957                 
958                 super.closeElement(element, attributes, content, warnings);
959         }
960         
961         @Override
962         public void endHandler(String element, HashMap<String, String> attributes,
963                         String content, WarningSet warnings) {
964                 try {
965                         finset.setPoints(coordinates.toArray(new Coordinate[0]));
966                 } catch (IllegalFinPointException e) {
967                         warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
968                 }
969         }
970 }
971
972
973 class MotorMountHandler extends AbstractElementHandler {
974         private final DocumentLoadingContext context;
975         private final MotorMount mount;
976         private MotorHandler motorHandler;
977         
978         public MotorMountHandler(MotorMount mount, DocumentLoadingContext context) {
979                 this.mount = mount;
980                 this.context = context;
981                 mount.setMotorMount(true);
982         }
983         
984         @Override
985         public ElementHandler openElement(String element, HashMap<String, String> attributes,
986                         WarningSet warnings) {
987                 
988                 if (element.equals("motor")) {
989                         motorHandler = new MotorHandler(context);
990                         return motorHandler;
991                 }
992                 
993                 if (element.equals("ignitionevent") ||
994                                 element.equals("ignitiondelay") ||
995                                 element.equals("overhang")) {
996                         return PlainTextHandler.INSTANCE;
997                 }
998                 
999                 warnings.add(Warning.fromString("Unknown element '" + element + "' encountered, ignoring."));
1000                 return null;
1001         }
1002         
1003         
1004         
1005         @Override
1006         public void closeElement(String element, HashMap<String, String> attributes,
1007                         String content, WarningSet warnings) throws SAXException {
1008                 
1009                 if (element.equals("motor")) {
1010                         String id = attributes.get("configid");
1011                         if (id == null || id.equals("")) {
1012                                 warnings.add(Warning.fromString("Illegal motor specification, ignoring."));
1013                                 return;
1014                         }
1015                         
1016                         Motor motor = motorHandler.getMotor(warnings);
1017                         mount.setMotor(id, motor);
1018                         mount.setMotorDelay(id, motorHandler.getDelay(warnings));
1019                         return;
1020                 }
1021                 
1022                 if (element.equals("ignitionevent")) {
1023                         MotorMount.IgnitionEvent event = null;
1024                         for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) {
1025                                 if (e.name().toLowerCase(Locale.ENGLISH).replaceAll("_", "").equals(content)) {
1026                                         event = e;
1027                                         break;
1028                                 }
1029                         }
1030                         if (event == null) {
1031                                 warnings.add(Warning.fromString("Unknown ignition event type '" + content + "', ignoring."));
1032                                 return;
1033                         }
1034                         mount.setIgnitionEvent(event);
1035                         return;
1036                 }
1037                 
1038                 if (element.equals("ignitiondelay")) {
1039                         double d;
1040                         try {
1041                                 d = Double.parseDouble(content);
1042                         } catch (NumberFormatException nfe) {
1043                                 warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
1044                                 return;
1045                         }
1046                         mount.setIgnitionDelay(d);
1047                         return;
1048                 }
1049                 
1050                 if (element.equals("overhang")) {
1051                         double d;
1052                         try {
1053                                 d = Double.parseDouble(content);
1054                         } catch (NumberFormatException nfe) {
1055                                 warnings.add(Warning.fromString("Illegal overhang specified, ignoring."));
1056                                 return;
1057                         }
1058                         mount.setMotorOverhang(d);
1059                         return;
1060                 }
1061                 
1062                 super.closeElement(element, attributes, content, warnings);
1063         }
1064 }
1065
1066
1067
1068
1069 class MotorConfigurationHandler extends AbstractElementHandler {
1070         @SuppressWarnings("unused")
1071         private final DocumentLoadingContext context;
1072         private final Rocket rocket;
1073         private String name = null;
1074         private boolean inNameElement = false;
1075         
1076         public MotorConfigurationHandler(Rocket rocket, DocumentLoadingContext context) {
1077                 this.rocket = rocket;
1078                 this.context = context;
1079         }
1080         
1081         @Override
1082         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1083                         WarningSet warnings) {
1084                 
1085                 if (inNameElement || !element.equals("name")) {
1086                         warnings.add(Warning.FILE_INVALID_PARAMETER);
1087                         return null;
1088                 }
1089                 inNameElement = true;
1090                 
1091                 return PlainTextHandler.INSTANCE;
1092         }
1093         
1094         @Override
1095         public void closeElement(String element, HashMap<String, String> attributes,
1096                         String content, WarningSet warnings) {
1097                 name = content;
1098         }
1099         
1100         @Override
1101         public void endHandler(String element, HashMap<String, String> attributes,
1102                         String content, WarningSet warnings) throws SAXException {
1103                 
1104                 String configid = attributes.remove("configid");
1105                 if (configid == null || configid.equals("")) {
1106                         warnings.add(Warning.FILE_INVALID_PARAMETER);
1107                         return;
1108                 }
1109                 
1110                 if (!rocket.addMotorConfigurationID(configid)) {
1111                         warnings.add("Duplicate motor configuration ID used.");
1112                         return;
1113                 }
1114                 
1115                 if (name != null && name.trim().length() > 0) {
1116                         rocket.setMotorConfigurationName(configid, name);
1117                 }
1118                 
1119                 if ("true".equals(attributes.remove("default"))) {
1120                         rocket.getDefaultConfiguration().setMotorConfigurationID(configid);
1121                 }
1122                 
1123                 super.closeElement(element, attributes, content, warnings);
1124         }
1125 }
1126
1127
1128 class MotorHandler extends AbstractElementHandler {
1129         /** File version where latest digest format was introduced */
1130         private static final int MOTOR_DIGEST_VERSION = 104;
1131         
1132         private final DocumentLoadingContext context;
1133         private Motor.Type type = null;
1134         private String manufacturer = null;
1135         private String designation = null;
1136         private String digest = null;
1137         private double diameter = Double.NaN;
1138         private double length = Double.NaN;
1139         private double delay = Double.NaN;
1140         
1141         public MotorHandler(DocumentLoadingContext context) {
1142                 this.context = context;
1143         }
1144         
1145         
1146         @Override
1147         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1148                         WarningSet warnings) {
1149                 return PlainTextHandler.INSTANCE;
1150         }
1151         
1152         
1153         /**
1154          * Return the motor to use, or null.
1155          */
1156         public Motor getMotor(WarningSet warnings) {
1157                 return context.getMotorFinder().findMotor(type, manufacturer, designation, diameter, length, digest, warnings);
1158         }
1159         
1160         /**
1161          * Return the delay to use for the motor.
1162          */
1163         public double getDelay(WarningSet warnings) {
1164                 if (Double.isNaN(delay)) {
1165                         warnings.add(Warning.fromString("Motor delay not specified, assuming no ejection charge."));
1166                         return Motor.PLUGGED;
1167                 }
1168                 return delay;
1169         }
1170         
1171         
1172         @Override
1173         public void closeElement(String element, HashMap<String, String> attributes,
1174                         String content, WarningSet warnings) throws SAXException {
1175                 
1176                 content = content.trim();
1177                 
1178                 if (element.equals("type")) {
1179                         
1180                         // Motor type
1181                         type = null;
1182                         for (Motor.Type t : Motor.Type.values()) {
1183                                 if (t.name().toLowerCase(Locale.ENGLISH).equals(content.trim())) {
1184                                         type = t;
1185                                         break;
1186                                 }
1187                         }
1188                         if (type == null) {
1189                                 warnings.add(Warning.fromString("Unknown motor type '" + content + "', ignoring."));
1190                         }
1191                         
1192                 } else if (element.equals("manufacturer")) {
1193                         
1194                         // Manufacturer
1195                         manufacturer = content.trim();
1196                         
1197                 } else if (element.equals("designation")) {
1198                         
1199                         // Designation
1200                         designation = content.trim();
1201                         
1202                 } else if (element.equals("digest")) {
1203                         
1204                         // Digest is used only for file versions saved using the same digest algorithm
1205                         if (context.getFileVersion() >= MOTOR_DIGEST_VERSION) {
1206                                 digest = content.trim();
1207                         }
1208                         
1209                 } else if (element.equals("diameter")) {
1210                         
1211                         // Diameter
1212                         diameter = Double.NaN;
1213                         try {
1214                                 diameter = Double.parseDouble(content.trim());
1215                         } catch (NumberFormatException e) {
1216                                 // Ignore
1217                         }
1218                         if (Double.isNaN(diameter)) {
1219                                 warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
1220                         }
1221                         
1222                 } else if (element.equals("length")) {
1223                         
1224                         // Length
1225                         length = Double.NaN;
1226                         try {
1227                                 length = Double.parseDouble(content.trim());
1228                         } catch (NumberFormatException ignore) {
1229                         }
1230                         
1231                         if (Double.isNaN(length)) {
1232                                 warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
1233                         }
1234                         
1235                 } else if (element.equals("delay")) {
1236                         
1237                         // Delay
1238                         delay = Double.NaN;
1239                         if (content.equals("none")) {
1240                                 delay = Motor.PLUGGED;
1241                         } else {
1242                                 try {
1243                                         delay = Double.parseDouble(content.trim());
1244                                 } catch (NumberFormatException ignore) {
1245                                 }
1246                                 
1247                                 if (Double.isNaN(delay)) {
1248                                         warnings.add(Warning.fromString("Illegal motor delay specified, ignoring."));
1249                                 }
1250                                 
1251                         }
1252                         
1253                 } else {
1254                         super.closeElement(element, attributes, content, warnings);
1255                 }
1256         }
1257         
1258 }
1259
1260
1261
1262 class SimulationsHandler extends AbstractElementHandler {
1263         private final DocumentLoadingContext context;
1264         private final OpenRocketDocument doc;
1265         private SingleSimulationHandler handler;
1266         
1267         public SimulationsHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
1268                 this.doc = doc;
1269                 this.context = context;
1270         }
1271         
1272         @Override
1273         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1274                         WarningSet warnings) {
1275                 
1276                 if (!element.equals("simulation")) {
1277                         warnings.add("Unknown element '" + element + "', ignoring.");
1278                         return null;
1279                 }
1280                 
1281                 handler = new SingleSimulationHandler(doc, context);
1282                 return handler;
1283         }
1284         
1285         @Override
1286         public void closeElement(String element, HashMap<String, String> attributes,
1287                         String content, WarningSet warnings) throws SAXException {
1288                 attributes.remove("status");
1289                 
1290                 //Finished loading. Rebuilding custom expressions in case something has changed such as listener variable come available.
1291                 for (CustomExpression exp : doc.getCustomExpressions()){
1292                         exp.setExpression(exp.getExpressionString());
1293                 }
1294                 
1295                 super.closeElement(element, attributes, content, warnings);
1296         }
1297 }
1298
1299 class SingleSimulationHandler extends AbstractElementHandler {
1300         
1301         private final DocumentLoadingContext context;
1302         
1303         private final OpenRocketDocument doc;
1304         
1305         private String name;
1306         
1307         private SimulationConditionsHandler conditionHandler;
1308         private FlightDataHandler dataHandler;
1309         
1310         private final List<String> listeners = new ArrayList<String>();
1311         
1312         public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
1313                 this.doc = doc;
1314                 this.context = context;
1315         }
1316         
1317         public OpenRocketDocument getDocument() {
1318                 return doc;
1319         }
1320         
1321         @Override
1322         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1323                         WarningSet warnings) {
1324                 
1325                 if (element.equals("name") || element.equals("simulator") ||
1326                                 element.equals("calculator") || element.equals("listener")) {
1327                         return PlainTextHandler.INSTANCE;
1328                 } else if (element.equals("conditions")) {
1329                         conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
1330                         return conditionHandler;
1331                 } else if (element.equals("flightdata")) {
1332                         dataHandler = new FlightDataHandler(this, context);
1333                         return dataHandler;
1334                 } else {
1335                         warnings.add("Unknown element '" + element + "', ignoring.");
1336                         return null;
1337                 }
1338         }
1339         
1340         @Override
1341         public void closeElement(String element, HashMap<String, String> attributes,
1342                         String content, WarningSet warnings) {
1343                 
1344                 if (element.equals("name")) {
1345                         name = content;
1346                 } else if (element.equals("simulator")) {
1347                         if (!content.trim().equals("RK4Simulator")) {
1348                                 warnings.add("Unknown simulator '" + content.trim() + "' specified, ignoring.");
1349                         }
1350                 } else if (element.equals("calculator")) {
1351                         if (!content.trim().equals("BarrowmanCalculator")) {
1352                                 warnings.add("Unknown calculator '" + content.trim() + "' specified, ignoring.");
1353                         }
1354                 } else if (element.equals("listener") && content.trim().length() > 0) {
1355                         listeners.add(content.trim());
1356                 }
1357                 
1358         }
1359         
1360         @Override
1361         public void endHandler(String element, HashMap<String, String> attributes,
1362                         String content, WarningSet warnings) {
1363                 
1364                 String s = attributes.get("status");
1365                 Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
1366                 if (status == null) {
1367                         warnings.add("Simulation status unknown, assuming outdated.");
1368                         status = Simulation.Status.OUTDATED;
1369                 }
1370                 
1371                 SimulationOptions conditions;
1372                 if (conditionHandler != null) {
1373                         conditions = conditionHandler.getConditions();
1374                 } else {
1375                         warnings.add("Simulation conditions not defined, using defaults.");
1376                         conditions = new SimulationOptions(doc.getRocket());
1377                 }
1378                 
1379                 if (name == null)
1380                         name = "Simulation";
1381                 
1382                 FlightData data;
1383                 if (dataHandler == null)
1384                         data = null;
1385                 else
1386                         data = dataHandler.getFlightData();
1387                 
1388                 Simulation simulation = new Simulation(doc.getRocket(), status, name,
1389                                 conditions, listeners, data);
1390                 
1391                 doc.addSimulation(simulation);
1392         }
1393 }
1394
1395 class SimulationConditionsHandler extends AbstractElementHandler {
1396         private final DocumentLoadingContext context;
1397         private SimulationOptions conditions;
1398         private AtmosphereHandler atmosphereHandler;
1399         
1400         public SimulationConditionsHandler(Rocket rocket, DocumentLoadingContext context) {
1401                 this.context = context;
1402                 conditions = new SimulationOptions(rocket);
1403                 // Set up default loading settings (which may differ from the new defaults)
1404                 conditions.setGeodeticComputation(GeodeticComputationStrategy.FLAT);
1405         }
1406         
1407         public SimulationOptions getConditions() {
1408                 return conditions;
1409         }
1410         
1411         @Override
1412         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1413                         WarningSet warnings) {
1414                 if (element.equals("atmosphere")) {
1415                         atmosphereHandler = new AtmosphereHandler(attributes.get("model"), context);
1416                         return atmosphereHandler;
1417                 }
1418                 return PlainTextHandler.INSTANCE;
1419         }
1420         
1421         @Override
1422         public void closeElement(String element, HashMap<String, String> attributes,
1423                         String content, WarningSet warnings) {
1424                 
1425                 double d = Double.NaN;
1426                 try {
1427                         d = Double.parseDouble(content);
1428                 } catch (NumberFormatException ignore) {
1429                 }
1430                 
1431                 
1432                 if (element.equals("configid")) {
1433                         if (content.equals("")) {
1434                                 conditions.setMotorConfigurationID(null);
1435                         } else {
1436                                 conditions.setMotorConfigurationID(content);
1437                         }
1438                 } else if (element.equals("launchrodlength")) {
1439                         if (Double.isNaN(d)) {
1440                                 warnings.add("Illegal launch rod length defined, ignoring.");
1441                         } else {
1442                                 conditions.setLaunchRodLength(d);
1443                         }
1444                 } else if (element.equals("launchrodangle")) {
1445                         if (Double.isNaN(d)) {
1446                                 warnings.add("Illegal launch rod angle defined, ignoring.");
1447                         } else {
1448                                 conditions.setLaunchRodAngle(d * Math.PI / 180);
1449                         }
1450                 } else if (element.equals("launchroddirection")) {
1451                         if (Double.isNaN(d)) {
1452                                 warnings.add("Illegal launch rod direction defined, ignoring.");
1453                         } else {
1454                                 conditions.setLaunchRodDirection(d * Math.PI / 180);
1455                         }
1456                 } else if (element.equals("windaverage")) {
1457                         if (Double.isNaN(d)) {
1458                                 warnings.add("Illegal average windspeed defined, ignoring.");
1459                         } else {
1460                                 conditions.setWindSpeedAverage(d);
1461                         }
1462                 } else if (element.equals("windturbulence")) {
1463                         if (Double.isNaN(d)) {
1464                                 warnings.add("Illegal wind turbulence intensity defined, ignoring.");
1465                         } else {
1466                                 conditions.setWindTurbulenceIntensity(d);
1467                         }
1468                 } else if (element.equals("launchaltitude")) {
1469                         if (Double.isNaN(d)) {
1470                                 warnings.add("Illegal launch altitude defined, ignoring.");
1471                         } else {
1472                                 conditions.setLaunchAltitude(d);
1473                         }
1474                 } else if (element.equals("launchlatitude")) {
1475                         if (Double.isNaN(d)) {
1476                                 warnings.add("Illegal launch latitude defined, ignoring.");
1477                         } else {
1478                                 conditions.setLaunchLatitude(d);
1479                         }
1480                 } else if (element.equals("launchlongitude")) {
1481                         if (Double.isNaN(d)) {
1482                                 warnings.add("Illegal launch longitude.");
1483                         } else {
1484                                 conditions.setLaunchLongitude(d);
1485                         }
1486                 } else if (element.equals("geodeticmethod")) {
1487                         GeodeticComputationStrategy gcs =
1488                                         (GeodeticComputationStrategy) DocumentConfig.findEnum(content, GeodeticComputationStrategy.class);
1489                         if (gcs != null) {
1490                                 conditions.setGeodeticComputation(gcs);
1491                         } else {
1492                                 warnings.add("Unknown geodetic computation method '" + content + "'");
1493                         }
1494                 } else if (element.equals("atmosphere")) {
1495                         atmosphereHandler.storeSettings(conditions, warnings);
1496                 } else if (element.equals("timestep")) {
1497                         if (Double.isNaN(d)) {
1498                                 warnings.add("Illegal time step defined, ignoring.");
1499                         } else {
1500                                 conditions.setTimeStep(d);
1501                         }
1502                 }
1503         }
1504 }
1505
1506
1507 class AtmosphereHandler extends AbstractElementHandler {
1508         @SuppressWarnings("unused")
1509         private final DocumentLoadingContext context;
1510         private final String model;
1511         private double temperature = Double.NaN;
1512         private double pressure = Double.NaN;
1513         
1514         public AtmosphereHandler(String model, DocumentLoadingContext context) {
1515                 this.model = model;
1516                 this.context = context;
1517         }
1518         
1519         @Override
1520         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1521                         WarningSet warnings) {
1522                 return PlainTextHandler.INSTANCE;
1523         }
1524         
1525         @Override
1526         public void closeElement(String element, HashMap<String, String> attributes,
1527                         String content, WarningSet warnings) throws SAXException {
1528                 
1529                 double d = Double.NaN;
1530                 try {
1531                         d = Double.parseDouble(content);
1532                 } catch (NumberFormatException ignore) {
1533                 }
1534                 
1535                 if (element.equals("basetemperature")) {
1536                         if (Double.isNaN(d)) {
1537                                 warnings.add("Illegal base temperature specified, ignoring.");
1538                         }
1539                         temperature = d;
1540                 } else if (element.equals("basepressure")) {
1541                         if (Double.isNaN(d)) {
1542                                 warnings.add("Illegal base pressure specified, ignoring.");
1543                         }
1544                         pressure = d;
1545                 } else {
1546                         super.closeElement(element, attributes, content, warnings);
1547                 }
1548         }
1549         
1550         
1551         public void storeSettings(SimulationOptions cond, WarningSet warnings) {
1552                 if (!Double.isNaN(pressure)) {
1553                         cond.setLaunchPressure(pressure);
1554                 }
1555                 if (!Double.isNaN(temperature)) {
1556                         cond.setLaunchTemperature(temperature);
1557                 }
1558                 
1559                 if ("isa".equals(model)) {
1560                         cond.setISAAtmosphere(true);
1561                 } else if ("extendedisa".equals(model)) {
1562                         cond.setISAAtmosphere(false);
1563                 } else {
1564                         cond.setISAAtmosphere(true);
1565                         warnings.add("Unknown atmospheric model, using ISA.");
1566                 }
1567         }
1568         
1569 }
1570
1571
1572 class FlightDataHandler extends AbstractElementHandler {
1573         private final DocumentLoadingContext context;
1574         
1575         private FlightDataBranchHandler dataHandler;
1576         private WarningSet warningSet = new WarningSet();
1577         private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
1578         
1579         private SingleSimulationHandler simHandler;
1580         private FlightData data;
1581         
1582         
1583         public FlightDataHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) {
1584                 this.context = context;
1585                 this.simHandler = simHandler;
1586         }
1587         
1588         public FlightData getFlightData() {
1589                 return data;
1590         }
1591         
1592         @Override
1593         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1594                         WarningSet warnings) {
1595                 
1596                 if (element.equals("warning")) {
1597                         return PlainTextHandler.INSTANCE;
1598                 }
1599                 if (element.equals("databranch")) {
1600                         if (attributes.get("name") == null || attributes.get("types") == null) {
1601                                 warnings.add("Illegal flight data definition, ignoring.");
1602                                 return null;
1603                         }
1604                         dataHandler = new FlightDataBranchHandler(attributes.get("name"),
1605                                         attributes.get("types"),
1606                                         simHandler, context);
1607                         return dataHandler;
1608                 }
1609                 
1610                 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
1611                 return null;
1612         }
1613         
1614         
1615         @Override
1616         public void closeElement(String element, HashMap<String, String> attributes,
1617                         String content, WarningSet warnings) {
1618                 
1619                 if (element.equals("databranch")) {
1620                         FlightDataBranch branch = dataHandler.getBranch();
1621                         if (branch.getLength() > 0) {
1622                                 branches.add(branch);
1623                         }
1624                 } else if (element.equals("warning")) {
1625                         warningSet.add(Warning.fromString(content));
1626                 }
1627         }
1628         
1629         
1630         @Override
1631         public void endHandler(String element, HashMap<String, String> attributes,
1632                         String content, WarningSet warnings) {
1633                 
1634                 if (branches.size() > 0) {
1635                         data = new FlightData(branches.toArray(new FlightDataBranch[0]));
1636                 } else {
1637                         double maxAltitude = Double.NaN;
1638                         double maxVelocity = Double.NaN;
1639                         double maxAcceleration = Double.NaN;
1640                         double maxMach = Double.NaN;
1641                         double timeToApogee = Double.NaN;
1642                         double flightTime = Double.NaN;
1643                         double groundHitVelocity = Double.NaN;
1644                         double launchRodVelocity = Double.NaN;
1645                         double deploymentVelocity = Double.NaN;
1646                         
1647                         try {
1648                                 maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude"));
1649                         } catch (NumberFormatException ignore) {
1650                         }
1651                         try {
1652                                 maxVelocity = DocumentConfig.stringToDouble(attributes.get("maxvelocity"));
1653                         } catch (NumberFormatException ignore) {
1654                         }
1655                         try {
1656                                 maxAcceleration = DocumentConfig.stringToDouble(attributes.get("maxacceleration"));
1657                         } catch (NumberFormatException ignore) {
1658                         }
1659                         try {
1660                                 maxMach = DocumentConfig.stringToDouble(attributes.get("maxmach"));
1661                         } catch (NumberFormatException ignore) {
1662                         }
1663                         try {
1664                                 timeToApogee = DocumentConfig.stringToDouble(attributes.get("timetoapogee"));
1665                         } catch (NumberFormatException ignore) {
1666                         }
1667                         try {
1668                                 flightTime = DocumentConfig.stringToDouble(attributes.get("flighttime"));
1669                         } catch (NumberFormatException ignore) {
1670                         }
1671                         try {
1672                                 groundHitVelocity =
1673                                                 DocumentConfig.stringToDouble(attributes.get("groundhitvelocity"));
1674                         } catch (NumberFormatException ignore) {
1675                         }
1676                         try {
1677                                 launchRodVelocity = DocumentConfig.stringToDouble(attributes.get("launchrodvelocity"));
1678                         } catch (NumberFormatException ignore) {
1679                         }
1680                         try {
1681                                 deploymentVelocity = DocumentConfig.stringToDouble(attributes.get("deploymentvelocity"));
1682                         } catch (NumberFormatException ignore) {
1683                         }
1684                         
1685                         data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
1686                                         timeToApogee, flightTime, groundHitVelocity, launchRodVelocity, deploymentVelocity);
1687                 }
1688                 
1689                 data.getWarningSet().addAll(warningSet);
1690                 data.immute();
1691         }
1692         
1693         
1694 }
1695
1696
1697 class FlightDataBranchHandler extends AbstractElementHandler {
1698         @SuppressWarnings("unused")
1699         private final DocumentLoadingContext context;
1700         private final FlightDataType[] types;
1701         private final FlightDataBranch branch;
1702         
1703         private static final LogHelper log = Application.getLogger();
1704         private final SingleSimulationHandler simHandler;
1705         
1706         public FlightDataBranchHandler(String name, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
1707                 this.simHandler = simHandler;
1708                 this.context = context;
1709                 String[] split = typeList.split(",");
1710                 types = new FlightDataType[split.length];
1711                 for (int i = 0; i < split.length; i++) {
1712                         String typeName = split[i];
1713                         FlightDataType matching = findFlightDataType(typeName);
1714                         types[i] = matching;
1715                         //types[i] = FlightDataType.getType(typeName, matching.getSymbol(), matching.getUnitGroup());
1716                 }
1717                 
1718                 // TODO: LOW: May throw an IllegalArgumentException
1719                 branch = new FlightDataBranch(name, types);
1720         }
1721         
1722         // Find the full flight data type given name only
1723         // Note: this way of doing it requires that custom expressions always come before flight data in the file,
1724         // not the nicest but this is always the case anyway.
1725         private FlightDataType findFlightDataType(String name) {
1726                 
1727                 // Kevins version with lookup by key. Not using right now
1728                 /*
1729                 if ( key != null ) {
1730                         for (FlightDataType t : FlightDataType.ALL_TYPES){
1731                                 if (t.getKey().equals(key) ){
1732                                         return t;
1733                                 }
1734                         }
1735                 }
1736                 */
1737                 
1738                 // Look in built in types
1739                 for (FlightDataType t : FlightDataType.ALL_TYPES) {
1740                         if (t.getName().equals(name)) {
1741                                 return t;
1742                         }
1743                 }
1744                 
1745                 // Look in custom expressions
1746                 for (CustomExpression exp : simHandler.getDocument().getCustomExpressions()) {
1747                         if (exp.getName().equals(name)) {
1748                                 return exp.getType();
1749                         }
1750                 }
1751                 
1752                 log.warn("Could not find the flight data type '" + name + "' used in the XML file. Substituted type with unknown symbol and units.");
1753                 return FlightDataType.getType(name, "Unknown", UnitGroup.UNITS_NONE);
1754         }
1755         
1756         public FlightDataBranch getBranch() {
1757                 branch.immute();
1758                 return branch;
1759         }
1760         
1761         @Override
1762         public ElementHandler openElement(String element, HashMap<String, String> attributes,
1763                         WarningSet warnings) {
1764                 
1765                 if (element.equals("datapoint"))
1766                         return PlainTextHandler.INSTANCE;
1767                 if (element.equals("event"))
1768                         return PlainTextHandler.INSTANCE;
1769                 
1770                 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
1771                 return null;
1772         }
1773         
1774         
1775         @Override
1776         public void closeElement(String element, HashMap<String, String> attributes,
1777                         String content, WarningSet warnings) {
1778                 
1779                 if (element.equals("event")) {
1780                         double time;
1781                         FlightEvent.Type type;
1782                         
1783                         try {
1784                                 time = DocumentConfig.stringToDouble(attributes.get("time"));
1785                         } catch (NumberFormatException e) {
1786                                 warnings.add("Illegal event specification, ignoring.");
1787                                 return;
1788                         }
1789                         
1790                         type = (Type) DocumentConfig.findEnum(attributes.get("type"), FlightEvent.Type.class);
1791                         if (type == null) {
1792                                 warnings.add("Illegal event specification, ignoring.");
1793                                 return;
1794                         }
1795                         
1796                         branch.addEvent(new FlightEvent(type, time));
1797                         return;
1798                 }
1799                 
1800                 if (!element.equals("datapoint")) {
1801                         warnings.add("Unknown element '" + element + "' encountered, ignoring.");
1802                         return;
1803                 }
1804                 
1805                 // element == "datapoint"
1806                 
1807                 
1808                 // Check line format
1809                 String[] split = content.split(",");
1810                 if (split.length != types.length) {
1811                         warnings.add("Data point did not contain correct amount of values, ignoring point.");
1812                         return;
1813                 }
1814                 
1815                 // Parse the doubles
1816                 double[] values = new double[split.length];
1817                 for (int i = 0; i < values.length; i++) {
1818                         try {
1819                                 values[i] = DocumentConfig.stringToDouble(split[i]);
1820                         } catch (NumberFormatException e) {
1821                                 warnings.add("Data point format error, ignoring point.");
1822                                 return;
1823                         }
1824                 }
1825                 
1826                 // Add point to branch
1827                 branch.addPoint();
1828                 for (int i = 0; i < types.length; i++) {
1829                         branch.setValue(types[i], values[i]);
1830                 }
1831         }
1832 }
1833
1834
1835
1836
1837
1838 /////////////////    Setters implementation
1839
1840
1841 ////  Interface
1842 interface Setter {
1843         /**
1844          * Set the specified value to the given component.
1845          * 
1846          * @param component             the component to which to set.
1847          * @param value                 the value within the element.
1848          * @param attributes    attributes for the element.
1849          * @param warnings              the warning set to use.
1850          */
1851         public void set(RocketComponent component, String value,
1852                         HashMap<String, String> attributes, WarningSet warnings);
1853 }
1854
1855
1856 ////  StringSetter - sets the value to the contained String
1857 class StringSetter implements Setter {
1858         private final Reflection.Method setMethod;
1859         
1860         public StringSetter(Reflection.Method set) {
1861                 setMethod = set;
1862         }
1863         
1864         @Override
1865         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1866                         WarningSet warnings) {
1867                 setMethod.invoke(c, s);
1868         }
1869 }
1870
1871 ////  IntSetter - set an integer value
1872 class IntSetter implements Setter {
1873         private final Reflection.Method setMethod;
1874         
1875         public IntSetter(Reflection.Method set) {
1876                 setMethod = set;
1877         }
1878         
1879         @Override
1880         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1881                         WarningSet warnings) {
1882                 try {
1883                         int n = Integer.parseInt(s);
1884                         setMethod.invoke(c, n);
1885                 } catch (NumberFormatException e) {
1886                         warnings.add(Warning.FILE_INVALID_PARAMETER);
1887                 }
1888         }
1889 }
1890
1891
1892 //// BooleanSetter - set a boolean value
1893 class BooleanSetter implements Setter {
1894         private final Reflection.Method setMethod;
1895         
1896         public BooleanSetter(Reflection.Method set) {
1897                 setMethod = set;
1898         }
1899         
1900         @Override
1901         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1902                         WarningSet warnings) {
1903                 
1904                 s = s.trim();
1905                 if (s.equalsIgnoreCase("true")) {
1906                         setMethod.invoke(c, true);
1907                 } else if (s.equalsIgnoreCase("false")) {
1908                         setMethod.invoke(c, false);
1909                 } else {
1910                         warnings.add(Warning.FILE_INVALID_PARAMETER);
1911                 }
1912         }
1913 }
1914
1915
1916
1917 ////  DoubleSetter - sets a double value or (alternatively) if a specific string is encountered
1918 ////  calls a setXXX(boolean) method.
1919 class DoubleSetter implements Setter {
1920         private final Reflection.Method setMethod;
1921         private final String specialString;
1922         private final Reflection.Method specialMethod;
1923         private final double multiplier;
1924         
1925         /**
1926          * Set only the double value.
1927          * @param set   set method for the double value. 
1928          */
1929         public DoubleSetter(Reflection.Method set) {
1930                 this.setMethod = set;
1931                 this.specialString = null;
1932                 this.specialMethod = null;
1933                 this.multiplier = 1.0;
1934         }
1935         
1936         /**
1937          * Multiply with the given multiplier and set the double value.
1938          * @param set   set method for the double value.
1939          * @param mul   multiplier.
1940          */
1941         public DoubleSetter(Reflection.Method set, double mul) {
1942                 this.setMethod = set;
1943                 this.specialString = null;
1944                 this.specialMethod = null;
1945                 this.multiplier = mul;
1946         }
1947         
1948         /**
1949          * Set the double value, or if the value equals the special string, use the
1950          * special setter and set it to true.
1951          * 
1952          * @param set                   double setter.
1953          * @param special               special string
1954          * @param specialMethod boolean setter.
1955          */
1956         public DoubleSetter(Reflection.Method set, String special,
1957                         Reflection.Method specialMethod) {
1958                 this.setMethod = set;
1959                 this.specialString = special;
1960                 this.specialMethod = specialMethod;
1961                 this.multiplier = 1.0;
1962         }
1963         
1964         
1965         @Override
1966         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1967                         WarningSet warnings) {
1968                 
1969                 s = s.trim();
1970                 
1971                 // Check for special case
1972                 if (specialMethod != null && s.equalsIgnoreCase(specialString)) {
1973                         specialMethod.invoke(c, true);
1974                         return;
1975                 }
1976                 
1977                 // Normal case
1978                 try {
1979                         double d = Double.parseDouble(s);
1980                         setMethod.invoke(c, d * multiplier);
1981                 } catch (NumberFormatException e) {
1982                         warnings.add(Warning.FILE_INVALID_PARAMETER);
1983                 }
1984         }
1985 }
1986
1987
1988 class OverrideSetter implements Setter {
1989         private final Reflection.Method setMethod;
1990         private final Reflection.Method enabledMethod;
1991         
1992         public OverrideSetter(Reflection.Method set, Reflection.Method enabledMethod) {
1993                 this.setMethod = set;
1994                 this.enabledMethod = enabledMethod;
1995         }
1996         
1997         @Override
1998         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1999                         WarningSet warnings) {
2000                 
2001                 try {
2002                         double d = Double.parseDouble(s);
2003                         setMethod.invoke(c, d);
2004                         enabledMethod.invoke(c, true);
2005                 } catch (NumberFormatException e) {
2006                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2007                 }
2008         }
2009 }
2010
2011 ////  EnumSetter  -  sets a generic enum type
2012 class EnumSetter<T extends Enum<T>> implements Setter {
2013         private final Reflection.Method setter;
2014         private final Class<T> enumClass;
2015         
2016         public EnumSetter(Reflection.Method set, Class<T> enumClass) {
2017                 this.setter = set;
2018                 this.enumClass = enumClass;
2019         }
2020         
2021         @Override
2022         public void set(RocketComponent c, String name, HashMap<String, String> attributes,
2023                         WarningSet warnings) {
2024                 
2025                 Enum<?> setEnum = DocumentConfig.findEnum(name, enumClass);
2026                 if (setEnum == null) {
2027                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2028                         return;
2029                 }
2030                 
2031                 setter.invoke(c, setEnum);
2032         }
2033 }
2034
2035
2036 ////  ColorSetter  -  sets a Color value
2037 class ColorSetter implements Setter {
2038         private final Reflection.Method setMethod;
2039         
2040         public ColorSetter(Reflection.Method set) {
2041                 setMethod = set;
2042         }
2043         
2044         @Override
2045         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
2046                         WarningSet warnings) {
2047                 
2048                 String red = attributes.get("red");
2049                 String green = attributes.get("green");
2050                 String blue = attributes.get("blue");
2051                 
2052                 if (red == null || green == null || blue == null) {
2053                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2054                         return;
2055                 }
2056                 
2057                 int r, g, b;
2058                 try {
2059                         r = Integer.parseInt(red);
2060                         g = Integer.parseInt(green);
2061                         b = Integer.parseInt(blue);
2062                 } catch (NumberFormatException e) {
2063                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2064                         return;
2065                 }
2066                 
2067                 if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
2068                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2069                         return;
2070                 }
2071                 
2072                 Color color = new Color(r, g, b);
2073                 setMethod.invoke(c, color);
2074                 
2075                 if (!s.trim().equals("")) {
2076                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2077                 }
2078         }
2079 }
2080
2081 ////ComponentPresetSetter  -  sets a ComponentPreset value
2082 class ComponentPresetSetter implements Setter {
2083         private final Reflection.Method setMethod;
2084         
2085         public ComponentPresetSetter(Reflection.Method set) {
2086                 this.setMethod = set;
2087         }
2088         
2089         @Override
2090         public void set(RocketComponent c, String name, HashMap<String, String> attributes,
2091                         WarningSet warnings) {
2092                 String manufacturerName = attributes.get("manufacturer");
2093                 if (manufacturerName == null) {
2094                         warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no manufacturer specified.  Ignored"));
2095                         return;
2096                 }
2097                 
2098                 String productNo = attributes.get("partno");
2099                 if (productNo == null) {
2100                         warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no partno specified.  Ignored"));
2101                         return;
2102                 }
2103                 
2104                 String digest = attributes.get("digest");
2105                 if (digest == null) {
2106                         warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no digest specified."));
2107                 }
2108                 
2109                 String type = attributes.get("type");
2110                 if (type == null) {
2111                         warnings.add(Warning.fromString("Invalid ComponentPreset for component " + c.getName() + ", no type specified."));
2112                 }
2113                 
2114                 List<ComponentPreset> presets = Application.getComponentPresetDao().find(manufacturerName, productNo);
2115                 
2116                 ComponentPreset matchingPreset = null;
2117                 
2118                 for (ComponentPreset preset : presets) {
2119                         if (digest != null && preset.getDigest().equals(digest)) {
2120                                 // Found one with matching digest.  Take it.
2121                                 matchingPreset = preset;
2122                                 break;
2123                         }
2124                         if (type != null && preset.getType().name().equals(type) && matchingPreset != null) {
2125                                 // Found the first one with matching type.
2126                                 matchingPreset = preset;
2127                         }
2128                 }
2129                 
2130                 // Was any found?
2131                 if (matchingPreset == null) {
2132                         warnings.add(Warning.fromString("No matching ComponentPreset for component " + c.getName() + " found matching " + manufacturerName + " " + productNo));
2133                         return;
2134                 }
2135                 
2136                 if (digest != null && !matchingPreset.getDigest().equals(digest)) {
2137                         warnings.add(Warning.fromString("ComponentPreset for component " + c.getName() + " has wrong digest"));
2138                 }
2139                 
2140                 setMethod.invoke(c, matchingPreset);
2141         }
2142 }
2143
2144
2145 ////MaterialSetter  -  sets a Material value
2146 class MaterialSetter implements Setter {
2147         private final Reflection.Method setMethod;
2148         private final Material.Type type;
2149         
2150         public MaterialSetter(Reflection.Method set, Material.Type type) {
2151                 this.setMethod = set;
2152                 this.type = type;
2153         }
2154         
2155         @Override
2156         public void set(RocketComponent c, String name, HashMap<String, String> attributes,
2157                         WarningSet warnings) {
2158                 
2159                 Material mat;
2160                 
2161                 // Check name != ""
2162                 name = name.trim();
2163                 if (name.equals("")) {
2164                         warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2165                         return;
2166                 }
2167                 
2168                 // Parse density
2169                 double density;
2170                 String str;
2171                 str = attributes.remove("density");
2172                 if (str == null) {
2173                         warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2174                         return;
2175                 }
2176                 try {
2177                         density = Double.parseDouble(str);
2178                 } catch (NumberFormatException e) {
2179                         warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2180                         return;
2181                 }
2182                 
2183                 // Parse thickness
2184                 //              double thickness = 0;
2185                 //              str = attributes.remove("thickness");
2186                 //              try {
2187                 //                      if (str != null)
2188                 //                              thickness = Double.parseDouble(str);
2189                 //              } catch (NumberFormatException e){
2190                 //                      warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2191                 //                      return;
2192                 //              }
2193                 
2194                 // Check type if specified
2195                 str = attributes.remove("type");
2196                 if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
2197                         warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
2198                         return;
2199                 }
2200                 
2201                 mat = Databases.findMaterial(type, name, density);
2202                 
2203                 setMethod.invoke(c, mat);
2204         }
2205 }
2206
2207
2208
2209
2210
2211 class PositionSetter implements Setter {
2212         
2213         @Override
2214         public void set(RocketComponent c, String value, HashMap<String, String> attributes,
2215                         WarningSet warnings) {
2216                 
2217                 RocketComponent.Position type = (Position) DocumentConfig.findEnum(attributes.get("type"),
2218                                 RocketComponent.Position.class);
2219                 if (type == null) {
2220                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2221                         return;
2222                 }
2223                 
2224                 double pos;
2225                 try {
2226                         pos = Double.parseDouble(value);
2227                 } catch (NumberFormatException e) {
2228                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2229                         return;
2230                 }
2231                 
2232                 if (c instanceof FinSet) {
2233                         ((FinSet) c).setRelativePosition(type);
2234                         c.setPositionValue(pos);
2235                 } else if (c instanceof LaunchLug) {
2236                         ((LaunchLug) c).setRelativePosition(type);
2237                         c.setPositionValue(pos);
2238                 } else if (c instanceof InternalComponent) {
2239                         ((InternalComponent) c).setRelativePosition(type);
2240                         c.setPositionValue(pos);
2241                 } else {
2242                         warnings.add(Warning.FILE_INVALID_PARAMETER);
2243                 }
2244                 
2245         }
2246 }
2247
2248
2249 class FinTabPositionSetter extends DoubleSetter {
2250         
2251         public FinTabPositionSetter() {
2252                 super(Reflection.findMethod(FinSet.class, "setTabShift", double.class));
2253         }
2254         
2255         @Override
2256         public void set(RocketComponent c, String s, HashMap<String, String> attributes,
2257                         WarningSet warnings) {
2258                 
2259                 if (!(c instanceof FinSet)) {
2260                         throw new IllegalStateException("FinTabPositionSetter called for component " + c);
2261                 }
2262                 
2263                 String relative = attributes.get("relativeto");
2264                 FinSet.TabRelativePosition position =
2265                                 (TabRelativePosition) DocumentConfig.findEnum(relative,
2266                                                 FinSet.TabRelativePosition.class);
2267                 
2268                 if (position != null) {
2269                         
2270                         ((FinSet) c).setTabRelativePosition(position);
2271                         
2272                 } else {
2273                         if (relative == null) {
2274                                 warnings.add("Required attribute 'relativeto' not found for fin tab position.");
2275                         } else {
2276                                 warnings.add("Illegal attribute value '" + relative + "' encountered.");
2277                         }
2278                 }
2279                 
2280                 super.set(c, s, attributes, warnings);
2281         }
2282         
2283         
2284 }
2285
2286
2287 class ClusterConfigurationSetter implements Setter {
2288         
2289         @Override
2290         public void set(RocketComponent component, String value, HashMap<String, String> attributes,
2291                         WarningSet warnings) {
2292                 
2293                 if (!(component instanceof Clusterable)) {
2294                         warnings.add("Illegal component defined as cluster.");
2295                         return;
2296                 }
2297                 
2298                 ClusterConfiguration config = null;
2299                 for (ClusterConfiguration c : ClusterConfiguration.CONFIGURATIONS) {
2300                         if (c.getXMLName().equals(value)) {
2301                                 config = c;
2302                                 break;
2303                         }
2304                 }
2305                 
2306                 if (config == null) {
2307                         warnings.add("Illegal cluster configuration specified.");
2308                         return;
2309                 }
2310                 
2311                 ((Clusterable) component).setClusterConfiguration(config);
2312         }
2313 }