1 package net.sf.openrocket.file;
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 import java.util.Stack;
13 import net.sf.openrocket.aerodynamics.Warning;
14 import net.sf.openrocket.aerodynamics.WarningSet;
15 import net.sf.openrocket.database.Databases;
16 import net.sf.openrocket.document.OpenRocketDocument;
17 import net.sf.openrocket.document.Simulation;
18 import net.sf.openrocket.document.StorageOptions;
19 import net.sf.openrocket.document.Simulation.Status;
20 import net.sf.openrocket.material.Material;
21 import net.sf.openrocket.rocketcomponent.BodyComponent;
22 import net.sf.openrocket.rocketcomponent.BodyTube;
23 import net.sf.openrocket.rocketcomponent.Bulkhead;
24 import net.sf.openrocket.rocketcomponent.CenteringRing;
25 import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
26 import net.sf.openrocket.rocketcomponent.Clusterable;
27 import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
28 import net.sf.openrocket.rocketcomponent.EngineBlock;
29 import net.sf.openrocket.rocketcomponent.ExternalComponent;
30 import net.sf.openrocket.rocketcomponent.FinSet;
31 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
32 import net.sf.openrocket.rocketcomponent.InnerTube;
33 import net.sf.openrocket.rocketcomponent.InternalComponent;
34 import net.sf.openrocket.rocketcomponent.LaunchLug;
35 import net.sf.openrocket.rocketcomponent.MassComponent;
36 import net.sf.openrocket.rocketcomponent.MassObject;
37 import net.sf.openrocket.rocketcomponent.Motor;
38 import net.sf.openrocket.rocketcomponent.MotorMount;
39 import net.sf.openrocket.rocketcomponent.NoseCone;
40 import net.sf.openrocket.rocketcomponent.Parachute;
41 import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
42 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
43 import net.sf.openrocket.rocketcomponent.ReferenceType;
44 import net.sf.openrocket.rocketcomponent.RingComponent;
45 import net.sf.openrocket.rocketcomponent.Rocket;
46 import net.sf.openrocket.rocketcomponent.RocketComponent;
47 import net.sf.openrocket.rocketcomponent.ShockCord;
48 import net.sf.openrocket.rocketcomponent.Stage;
49 import net.sf.openrocket.rocketcomponent.Streamer;
50 import net.sf.openrocket.rocketcomponent.StructuralComponent;
51 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
52 import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
53 import net.sf.openrocket.rocketcomponent.Transition;
54 import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
55 import net.sf.openrocket.rocketcomponent.TubeCoupler;
56 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
57 import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
58 import net.sf.openrocket.simulation.FlightData;
59 import net.sf.openrocket.simulation.FlightDataBranch;
60 import net.sf.openrocket.simulation.FlightEvent;
61 import net.sf.openrocket.simulation.SimulationConditions;
62 import net.sf.openrocket.simulation.FlightEvent.Type;
63 import net.sf.openrocket.unit.UnitGroup;
64 import net.sf.openrocket.util.Coordinate;
65 import net.sf.openrocket.util.LineStyle;
66 import net.sf.openrocket.util.Reflection;
68 import org.xml.sax.Attributes;
69 import org.xml.sax.InputSource;
70 import org.xml.sax.SAXException;
71 import org.xml.sax.XMLReader;
72 import org.xml.sax.helpers.DefaultHandler;
73 import org.xml.sax.helpers.XMLReaderFactory;
77 * Class that loads a rocket definition from an OpenRocket rocket file.
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>.
84 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
87 public class OpenRocketLoader extends RocketLoader {
90 public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
92 InputSource xmlSource = new InputSource(source);
93 OpenRocketHandler handler = new OpenRocketHandler();
95 DelegatorHandler xmlhandler = new DelegatorHandler(handler, warnings);
98 XMLReader parser = XMLReaderFactory.createXMLReader();
99 parser.setContentHandler(xmlhandler);
100 parser.setErrorHandler(xmlhandler);
101 parser.parse(xmlSource);
102 } catch (SAXException e) {
103 throw new RocketLoadException("Malformed XML in input.", e);
107 OpenRocketDocument doc = handler.getDocument();
108 doc.getDefaultConfiguration().setAllStages();
110 // Deduce suitable time skip
111 double timeSkip = StorageOptions.SIMULATION_DATA_NONE;
112 for (Simulation s: doc.getSimulations()) {
113 if (s.getStatus() == Simulation.Status.EXTERNAL ||
114 s.getStatus() == Simulation.Status.NOT_SIMULATED)
116 if (s.getSimulatedData() == null)
118 if (s.getSimulatedData().getBranchCount() == 0)
120 FlightDataBranch branch = s.getSimulatedData().getBranch(0);
123 List<Double> list = branch.get(FlightDataBranch.TYPE_TIME);
127 double previousTime = Double.NaN;
128 for (double time: list) {
129 if (time - previousTime < timeSkip)
130 timeSkip = time-previousTime;
135 timeSkip = Math.rint(timeSkip*100)/100;
137 doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip);
138 doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed
139 doc.getDefaultStorageOptions().setExplicitlySet(false);
147 class DocumentConfig {
149 /* Remember to update OpenRocketSaver as well! */
150 public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0" };
153 //////// Component constructors
154 static final HashMap<String, Constructor<? extends RocketComponent>> constructors = new HashMap<String, Constructor<? extends RocketComponent>>();
157 // External components
158 constructors.put("bodytube", BodyTube.class.getConstructor(new Class<?>[0]));
159 constructors.put("transition", Transition.class.getConstructor(new Class<?>[0]));
160 constructors.put("nosecone", NoseCone.class.getConstructor(new Class<?>[0]));
161 constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
162 constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
163 constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
164 constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
166 // Internal components
167 constructors.put("engineblock", EngineBlock.class.getConstructor(new Class<?>[0]));
168 constructors.put("innertube", InnerTube.class.getConstructor(new Class<?>[0]));
169 constructors.put("tubecoupler", TubeCoupler.class.getConstructor(new Class<?>[0]));
170 constructors.put("bulkhead", Bulkhead.class.getConstructor(new Class<?>[0]));
171 constructors.put("centeringring", CenteringRing.class.getConstructor(new Class<?>[0]));
173 constructors.put("masscomponent", MassComponent.class.getConstructor(new Class<?>[0]));
174 constructors.put("shockcord", ShockCord.class.getConstructor(new Class<?>[0]));
175 constructors.put("parachute", Parachute.class.getConstructor(new Class<?>[0]));
176 constructors.put("streamer", Streamer.class.getConstructor(new Class<?>[0]));
179 constructors.put("stage", Stage.class.getConstructor(new Class<?>[0]));
181 } catch (NoSuchMethodException e) {
182 throw new RuntimeException(
183 "Error in constructing the 'constructors' HashMap.");
188 //////// Parameter setters
190 * The keys are of the form Class:param, where Class is the class name and param
191 * the element name. Setters are searched for in descending class order.
192 * A setter of null means setting the parameter is not allowed.
194 static final HashMap<String, Setter> setters = new HashMap<String, Setter>();
197 setters.put("RocketComponent:name", new StringSetter(
198 Reflection.findMethodStatic(RocketComponent.class, "setName", String.class)));
199 setters.put("RocketComponent:color", new ColorSetter(
200 Reflection.findMethodStatic(RocketComponent.class, "setColor", Color.class)));
201 setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
202 Reflection.findMethodStatic(RocketComponent.class, "setLineStyle", LineStyle.class),
204 setters.put("RocketComponent:position", new PositionSetter());
205 setters.put("RocketComponent:overridemass", new OverrideSetter(
206 Reflection.findMethodStatic(RocketComponent.class, "setOverrideMass", double.class),
207 Reflection.findMethodStatic(RocketComponent.class, "setMassOverridden", boolean.class)));
208 setters.put("RocketComponent:overridecg", new OverrideSetter(
209 Reflection.findMethodStatic(RocketComponent.class, "setOverrideCGX", double.class),
210 Reflection.findMethodStatic(RocketComponent.class, "setCGOverridden", boolean.class)));
211 setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
212 Reflection.findMethodStatic(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
213 setters.put("RocketComponent:comment", new StringSetter(
214 Reflection.findMethodStatic(RocketComponent.class, "setComment", String.class)));
217 setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
218 Reflection.findMethodStatic(ExternalComponent.class, "setFinish", Finish.class),
220 setters.put("ExternalComponent:material", new MaterialSetter(
221 Reflection.findMethodStatic(ExternalComponent.class, "setMaterial", Material.class),
222 Material.Type.BULK));
225 setters.put("BodyComponent:length", new DoubleSetter(
226 Reflection.findMethodStatic(BodyComponent.class, "setLength", double.class)));
228 // SymmetricComponent
229 setters.put("SymmetricComponent:thickness", new DoubleSetter(
230 Reflection.findMethodStatic(SymmetricComponent.class,"setThickness", double.class),
232 Reflection.findMethodStatic(SymmetricComponent.class,"setFilled", boolean.class)));
235 setters.put("BodyTube:radius", new DoubleSetter(
236 Reflection.findMethodStatic(BodyTube.class, "setRadius", double.class),
238 Reflection.findMethodStatic(BodyTube.class,"setRadiusAutomatic", boolean.class)));
241 setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
242 Reflection.findMethodStatic(Transition.class, "setType", Transition.Shape.class),
243 Transition.Shape.class));
244 setters.put("Transition:shapeclipped", new BooleanSetter(
245 Reflection.findMethodStatic(Transition.class, "setClipped", boolean.class)));
246 setters.put("Transition:shapeparameter", new DoubleSetter(
247 Reflection.findMethodStatic(Transition.class, "setShapeParameter", double.class)));
249 setters.put("Transition:foreradius", new DoubleSetter(
250 Reflection.findMethodStatic(Transition.class, "setForeRadius", double.class),
252 Reflection.findMethodStatic(Transition.class, "setForeRadiusAutomatic", boolean.class)));
253 setters.put("Transition:aftradius", new DoubleSetter(
254 Reflection.findMethodStatic(Transition.class, "setAftRadius", double.class),
256 Reflection.findMethodStatic(Transition.class, "setAftRadiusAutomatic", boolean.class)));
258 setters.put("Transition:foreshoulderradius", new DoubleSetter(
259 Reflection.findMethodStatic(Transition.class, "setForeShoulderRadius", double.class)));
260 setters.put("Transition:foreshoulderlength", new DoubleSetter(
261 Reflection.findMethodStatic(Transition.class, "setForeShoulderLength", double.class)));
262 setters.put("Transition:foreshoulderthickness", new DoubleSetter(
263 Reflection.findMethodStatic(Transition.class, "setForeShoulderThickness", double.class)));
264 setters.put("Transition:foreshouldercapped", new BooleanSetter(
265 Reflection.findMethodStatic(Transition.class, "setForeShoulderCapped", boolean.class)));
267 setters.put("Transition:aftshoulderradius", new DoubleSetter(
268 Reflection.findMethodStatic(Transition.class, "setAftShoulderRadius", double.class)));
269 setters.put("Transition:aftshoulderlength", new DoubleSetter(
270 Reflection.findMethodStatic(Transition.class, "setAftShoulderLength", double.class)));
271 setters.put("Transition:aftshoulderthickness", new DoubleSetter(
272 Reflection.findMethodStatic(Transition.class, "setAftShoulderThickness", double.class)));
273 setters.put("Transition:aftshouldercapped", new BooleanSetter(
274 Reflection.findMethodStatic(Transition.class, "setAftShoulderCapped", boolean.class)));
276 // NoseCone - disable disallowed elements
277 setters.put("NoseCone:foreradius", null);
278 setters.put("NoseCone:foreshoulderradius", null);
279 setters.put("NoseCone:foreshoulderlength", null);
280 setters.put("NoseCone:foreshoulderthickness", null);
281 setters.put("NoseCone:foreshouldercapped", null);
284 setters.put("FinSet:fincount", new IntSetter(
285 Reflection.findMethodStatic(FinSet.class, "setFinCount", int.class)));
286 setters.put("FinSet:rotation", new DoubleSetter(
287 Reflection.findMethodStatic(FinSet.class, "setBaseRotation", double.class), Math.PI/180.0));
288 setters.put("FinSet:thickness", new DoubleSetter(
289 Reflection.findMethodStatic(FinSet.class, "setThickness", double.class)));
290 setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
291 Reflection.findMethodStatic(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
292 FinSet.CrossSection.class));
293 setters.put("FinSet:cant", new DoubleSetter(
294 Reflection.findMethodStatic(FinSet.class, "setCantAngle", double.class), Math.PI/180.0));
297 setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
298 Reflection.findMethodStatic(TrapezoidFinSet.class, "setRootChord", double.class)));
299 setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
300 Reflection.findMethodStatic(TrapezoidFinSet.class, "setTipChord", double.class)));
301 setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
302 Reflection.findMethodStatic(TrapezoidFinSet.class, "setSweep", double.class)));
303 setters.put("TrapezoidFinSet:height", new DoubleSetter(
304 Reflection.findMethodStatic(TrapezoidFinSet.class, "setHeight", double.class)));
307 setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
308 Reflection.findMethodStatic(EllipticalFinSet.class, "setLength", double.class)));
309 setters.put("EllipticalFinSet:height", new DoubleSetter(
310 Reflection.findMethodStatic(EllipticalFinSet.class, "setHeight", double.class)));
312 // FreeformFinSet points handled as a special handler
315 setters.put("LaunchLug:radius", new DoubleSetter(
316 Reflection.findMethodStatic(LaunchLug.class, "setRadius", double.class)));
317 setters.put("LaunchLug:length", new DoubleSetter(
318 Reflection.findMethodStatic(LaunchLug.class, "setLength", double.class)));
319 setters.put("LaunchLug:thickness", new DoubleSetter(
320 Reflection.findMethodStatic(LaunchLug.class, "setThickness", double.class)));
321 setters.put("LaunchLug:radialDirection", new DoubleSetter(
322 Reflection.findMethodStatic(LaunchLug.class, "setRadialDirection", double.class),
325 // InternalComponent - nothing
327 // StructuralComponent
328 setters.put("StructuralComponent:material", new MaterialSetter(
329 Reflection.findMethodStatic(StructuralComponent.class, "setMaterial", Material.class),
330 Material.Type.BULK));
333 setters.put("RingComponent:length", new DoubleSetter(
334 Reflection.findMethodStatic(RingComponent.class, "setLength", double.class)));
335 setters.put("RingComponent:radialposition", new DoubleSetter(
336 Reflection.findMethodStatic(RingComponent.class, "setRadialPosition", double.class)));
337 setters.put("RingComponent:radialdirection", new DoubleSetter(
338 Reflection.findMethodStatic(RingComponent.class, "setRadialDirection", double.class),
341 // ThicknessRingComponent - radius on separate components due to differing automatics
342 setters.put("ThicknessRingComponent:thickness", new DoubleSetter(
343 Reflection.findMethodStatic(ThicknessRingComponent.class, "setThickness", double.class)));
346 setters.put("EngineBlock:outerradius", new DoubleSetter(
347 Reflection.findMethodStatic(EngineBlock.class, "setOuterRadius", double.class),
349 Reflection.findMethodStatic(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
352 setters.put("TubeCoupler:outerradius", new DoubleSetter(
353 Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadius", double.class),
355 Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
358 setters.put("InnerTube:outerradius", new DoubleSetter(
359 Reflection.findMethodStatic(InnerTube.class, "setOuterRadius", double.class)));
360 setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
361 setters.put("InnerTube:clusterscale", new DoubleSetter(
362 Reflection.findMethodStatic(InnerTube.class, "setClusterScale", double.class)));
363 setters.put("InnerTube:clusterrotation", new DoubleSetter(
364 Reflection.findMethodStatic(InnerTube.class, "setClusterRotation", double.class),
367 // RadiusRingComponent
370 setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
371 Reflection.findMethodStatic(RadiusRingComponent.class, "setInnerRadius", double.class)));
372 setters.put("Bulkhead:outerradius", new DoubleSetter(
373 Reflection.findMethodStatic(Bulkhead.class, "setOuterRadius", double.class),
375 Reflection.findMethodStatic(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
378 setters.put("CenteringRing:innerradius", new DoubleSetter(
379 Reflection.findMethodStatic(CenteringRing.class, "setInnerRadius", double.class),
381 Reflection.findMethodStatic(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
382 setters.put("CenteringRing:outerradius", new DoubleSetter(
383 Reflection.findMethodStatic(CenteringRing.class, "setOuterRadius", double.class),
385 Reflection.findMethodStatic(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
389 setters.put("MassObject:packedlength", new DoubleSetter(
390 Reflection.findMethodStatic(MassObject.class, "setLength", double.class)));
391 setters.put("MassObject:packedradius", new DoubleSetter(
392 Reflection.findMethodStatic(MassObject.class, "setRadius", double.class)));
393 setters.put("MassObject:radialposition", new DoubleSetter(
394 Reflection.findMethodStatic(MassObject.class, "setRadialPosition", double.class)));
395 setters.put("MassObject:radialdirection", new DoubleSetter(
396 Reflection.findMethodStatic(MassObject.class, "setRadialDirection", double.class),
400 setters.put("MassComponent:mass", new DoubleSetter(
401 Reflection.findMethodStatic(MassComponent.class, "setComponentMass", double.class)));
404 setters.put("ShockCord:cordlength", new DoubleSetter(
405 Reflection.findMethodStatic(ShockCord.class, "setCordLength", double.class)));
406 setters.put("ShockCord:material", new MaterialSetter(
407 Reflection.findMethodStatic(ShockCord.class, "setMaterial", Material.class),
408 Material.Type.LINE));
411 setters.put("RecoveryDevice:cd", new DoubleSetter(
412 Reflection.findMethodStatic(RecoveryDevice.class, "setCD", double.class),
414 Reflection.findMethodStatic(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
415 setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
416 Reflection.findMethodStatic(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
417 RecoveryDevice.DeployEvent.class));
418 setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
419 Reflection.findMethodStatic(RecoveryDevice.class, "setDeployAltitude", double.class)));
420 setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
421 Reflection.findMethodStatic(RecoveryDevice.class, "setDeployDelay", double.class)));
422 setters.put("RecoveryDevice:material", new MaterialSetter(
423 Reflection.findMethodStatic(RecoveryDevice.class, "setMaterial", Material.class),
424 Material.Type.SURFACE));
427 setters.put("Parachute:diameter", new DoubleSetter(
428 Reflection.findMethodStatic(Parachute.class, "setDiameter", double.class)));
429 setters.put("Parachute:linecount", new IntSetter(
430 Reflection.findMethodStatic(Parachute.class, "setLineCount", int.class)));
431 setters.put("Parachute:linelength", new DoubleSetter(
432 Reflection.findMethodStatic(Parachute.class, "setLineLength", double.class)));
433 setters.put("Parachute:linematerial", new MaterialSetter(
434 Reflection.findMethodStatic(Parachute.class, "setLineMaterial", Material.class),
435 Material.Type.LINE));
438 setters.put("Streamer:striplength", new DoubleSetter(
439 Reflection.findMethodStatic(Streamer.class, "setStripLength", double.class)));
440 setters.put("Streamer:stripwidth", new DoubleSetter(
441 Reflection.findMethodStatic(Streamer.class, "setStripWidth", double.class)));
444 // <motorconfiguration> handled by separate handler
445 setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
446 Reflection.findMethodStatic(Rocket.class, "setReferenceType", ReferenceType.class),
447 ReferenceType.class));
448 setters.put("Rocket:customreference", new DoubleSetter(
449 Reflection.findMethodStatic(Rocket.class, "setCustomReferenceLength", double.class)));
450 setters.put("Rocket:designer", new StringSetter(
451 Reflection.findMethodStatic(Rocket.class, "setDesigner", String.class)));
452 setters.put("Rocket:revision", new StringSetter(
453 Reflection.findMethodStatic(Rocket.class, "setRevision", String.class)));
458 * Search for a enum value that has the corresponding name as an XML value. The current
459 * conversion from enum name to XML value is to lowercase the name and strip out all
460 * underscore characters. This method returns a match to these criteria, or <code>null</code>
461 * if no such enum exists.
463 * @param <T> then enum type.
464 * @param name the XML value, null ok.
465 * @param enumClass the class of the enum.
466 * @return the found enum value, or <code>null</code>.
468 public static <T extends Enum<T>> Enum<T> findEnum(String name, Class<? extends Enum<T>> enumClass) {
473 for (Enum<T> e: enumClass.getEnumConstants()) {
474 if (e.name().toLowerCase().replace("_", "").equals(name)) {
483 * Convert a string to a double including formatting specifications of the OpenRocket
484 * file format. This accepts all formatting that is valid for
485 * <code>Double.parseDouble(s)</code> and a few others as well ("Inf", "-Inf").
487 * @param s the string to parse.
488 * @return the numerical value.
489 * @throws NumberFormatException the the string cannot be parsed.
491 public static double stringToDouble(String s) throws NumberFormatException {
493 throw new NumberFormatException("null string");
494 if (s.equalsIgnoreCase("NaN"))
496 if (s.equalsIgnoreCase("Inf"))
497 return Double.POSITIVE_INFINITY;
498 if (s.equalsIgnoreCase("-Inf"))
499 return Double.NEGATIVE_INFINITY;
500 return Double.parseDouble(s);
508 * The actual handler class. Contains the necessary methods for parsing the SAX source.
510 class DelegatorHandler extends DefaultHandler {
511 private final WarningSet warnings;
513 private final Stack<ElementHandler> handlerStack = new Stack<ElementHandler>();
514 private final Stack<StringBuilder> elementData = new Stack<StringBuilder>();
515 private final Stack<HashMap<String, String>> elementAttributes = new Stack<HashMap<String, String>>();
518 // Ignore all elements as long as ignore > 0
519 private int ignore = 0;
522 public DelegatorHandler(ElementHandler initialHandler, WarningSet warnings) {
523 this.warnings = warnings;
524 handlerStack.add(initialHandler);
525 elementData.add(new StringBuilder()); // Just in case
529 ///////// SAX handlers
532 public void startElement(String uri, String localName, String name,
533 Attributes attributes) throws SAXException {
541 // Check for unknown namespace
542 if (!uri.equals("")) {
543 warnings.add(Warning.fromString("Unknown namespace element '" + uri
544 + "' encountered, ignoring."));
549 // Add layer to data stacks
550 elementData.push(new StringBuilder());
551 elementAttributes.push(copyAttributes(attributes));
554 ElementHandler h = handlerStack.peek();
555 h = h.openElement(localName, elementAttributes.peek(), warnings);
557 handlerStack.push(h);
559 // Start ignoring elements
566 * Stores encountered characters in the elementData stack.
569 public void characters(char[] chars, int start, int length) throws SAXException {
574 StringBuilder sb = elementData.peek();
575 sb.append(chars, start, length);
580 * Removes the last layer from the stack.
583 public void endElement(String uri, String localName, String name) throws SAXException {
591 // Remove data from stack
592 String data = elementData.pop().toString(); // throws on error
593 HashMap<String, String> attr = elementAttributes.pop();
595 // Remove last handler and call the next one
598 h = handlerStack.pop();
599 h.endHandler(localName, attr, data, warnings);
601 h = handlerStack.peek();
602 h.closeElement(localName, attr, data, warnings);
606 private static HashMap<String, String> copyAttributes(Attributes atts) {
607 HashMap<String, String> ret = new HashMap<String, String>();
608 for (int i = 0; i < atts.getLength(); i++) {
609 ret.put(atts.getLocalName(i), atts.getValue(i));
618 abstract class ElementHandler {
621 * Called when an opening element is encountered. Returns the handler that will handle
622 * the elements within that element, or <code>null</code> if the element and all of
623 * its contents is to be ignored.
625 * @param element the element name.
626 * @param attributes attributes of the element.
627 * @param warnings the warning set to store warnings in.
628 * @return the handler that handles elements encountered within this element,
629 * or <code>null</code> if the element is to be ignored.
631 public abstract ElementHandler openElement(String element,
632 HashMap<String, String> attributes, WarningSet warnings);
635 * Called when an element is closed. The default implementation checks whether there is
636 * any non-space text within the element and if there exists any attributes, and adds
637 * a warning of both. This can be used at the and of the method to check for
640 * @param element the element name.
641 * @param attributes attributes of the element.
642 * @param content the textual content of the element.
643 * @param warnings the warning set to store warnings in.
645 public void closeElement(String element, HashMap<String, String> attributes,
646 String content, WarningSet warnings) {
648 if (!content.trim().equals("")) {
649 warnings.add(Warning.fromString("Unknown text in element " + element
652 if (!attributes.isEmpty()) {
653 warnings.add(Warning.fromString("Unknown attributes in element " + element
660 * Called when the element block that this handler is handling ends.
661 * The default implementation is a no-op.
663 * @param warnings the warning set to store warnings in.
665 public void endHandler(String element, HashMap<String, String> attributes,
666 String content, WarningSet warnings) {
674 * The starting point of the handlers. Accepts a single <openrocket> element and hands
675 * the contents to be read by a OpenRocketContentsHandler.
677 class OpenRocketHandler extends ElementHandler {
678 private OpenRocketContentHandler handler = null;
681 * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
682 * has not been read yet.
684 * @return the document read, or null.
686 public OpenRocketDocument getDocument() {
687 return handler.getDocument();
691 public ElementHandler openElement(String element, HashMap<String, String> attributes,
692 WarningSet warnings) {
694 // Check for unknown elements
695 if (!element.equals("openrocket")) {
696 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
700 // Check for first call
701 if (handler != null) {
702 warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
707 // Check version number
708 String version = null;
709 String docVersion = attributes.remove("version");
710 for (String v : DocumentConfig.SUPPORTED_VERSIONS) {
711 if (v.equals(docVersion)) {
716 if (version == null) {
717 if (docVersion != null)
718 warnings.add(Warning.fromString("Unsupported document version "
719 + docVersion + ", attempting to read anyway."));
721 warnings.add(Warning.fromString("Unsupported document version, attempting to"
725 handler = new OpenRocketContentHandler();
732 * Handles the content of the <openrocket> tag.
734 class OpenRocketContentHandler extends ElementHandler {
735 private final OpenRocketDocument doc;
736 private final Rocket rocket;
738 private boolean rocketDefined = false;
739 private boolean simulationsDefined = false;
741 public OpenRocketContentHandler() {
742 this.rocket = new Rocket();
743 this.doc = new OpenRocketDocument(rocket);
747 public OpenRocketDocument getDocument() {
754 public ElementHandler openElement(String element, HashMap<String, String> attributes,
755 WarningSet warnings) {
757 if (element.equals("rocket")) {
760 .fromString("Multiple rocket designs within one document, "
761 + "ignoring later ones."));
764 rocketDefined = true;
765 return new ComponentParameterHandler(rocket);
768 if (element.equals("simulations")) {
769 if (simulationsDefined) {
771 .fromString("Multiple simulation definitions within one document, "
772 + "ignoring later ones."));
775 simulationsDefined = true;
776 return new SimulationsHandler(doc);
779 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
787 * An element handler that does not allow any sub-elements. If any are encountered
788 * a warning is generated and they are ignored.
790 class PlainTextHandler extends ElementHandler {
791 public static final PlainTextHandler INSTANCE = new PlainTextHandler();
793 private PlainTextHandler() {
797 public ElementHandler openElement(String element, HashMap<String, String> attributes,
798 WarningSet warnings) {
799 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
804 public void closeElement(String element, HashMap<String, String> attributes,
805 String content, WarningSet warnings) {
806 // Warning from openElement is sufficient.
813 * A handler that creates components from the corresponding elements. The control of the
814 * contents is passed on to ComponentParameterHandler.
816 class ComponentHandler extends ElementHandler {
817 private final RocketComponent parent;
819 public ComponentHandler(RocketComponent parent) {
820 this.parent = parent;
824 public ElementHandler openElement(String element, HashMap<String, String> attributes,
825 WarningSet warnings) {
827 // Attempt to construct new component
828 Constructor<? extends RocketComponent> constructor = DocumentConfig.constructors
830 if (constructor == null) {
831 warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
837 c = constructor.newInstance();
838 } catch (InstantiationException e) {
839 throw new RuntimeException("Error constructing component.", e);
840 } catch (IllegalAccessException e) {
841 throw new RuntimeException("Error constructing component.", e);
842 } catch (InvocationTargetException e) {
843 throw new RuntimeException("Error constructing component.", e);
848 return new ComponentParameterHandler(c);
854 * A handler that populates the parameters of a previously constructed rocket component.
855 * This uses the setters, or delegates the handling to another handler for specific
858 class ComponentParameterHandler extends ElementHandler {
859 private final RocketComponent component;
861 public ComponentParameterHandler(RocketComponent c) {
866 public ElementHandler openElement(String element, HashMap<String, String> attributes,
867 WarningSet warnings) {
869 // Check for specific elements that contain other elements
870 if (element.equals("subcomponents")) {
871 return new ComponentHandler(component);
873 if (element.equals("motormount")) {
874 if (!(component instanceof MotorMount)) {
875 warnings.add(Warning.fromString("Illegal component defined as motor mount."));
878 return new MotorMountHandler((MotorMount)component);
880 if (element.equals("finpoints")) {
881 if (!(component instanceof FreeformFinSet)) {
882 warnings.add(Warning.fromString("Illegal component defined for fin points."));
885 return new FinSetPointHandler((FreeformFinSet)component);
887 if (element.equals("motorconfiguration")) {
888 if (!(component instanceof Rocket)) {
889 warnings.add(Warning.fromString("Illegal component defined for motor configuration."));
892 return new MotorConfigurationHandler((Rocket)component);
896 return PlainTextHandler.INSTANCE;
900 public void closeElement(String element, HashMap<String, String> attributes,
901 String content, WarningSet warnings) {
903 if (element.equals("subcomponents") || element.equals("motormount") ||
904 element.equals("finpoints") || element.equals("motorconfiguration")) {
908 // Search for the correct setter class
911 for (c = component.getClass(); c != null; c = c.getSuperclass()) {
912 String setterKey = c.getSimpleName() + ":" + element;
913 Setter s = DocumentConfig.setters.get(setterKey);
916 System.out.println("Calling with key "+setterKey);
917 s.set(component, content, attributes, warnings);
920 if (DocumentConfig.setters.containsKey(setterKey)) {
921 // Key exists but is null -> invalid parameter
927 warnings.add(Warning.fromString("Unknown parameter type " + element + " for "
928 + component.getComponentName()));
935 * A handler that reads the <point> specifications within the freeformfinset's
936 * <finpoints> elements.
938 class FinSetPointHandler extends ElementHandler {
939 private final FreeformFinSet finset;
940 private final ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
942 public FinSetPointHandler(FreeformFinSet finset) {
943 this.finset = finset;
947 public ElementHandler openElement(String element, HashMap<String, String> attributes,
948 WarningSet warnings) {
949 return PlainTextHandler.INSTANCE;
954 public void closeElement(String element, HashMap<String, String> attributes,
955 String content, WarningSet warnings) {
957 String strx = attributes.remove("x");
958 String stry = attributes.remove("y");
959 if (strx == null || stry == null) {
960 warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
964 double x = Double.parseDouble(strx);
965 double y = Double.parseDouble(stry);
966 coordinates.add(new Coordinate(x,y));
967 } catch (NumberFormatException e) {
968 warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
972 super.closeElement(element, attributes, content, warnings);
976 public void endHandler(String element, HashMap<String, String> attributes,
977 String content, WarningSet warnings) {
979 finset.setPoints(coordinates.toArray(new Coordinate[0]));
980 } catch (IllegalArgumentException e) {
981 warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
987 class MotorMountHandler extends ElementHandler {
988 private final MotorMount mount;
989 private MotorHandler motorHandler;
991 public MotorMountHandler(MotorMount mount) {
993 mount.setMotorMount(true);
997 public ElementHandler openElement(String element, HashMap<String, String> attributes,
998 WarningSet warnings) {
1000 if (element.equals("motor")) {
1001 motorHandler = new MotorHandler();
1002 return motorHandler;
1005 if (element.equals("ignitionevent") ||
1006 element.equals("ignitiondelay") ||
1007 element.equals("overhang")) {
1008 return PlainTextHandler.INSTANCE;
1011 warnings.add(Warning.fromString("Unknown element '"+element+"' encountered, ignoring."));
1018 public void closeElement(String element, HashMap<String, String> attributes,
1019 String content, WarningSet warnings) {
1021 if (element.equals("motor")) {
1022 String id = attributes.get("configid");
1023 if (id == null || id.equals("")) {
1024 warnings.add(Warning.fromString("Illegal motor specification, ignoring."));
1028 Motor motor = motorHandler.getMotor(warnings);
1029 mount.setMotor(id, motor);
1030 mount.setMotorDelay(id, motorHandler.getDelay(warnings));
1034 if (element.equals("ignitionevent")) {
1035 MotorMount.IgnitionEvent event = null;
1036 for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) {
1037 if (e.name().toLowerCase().replaceAll("_", "").equals(content)) {
1042 if (event == null) {
1043 warnings.add(Warning.fromString("Unknown ignition event type '"+content+"', ignoring."));
1046 mount.setIgnitionEvent(event);
1050 if (element.equals("ignitiondelay")) {
1053 d = Double.parseDouble(content);
1054 } catch (NumberFormatException nfe) {
1055 warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
1058 mount.setIgnitionDelay(d);
1062 if (element.equals("overhang")) {
1065 d = Double.parseDouble(content);
1066 } catch (NumberFormatException nfe) {
1067 warnings.add(Warning.fromString("Illegal overhang specified, ignoring."));
1070 mount.setMotorOverhang(d);
1074 super.closeElement(element, attributes, content, warnings);
1081 class MotorConfigurationHandler extends ElementHandler {
1082 private final Rocket rocket;
1083 private String name = null;
1084 private boolean inNameElement = false;
1086 public MotorConfigurationHandler(Rocket rocket) {
1087 this.rocket = rocket;
1091 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1092 WarningSet warnings) {
1094 if (inNameElement || !element.equals("name")) {
1095 warnings.add(Warning.FILE_INVALID_PARAMETER);
1098 inNameElement = true;
1100 return PlainTextHandler.INSTANCE;
1104 public void closeElement(String element, HashMap<String, String> attributes,
1105 String content, WarningSet warnings) {
1110 public void endHandler(String element, HashMap<String, String> attributes,
1111 String content, WarningSet warnings) {
1113 String configid = attributes.remove("configid");
1114 if (configid == null || configid.equals("")) {
1115 warnings.add(Warning.FILE_INVALID_PARAMETER);
1119 if (!rocket.addMotorConfigurationID(configid)) {
1120 warnings.add("Duplicate motor configuration ID used.");
1124 if (name != null && name.trim().length() > 0) {
1125 rocket.setMotorConfigurationName(configid, name);
1128 if ("true".equals(attributes.remove("default"))) {
1129 rocket.getDefaultConfiguration().setMotorConfigurationID(configid);
1132 super.closeElement(element, attributes, content, warnings);
1137 class MotorHandler extends ElementHandler {
1138 private Motor.Type type = null;
1139 private String manufacturer = null;
1140 private String designation = null;
1141 private double diameter = Double.NaN;
1142 private double length = Double.NaN;
1143 private double delay = Double.NaN;
1146 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1147 WarningSet warnings) {
1148 return PlainTextHandler.INSTANCE;
1153 * Return the motor to use, or null.
1155 public Motor getMotor(WarningSet warnings) {
1156 if (designation == null) {
1157 warnings.add(Warning.fromString("No motor specified, ignoring."));
1160 Motor[] motors = Databases.findMotors(type, manufacturer, designation, diameter, length);
1161 if (motors.length == 0) {
1162 String str = "No motor with designation '"+designation+"'";
1163 if (manufacturer != null)
1164 str += " for manufacturer '" + manufacturer + "'";
1165 warnings.add(Warning.fromString(str + " found."));
1168 if (motors.length > 1) {
1169 String str = "Multiple motors with designation '"+designation+"'";
1170 if (manufacturer != null)
1171 str += " for manufacturer '" + manufacturer + "'";
1172 warnings.add(Warning.fromString(str + " found, one chosen arbitrarily."));
1179 * Return the delay to use for the motor.
1181 public double getDelay(WarningSet warnings) {
1182 if (Double.isNaN(delay)) {
1183 warnings.add(Warning.fromString("Motor delay not specified, assuming no ejection charge."));
1184 return Motor.PLUGGED;
1191 public void closeElement(String element, HashMap<String, String> attributes,
1192 String content, WarningSet warnings) {
1194 content = content.trim();
1196 if (element.equals("type")) {
1200 for (Motor.Type t: Motor.Type.values()) {
1201 if (t.name().toLowerCase().equals(content)) {
1207 warnings.add(Warning.fromString("Unknown motor type '"+content+"', ignoring."));
1210 } else if (element.equals("manufacturer")) {
1213 manufacturer = content;
1215 } else if (element.equals("designation")) {
1218 designation = content;
1220 } else if (element.equals("diameter")) {
1223 diameter = Double.NaN;
1225 diameter = Double.parseDouble(content);
1226 } catch (NumberFormatException e) {
1229 if (Double.isNaN(diameter)) {
1230 warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
1233 } else if (element.equals("length")) {
1236 length = Double.NaN;
1238 length = Double.parseDouble(content);
1239 } catch (NumberFormatException ignore) { }
1241 if (Double.isNaN(length)) {
1242 warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
1245 } else if (element.equals("delay")) {
1249 if (content.equals("none")) {
1250 delay = Motor.PLUGGED;
1253 delay = Double.parseDouble(content);
1254 } catch (NumberFormatException ignore) { }
1256 if (Double.isNaN(delay)) {
1257 warnings.add(Warning.fromString("Illegal motor delay specified, ignoring."));
1263 super.closeElement(element, attributes, content, warnings);
1271 class SimulationsHandler extends ElementHandler {
1272 private final OpenRocketDocument doc;
1273 private SingleSimulationHandler handler;
1275 public SimulationsHandler(OpenRocketDocument doc) {
1280 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1281 WarningSet warnings) {
1283 if (!element.equals("simulation")) {
1284 warnings.add("Unknown element '"+element+"', ignoring.");
1288 handler = new SingleSimulationHandler(doc);
1293 class SingleSimulationHandler extends ElementHandler {
1295 private final OpenRocketDocument doc;
1297 private String name;
1299 private SimulationConditionsHandler conditionHandler;
1300 private FlightDataHandler dataHandler;
1302 private final List<String> listeners = new ArrayList<String>();
1304 public SingleSimulationHandler(OpenRocketDocument doc) {
1311 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1312 WarningSet warnings) {
1314 if (element.equals("name") || element.equals("simulator") ||
1315 element.equals("calculator") || element.equals("listener")) {
1316 return PlainTextHandler.INSTANCE;
1317 } else if (element.equals("conditions")) {
1318 conditionHandler = new SimulationConditionsHandler(doc.getRocket());
1319 return conditionHandler;
1320 } else if (element.equals("flightdata")) {
1321 dataHandler = new FlightDataHandler();
1324 warnings.add("Unknown element '"+element+"', ignoring.");
1330 public void closeElement(String element, HashMap<String, String> attributes,
1331 String content, WarningSet warnings) {
1333 if (element.equals("name")) {
1335 } else if (element.equals("simulator")) {
1336 if (!content.equals("RK4Simulator")) {
1337 warnings.add("Unknown simulator specified, ignoring.");
1339 } else if (element.equals("calculator")) {
1340 if (!content.equals("BarrowmanSimulator")) {
1341 warnings.add("Unknown calculator specified, ignoring.");
1343 } else if (element.equals("listener") && content.trim().length() > 0) {
1344 listeners.add(content.trim());
1350 public void endHandler(String element, HashMap<String, String> attributes,
1351 String content, WarningSet warnings) {
1353 String s = attributes.get("status");
1354 Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
1355 if (status == null) {
1356 warnings.add("Simulation status unknown, assuming outdated.");
1357 status = Simulation.Status.OUTDATED;
1360 SimulationConditions conditions;
1361 if (conditionHandler != null) {
1362 conditions = conditionHandler.getConditions();
1364 warnings.add("Simulation conditions not defined, using defaults.");
1365 conditions = new SimulationConditions(doc.getRocket());
1369 name = "Simulation";
1372 if (dataHandler == null)
1375 data = dataHandler.getFlightData();
1377 Simulation simulation = new Simulation(doc.getRocket(), status, name,
1378 conditions, listeners, data);
1380 doc.addSimulation(simulation);
1386 class SimulationConditionsHandler extends ElementHandler {
1387 private SimulationConditions conditions;
1388 private AtmosphereHandler atmosphereHandler;
1390 public SimulationConditionsHandler(Rocket rocket) {
1391 conditions = new SimulationConditions(rocket);
1394 public SimulationConditions getConditions() {
1399 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1400 WarningSet warnings) {
1401 if (element.equals("atmosphere")) {
1402 atmosphereHandler = new AtmosphereHandler(attributes.get("model"));
1403 return atmosphereHandler;
1405 return PlainTextHandler.INSTANCE;
1409 public void closeElement(String element, HashMap<String, String> attributes,
1410 String content, WarningSet warnings) {
1412 double d = Double.NaN;
1414 d = Double.parseDouble(content);
1415 } catch (NumberFormatException ignore) { }
1418 if (element.equals("configid")) {
1419 if (content.equals("")) {
1420 conditions.setMotorConfigurationID(null);
1422 conditions.setMotorConfigurationID(content);
1424 } else if (element.equals("launchrodlength")) {
1425 if (Double.isNaN(d)) {
1426 warnings.add("Illegal launch rod length defined, ignoring.");
1428 conditions.setLaunchRodLength(d);
1430 } else if (element.equals("launchrodangle")) {
1431 if (Double.isNaN(d)) {
1432 warnings.add("Illegal launch rod angle defined, ignoring.");
1434 conditions.setLaunchRodAngle(d*Math.PI/180);
1436 } else if (element.equals("launchroddirection")) {
1437 if (Double.isNaN(d)) {
1438 warnings.add("Illegal launch rod direction defined, ignoring.");
1440 conditions.setLaunchRodDirection(d*Math.PI/180);
1442 } else if (element.equals("windaverage")) {
1443 if (Double.isNaN(d)) {
1444 warnings.add("Illegal average windspeed defined, ignoring.");
1446 conditions.setWindSpeedAverage(d);
1448 } else if (element.equals("windturbulence")) {
1449 if (Double.isNaN(d)) {
1450 warnings.add("Illegal wind turbulence intensity defined, ignoring.");
1452 conditions.setWindTurbulenceIntensity(d);
1454 } else if (element.equals("launchaltitude")) {
1455 if (Double.isNaN(d)) {
1456 warnings.add("Illegal launch altitude defined, ignoring.");
1458 conditions.setLaunchAltitude(d);
1460 } else if (element.equals("launchlatitude")) {
1461 if (Double.isNaN(d)) {
1462 warnings.add("Illegal launch latitude defined, ignoring.");
1464 conditions.setLaunchLatitude(d);
1466 } else if (element.equals("atmosphere")) {
1467 atmosphereHandler.storeSettings(conditions, warnings);
1468 } else if (element.equals("timestep")) {
1469 if (Double.isNaN(d)) {
1470 warnings.add("Illegal time step defined, ignoring.");
1472 conditions.setTimeStep(d);
1479 class AtmosphereHandler extends ElementHandler {
1480 private final String model;
1481 private double temperature = Double.NaN;
1482 private double pressure = Double.NaN;
1484 public AtmosphereHandler(String model) {
1489 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1490 WarningSet warnings) {
1491 return PlainTextHandler.INSTANCE;
1495 public void closeElement(String element, HashMap<String, String> attributes,
1496 String content, WarningSet warnings) {
1498 double d = Double.NaN;
1500 d = Double.parseDouble(content);
1501 } catch (NumberFormatException ignore) { }
1503 if (element.equals("basetemperature")) {
1504 if (Double.isNaN(d)) {
1505 warnings.add("Illegal base temperature specified, ignoring.");
1508 } else if (element.equals("basepressure")) {
1509 if (Double.isNaN(d)) {
1510 warnings.add("Illegal base pressure specified, ignoring.");
1514 super.closeElement(element, attributes, content, warnings);
1519 public void storeSettings(SimulationConditions cond, WarningSet warnings) {
1520 if (!Double.isNaN(pressure)) {
1521 cond.setLaunchPressure(pressure);
1523 if (!Double.isNaN(temperature)) {
1524 cond.setLaunchTemperature(temperature);
1527 if ("isa".equals(model)) {
1528 cond.setISAAtmosphere(true);
1529 } else if ("extendedisa".equals(model)){
1530 cond.setISAAtmosphere(false);
1532 cond.setISAAtmosphere(true);
1533 warnings.add("Unknown atmospheric model, using ISA.");
1540 class FlightDataHandler extends ElementHandler {
1542 private FlightDataBranchHandler dataHandler;
1543 private WarningSet warningSet = new WarningSet();
1544 private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
1546 private FlightData data;
1548 public FlightData getFlightData() {
1553 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1554 WarningSet warnings) {
1556 if (element.equals("warning")) {
1557 return PlainTextHandler.INSTANCE;
1559 if (element.equals("databranch")) {
1560 if (attributes.get("name") == null || attributes.get("types")==null) {
1561 warnings.add("Illegal flight data definition, ignoring.");
1564 dataHandler = new FlightDataBranchHandler(attributes.get("name"),
1565 attributes.get("types"));
1569 warnings.add("Unknown element '"+element+"' encountered, ignoring.");
1575 public void closeElement(String element, HashMap<String, String> attributes,
1576 String content, WarningSet warnings) {
1578 if (element.equals("databranch")) {
1579 FlightDataBranch branch = dataHandler.getBranch();
1580 if (branch.getLength() > 0) {
1581 branches.add(branch);
1583 } else if (element.equals("warning")) {
1584 warningSet.add(Warning.fromString(content));
1590 public void endHandler(String element, HashMap<String, String> attributes,
1591 String content, WarningSet warnings) {
1593 if (branches.size() > 0) {
1594 data = new FlightData(branches.toArray(new FlightDataBranch[0]));
1596 double maxAltitude = Double.NaN;
1597 double maxVelocity = Double.NaN;
1598 double maxAcceleration = Double.NaN;
1599 double maxMach = Double.NaN;
1600 double timeToApogee = Double.NaN;
1601 double flightTime = Double.NaN;
1602 double groundHitVelocity = Double.NaN;
1605 maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude"));
1606 } catch (NumberFormatException ignore) { }
1608 maxVelocity = DocumentConfig.stringToDouble(attributes.get("maxvelocity"));
1609 } catch (NumberFormatException ignore) { }
1611 maxAcceleration = DocumentConfig.stringToDouble(attributes.get("maxacceleration"));
1612 } catch (NumberFormatException ignore) { }
1614 maxMach = DocumentConfig.stringToDouble(attributes.get("maxmach"));
1615 } catch (NumberFormatException ignore) { }
1617 timeToApogee = DocumentConfig.stringToDouble(attributes.get("timetoapogee"));
1618 } catch (NumberFormatException ignore) { }
1620 flightTime = DocumentConfig.stringToDouble(attributes.get("flighttime"));
1621 } catch (NumberFormatException ignore) { }
1624 DocumentConfig.stringToDouble(attributes.get("groundhitvelocity"));
1625 } catch (NumberFormatException ignore) { }
1627 data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
1628 timeToApogee, flightTime, groundHitVelocity);
1631 data.getWarningSet().addAll(warningSet);
1638 class FlightDataBranchHandler extends ElementHandler {
1639 private final FlightDataBranch.Type[] types;
1640 private final FlightDataBranch branch;
1642 public FlightDataBranchHandler(String name, String typeList) {
1643 String[] split = typeList.split(",");
1644 types = new FlightDataBranch.Type[split.length];
1645 for (int i=0; i < split.length; i++) {
1646 types[i] = FlightDataBranch.getType(split[i], UnitGroup.UNITS_NONE);
1649 // TODO: LOW: May throw an IllegalArgumentException
1650 branch = new FlightDataBranch(name, types);
1653 public FlightDataBranch getBranch() {
1659 public ElementHandler openElement(String element, HashMap<String, String> attributes,
1660 WarningSet warnings) {
1662 if (element.equals("datapoint"))
1663 return PlainTextHandler.INSTANCE;
1664 if (element.equals("event"))
1665 return PlainTextHandler.INSTANCE;
1667 warnings.add("Unknown element '"+element+"' encountered, ignoring.");
1673 public void closeElement(String element, HashMap<String, String> attributes,
1674 String content, WarningSet warnings) {
1676 if (element.equals("event")) {
1678 FlightEvent.Type type;
1681 time = DocumentConfig.stringToDouble(attributes.get("time"));
1682 } catch (NumberFormatException e) {
1683 warnings.add("Illegal event specification, ignoring.");
1687 type = (Type) DocumentConfig.findEnum(attributes.get("type"), FlightEvent.Type.class);
1689 warnings.add("Illegal event specification, ignoring.");
1693 branch.addEvent(time, new FlightEvent(type, time));
1697 if (!element.equals("datapoint")) {
1698 warnings.add("Unknown element '"+element+"' encountered, ignoring.");
1702 // element == "datapoint"
1705 // Check line format
1706 String[] split = content.split(",");
1707 if (split.length != types.length) {
1708 warnings.add("Data point did not contain correct amount of values, ignoring point.");
1712 // Parse the doubles
1713 double[] values = new double[split.length];
1714 for (int i=0; i < values.length; i++) {
1716 values[i] = DocumentConfig.stringToDouble(split[i]);
1717 } catch (NumberFormatException e) {
1718 warnings.add("Data point format error, ignoring point.");
1723 // Add point to branch
1725 for (int i=0; i < types.length; i++) {
1726 branch.setValue(types[i], values[i]);
1736 ///////////////// Setters implementation
1742 * Set the specified value to the given component.
1744 * @param component the component to which to set.
1745 * @param value the value within the element.
1746 * @param attributes attributes for the element.
1747 * @param warnings the warning set to use.
1749 public void set(RocketComponent component, String value,
1750 HashMap<String, String> attributes, WarningSet warnings);
1754 //// StringSetter - sets the value to the contained String
1755 class StringSetter implements Setter {
1756 private final Reflection.Method setMethod;
1758 public StringSetter(Reflection.Method set) {
1762 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1763 WarningSet warnings) {
1764 setMethod.invoke(c, s);
1768 //// IntSetter - set an integer value
1769 class IntSetter implements Setter {
1770 private final Reflection.Method setMethod;
1772 public IntSetter(Reflection.Method set) {
1776 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1777 WarningSet warnings) {
1779 int n = Integer.parseInt(s);
1780 setMethod.invoke(c, n);
1781 } catch (NumberFormatException e) {
1782 warnings.add(Warning.FILE_INVALID_PARAMETER);
1788 //// BooleanSetter - set a boolean value
1789 class BooleanSetter implements Setter {
1790 private final Reflection.Method setMethod;
1792 public BooleanSetter(Reflection.Method set) {
1796 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1797 WarningSet warnings) {
1800 if (s.equalsIgnoreCase("true")) {
1801 setMethod.invoke(c, true);
1802 } else if (s.equalsIgnoreCase("false")) {
1803 setMethod.invoke(c, false);
1805 warnings.add(Warning.FILE_INVALID_PARAMETER);
1812 //// DoubleSetter - sets a double value or (alternatively) if a specific string is encountered
1813 //// calls a setXXX(boolean) method.
1814 class DoubleSetter implements Setter {
1815 private final Reflection.Method setMethod;
1816 private final String specialString;
1817 private final Reflection.Method specialMethod;
1818 private final double multiplier;
1821 * Set only the double value.
1822 * @param set set method for the double value.
1824 public DoubleSetter(Reflection.Method set) {
1825 this.setMethod = set;
1826 this.specialString = null;
1827 this.specialMethod = null;
1828 this.multiplier = 1.0;
1832 * Multiply with the given multiplier and set the double value.
1833 * @param set set method for the double value.
1834 * @param mul multiplier.
1836 public DoubleSetter(Reflection.Method set, double mul) {
1837 this.setMethod = set;
1838 this.specialString = null;
1839 this.specialMethod = null;
1840 this.multiplier = mul;
1844 * Set the double value, or if the value equals the special string, use the
1845 * special setter and set it to true.
1847 * @param set double setter.
1848 * @param special special string
1849 * @param specialMethod boolean setter.
1851 public DoubleSetter(Reflection.Method set, String special,
1852 Reflection.Method specialMethod) {
1853 this.setMethod = set;
1854 this.specialString = special;
1855 this.specialMethod = specialMethod;
1856 this.multiplier = 1.0;
1860 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1861 WarningSet warnings) {
1865 // Check for special case
1866 if (specialMethod != null && s.equalsIgnoreCase(specialString)) {
1867 specialMethod.invoke(c, true);
1873 double d = Double.parseDouble(s);
1874 setMethod.invoke(c, d * multiplier);
1875 } catch (NumberFormatException e) {
1876 warnings.add(Warning.FILE_INVALID_PARAMETER);
1882 class OverrideSetter implements Setter {
1883 private final Reflection.Method setMethod;
1884 private final Reflection.Method enabledMethod;
1886 public OverrideSetter(Reflection.Method set, Reflection.Method enabledMethod) {
1887 this.setMethod = set;
1888 this.enabledMethod = enabledMethod;
1891 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1892 WarningSet warnings) {
1895 double d = Double.parseDouble(s);
1896 setMethod.invoke(c, d);
1897 enabledMethod.invoke(c, true);
1898 } catch (NumberFormatException e) {
1899 warnings.add(Warning.FILE_INVALID_PARAMETER);
1904 //// EnumSetter - sets a generic enum type
1905 class EnumSetter<T extends Enum<T>> implements Setter {
1906 private final Reflection.Method setter;
1907 private final Class<T> enumClass;
1909 public EnumSetter(Reflection.Method set, Class<T> enumClass) {
1911 this.enumClass = enumClass;
1915 public void set(RocketComponent c, String name, HashMap<String, String> attributes,
1916 WarningSet warnings) {
1918 Enum<?> setEnum = DocumentConfig.findEnum(name, enumClass);
1919 if (setEnum == null) {
1920 warnings.add(Warning.FILE_INVALID_PARAMETER);
1924 setter.invoke(c, setEnum);
1929 //// ColorSetter - sets a Color value
1930 class ColorSetter implements Setter {
1931 private final Reflection.Method setMethod;
1933 public ColorSetter(Reflection.Method set) {
1937 public void set(RocketComponent c, String s, HashMap<String, String> attributes,
1938 WarningSet warnings) {
1940 String red = attributes.get("red");
1941 String green = attributes.get("green");
1942 String blue = attributes.get("blue");
1944 if (red == null || green == null || blue == null) {
1945 warnings.add(Warning.FILE_INVALID_PARAMETER);
1951 r = Integer.parseInt(red);
1952 g = Integer.parseInt(green);
1953 b = Integer.parseInt(blue);
1954 } catch (NumberFormatException e) {
1955 warnings.add(Warning.FILE_INVALID_PARAMETER);
1959 if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
1960 warnings.add(Warning.FILE_INVALID_PARAMETER);
1964 Color color = new Color(r, g, b);
1965 setMethod.invoke(c, color);
1967 if (!s.trim().equals("")) {
1968 warnings.add(Warning.FILE_INVALID_PARAMETER);
1975 class MaterialSetter implements Setter {
1976 private final Reflection.Method setMethod;
1977 private final Material.Type type;
1979 public MaterialSetter(Reflection.Method set, Material.Type type) {
1980 this.setMethod = set;
1984 public void set(RocketComponent c, String name, HashMap<String, String> attributes,
1985 WarningSet warnings) {
1991 if (name.equals("")) {
1992 warnings.add(Warning.fromString("Illegal material specification, ignoring."));
1999 str = attributes.remove("density");
2001 warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2005 density = Double.parseDouble(str);
2006 } catch (NumberFormatException e) {
2007 warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2012 // double thickness = 0;
2013 // str = attributes.remove("thickness");
2016 // thickness = Double.parseDouble(str);
2017 // } catch (NumberFormatException e){
2018 // warnings.add(Warning.fromString("Illegal material specification, ignoring."));
2022 // Check type if specified
2023 str = attributes.remove("type");
2024 if (str != null && !type.name().toLowerCase().equals(str)) {
2025 warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
2029 mat = Material.newMaterial(type, name, density);
2031 setMethod.invoke(c, mat);
2038 class PositionSetter implements Setter {
2040 public void set(RocketComponent c, String value, HashMap<String, String> attributes,
2041 WarningSet warnings) {
2043 RocketComponent.Position type = (Position) DocumentConfig.findEnum(attributes.get("type"),
2044 RocketComponent.Position.class);
2046 warnings.add(Warning.FILE_INVALID_PARAMETER);
2052 pos = Double.parseDouble(value);
2053 } catch (NumberFormatException e) {
2054 warnings.add(Warning.FILE_INVALID_PARAMETER);
2058 if (c instanceof FinSet) {
2059 ((FinSet)c).setRelativePosition(type);
2060 c.setPositionValue(pos);
2061 } else if (c instanceof LaunchLug) {
2062 ((LaunchLug)c).setRelativePosition(type);
2063 c.setPositionValue(pos);
2064 } else if (c instanceof InternalComponent) {
2065 ((InternalComponent)c).setRelativePosition(type);
2066 c.setPositionValue(pos);
2068 warnings.add(Warning.FILE_INVALID_PARAMETER);
2076 class ClusterConfigurationSetter implements Setter {
2078 public void set(RocketComponent component, String value, HashMap<String, String> attributes,
2079 WarningSet warnings) {
2081 if (!(component instanceof Clusterable)) {
2082 warnings.add("Illegal component defined as cluster.");
2086 ClusterConfiguration config = null;
2087 for (ClusterConfiguration c: ClusterConfiguration.CONFIGURATIONS) {
2088 if (c.getXMLName().equals(value)) {
2094 if (config == null) {
2095 warnings.add("Illegal cluster configuration specified.");
2099 ((Clusterable)component).setClusterConfiguration(config);