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