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