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