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