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