4cd29afd0b670a06623d307136cfe072bbff2ee0
[debian/openrocket] / src / net / sf / openrocket / file / rocksim / RocksimHandler.java
1 /*
2  * RocksimHandler.java
3  *
4  */
5 package net.sf.openrocket.file.rocksim;
6
7 import net.sf.openrocket.aerodynamics.Warning;
8 import net.sf.openrocket.aerodynamics.WarningSet;
9 import net.sf.openrocket.document.OpenRocketDocument;
10 import net.sf.openrocket.file.simplesax.ElementHandler;
11 import net.sf.openrocket.file.simplesax.PlainTextHandler;
12 import net.sf.openrocket.rocketcomponent.Rocket;
13 import net.sf.openrocket.rocketcomponent.RocketComponent;
14 import net.sf.openrocket.rocketcomponent.Stage;
15 import org.xml.sax.SAXException;
16
17 import java.util.HashMap;
18
19 /**
20  * This class is a Sax element handler for Rocksim version 9 design files.  It parses the Rocksim file (typically
21  * a .rkt extension) and creates corresponding OpenRocket components.  This is a best effort approach and may not
22  * be an exact replica.
23  * <p/>
24  * Limitations: Rocksim flight simulations are not imported; tube fins are not supported; Rocksim 'pods' are not supported.
25  */
26 public class RocksimHandler extends ElementHandler {
27
28     /**
29      * Length conversion.  Rocksim is in millimeters, OpenRocket in meters.
30      */
31     public static final int ROCKSIM_TO_OPENROCKET_LENGTH = 1000;
32
33     /**
34      * Mass conversion.  Rocksim is in grams, OpenRocket in kilograms.
35      */
36     public static final int ROCKSIM_TO_OPENROCKET_MASS = 1000;
37
38     /**
39      * Density conversion.  Rocksim is in milligrams/cubic centimeter, OpenRocket in grams/cubic centimeter.
40      */
41     public static final int ROCKSIM_TO_OPENROCKET_DENSITY = 1;
42
43     /**
44      * Radius conversion.  Rocksim is always in diameters, OpenRocket mostly in radius.
45      */
46     public static final int ROCKSIM_TO_OPENROCKET_RADIUS = 2 * ROCKSIM_TO_OPENROCKET_LENGTH;
47
48     /**
49      * The main content handler.
50      */
51     private RocksimContentHandler handler = null;
52
53     /**
54      * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
55      * has not been read yet.
56      *
57      * @return the document read, or null.
58      */
59     public OpenRocketDocument getDocument() {
60         return handler.getDocument();
61     }
62
63     @Override
64     public ElementHandler openElement(String element, HashMap<String, String> attributes,
65                                       WarningSet warnings) {
66
67         // Check for unknown elements
68         if (!element.equals("RockSimDocument")) {
69             warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
70             return null;
71         }
72
73         // Check for first call
74         if (handler != null) {
75             warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
76                                             + "ones."));
77             return null;
78         }
79
80         handler = new RocksimContentHandler();
81         return handler;
82     }
83
84 }
85
86 /**
87  * Handles the content of the <DesignInformation> tag.
88  */
89 class RocksimContentHandler extends ElementHandler {
90     /**
91      * The OpenRocketDocument that is the container for the rocket.
92      */
93     private final OpenRocketDocument doc;
94
95     /**
96      * The top-level component, from which all child components are added.
97      */
98     private final Rocket rocket;
99
100     /**
101      * The rocksim file version.
102      */
103     private String version;
104
105     /**
106      * Constructor.
107      */
108     public RocksimContentHandler() {
109         this.rocket = new Rocket();
110         this.doc = new OpenRocketDocument(rocket);
111     }
112
113     /**
114      * Get the OpenRocket document that has been created from parsing the Rocksim design file.
115      *
116      * @return the instantiated OpenRocketDocument
117      */
118     public OpenRocketDocument getDocument() {
119         return doc;
120     }
121
122     @Override
123     public ElementHandler openElement(String element, HashMap<String, String> attributes,
124                                       WarningSet warnings) {
125         if ("DesignInformation".equals(element)) {
126             //The next sub-element is "RocketDesign", which is really the only thing that matters.  Rather than
127             //create another handler just for that element, handle it here.
128             return this;
129         }
130         if ("FileVersion".equals(element)) {
131             return PlainTextHandler.INSTANCE;
132         }
133         if ("RocketDesign".equals(element)) {
134             return new RocketDesignHandler(rocket);
135         }
136         return null;
137     }
138
139     @Override
140     public void closeElement(String element, HashMap<String, String> attributes,
141                              String content, WarningSet warnings) throws SAXException {
142         /**
143          * SAX handler for Rocksim file version number.  The value is not used currently, but could be used in the future
144          * for backward/forward compatibility reasons (different lower level handlers could be called via a strategy pattern).
145          */
146         if ("FileVersion".equals(element)) {
147             version = content;
148         }
149     }
150
151     /**
152      * Answer the file version.
153      *
154      * @return the version of the Rocksim design file
155      */
156     public String getVersion() {
157         return version;
158     }
159 }
160
161
162 /**
163  * A SAX handler for the high level Rocksim design.  This structure includes sub-structures for each of the stages.
164  * Correct functioning of this handler is predicated on the stage count element appearing before the actual stage parts
165  * structures.  If that invariant is not true, then behavior will be unpredictable.
166  */
167 class RocketDesignHandler extends ElementHandler {
168     /**
169      * The parent component.
170      */
171     private final RocketComponent component;
172     /**
173      * The parsed stage count.  Defaults to 1.
174      */
175     private int stageCount = 1;
176     /**
177      * The overridden stage 1 mass.
178      */
179     private double stage1Mass = 0d;
180     /**
181      * The overridden stage 2 mass.
182      */
183     private double stage2Mass = 0d;
184     /**
185      * The overridden stage 3 mass.
186      */
187     private double stage3Mass = 0d;
188     /**
189      * The overridden stage 1 Cg.
190      */
191     private double stage1CG = 0d;
192     /**
193      * The overridden stage 2 Cg.
194      */
195     private double stage2CG = 0d;
196     /**
197      * The overridden stage 3 Cg.
198      */
199     private double stage3CG = 0d;
200
201     /**
202      * Constructor.
203      *
204      * @param c the parent component
205      */
206     public RocketDesignHandler(RocketComponent c) {
207         component = c;
208     }
209
210     @Override
211     public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
212         /**
213          * In Rocksim stages are from the top down, so a single stage rocket is actually stage '3'.  A 2-stage 
214          * rocket defines stage '2' as the initial booster with stage '3' sitting atop it.  And so on.
215          */
216         if ("Stage3Parts".equals(element)) {
217             final Stage stage = new Stage();
218             if (stage3Mass > 0.0d) {
219                 stage.setMassOverridden(true);
220                 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
221                 stage.setOverrideMass(stage3Mass);
222             }
223             if (stage3CG > 0.0d) {
224                 stage.setCGOverridden(true);
225                 stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
226                 stage.setOverrideCGX(stage3CG);
227             }
228             component.addChild(stage);
229             return new StageHandler(stage);
230         }
231         if ("Stage2Parts".equals(element)) {
232             if (stageCount >= 2) {
233                 final Stage stage = new Stage();
234                 if (stage2Mass > 0.0d) {
235                     stage.setMassOverridden(true);
236                     stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
237                     stage.setOverrideMass(stage2Mass);
238                 }
239                 if (stage2CG > 0.0d) {
240                     stage.setCGOverridden(true);
241                     stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
242                     stage.setOverrideCGX(stage2CG);
243                 }
244                 component.addChild(stage);
245                 return new StageHandler(stage);
246             }
247         }
248         if ("Stage1Parts".equals(element)) {
249             if (stageCount == 3) {
250                 final Stage stage = new Stage();
251                 if (stage1Mass > 0.0d) {
252                     stage.setMassOverridden(true);
253                     stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
254                     stage.setOverrideMass(stage1Mass);
255                 }
256                 if (stage1CG > 0.0d) {
257                     stage.setCGOverridden(true);
258                     stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override
259                     stage.setOverrideCGX(stage1CG);
260                 }
261                 component.addChild(stage);
262                 return new StageHandler(stage);
263             }
264         }
265         if ("Name".equals(element)) {
266             return PlainTextHandler.INSTANCE;
267         }
268         if ("StageCount".equals(element)) {
269             return PlainTextHandler.INSTANCE;
270         }
271         if ("Stage3Mass".equals(element)) {
272             return PlainTextHandler.INSTANCE;
273         }
274         if ("Stage2Mass".equals(element)) {
275             return PlainTextHandler.INSTANCE;
276         }
277         if ("Stage1Mass".equals(element)) {
278             return PlainTextHandler.INSTANCE;
279         }
280         if ("Stage3CG".equals(element)) {
281             return PlainTextHandler.INSTANCE;
282         }
283         if ("Stage2CGAlone".equals(element)) {
284             return PlainTextHandler.INSTANCE;
285         }
286         if ("Stage1CGAlone".equals(element)) {
287             return PlainTextHandler.INSTANCE;
288         }
289         return null;
290     }
291
292     @Override
293     public void closeElement(String element, HashMap<String, String> attributes,
294                              String content, WarningSet warnings) throws SAXException {
295         try {
296             if ("Name".equals(element)) {
297                 component.setName(content);
298             }
299             if ("StageCount".equals(element)) {
300                 stageCount = Integer.parseInt(content);
301             }
302             if ("Stage3Mass".equals(element)) {
303                 stage3Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
304             }
305             if ("Stage2Mass".equals(element)) {
306                 stage2Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
307             }
308             if ("Stage1Mass".equals(element)) {
309                 stage1Mass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
310             }
311             if ("Stage3CG".equals(element)) {
312                 stage3CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
313             }
314             if ("Stage2CGAlone".equals(element)) {
315                 stage2CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
316             }
317             if ("Stage1CGAlone".equals(element)) {
318                 stage1CG = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
319             }
320         }
321         catch (NumberFormatException nfe) {
322             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
323         }
324     }
325
326 }
327
328 /**
329  * A SAX handler for a Rocksim stage.
330  */
331 class StageHandler extends ElementHandler {
332     /**
333      * The parent OpenRocket component.
334      */
335     private final RocketComponent component;
336
337     /**
338      * Constructor.
339      *
340      * @param c the parent component
341      * @throws IllegalArgumentException thrown if <code>c</code> is null
342      */
343     public StageHandler(RocketComponent c) throws IllegalArgumentException {
344         if (c == null) {
345             throw new IllegalArgumentException("The stage component may not be null.");
346         }
347         component = c;
348     }
349
350     @Override
351     public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
352         if ("NoseCone".equals(element)) {
353             return new NoseConeHandler(component);
354         }
355         if ("BodyTube".equals(element)) {
356             return new BodyTubeHandler(component);
357         }
358         if ("Transition".equals(element)) {
359             return new TransitionHandler(component);
360         }
361         return null;
362     }
363 }