updates for 0.9.3
[debian/openrocket] / src / net / sf / openrocket / file / RockSimMotorLoader.java
1 package net.sf.openrocket.file;
2
3 import java.io.IOException;
4 import java.io.Reader;
5 import java.nio.charset.Charset;
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.List;
9
10 import net.sf.openrocket.aerodynamics.WarningSet;
11 import net.sf.openrocket.file.simplesax.ElementHandler;
12 import net.sf.openrocket.file.simplesax.NullElementHandler;
13 import net.sf.openrocket.file.simplesax.PlainTextHandler;
14 import net.sf.openrocket.file.simplesax.SimpleSAX;
15 import net.sf.openrocket.motor.Manufacturer;
16 import net.sf.openrocket.motor.Motor;
17 import net.sf.openrocket.motor.ThrustCurveMotor;
18 import net.sf.openrocket.util.Coordinate;
19
20 import org.xml.sax.InputSource;
21 import org.xml.sax.SAXException;
22
23 public class RockSimMotorLoader extends MotorLoader {
24         
25         public static final String CHARSET_NAME = "UTF-8";
26         
27         public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
28
29         
30         /** Any delay longed than this will be interpreted as a plugged motor. */
31         private static final int DELAY_LIMIT = 90;
32         
33
34         
35         @Override
36         protected Charset getDefaultCharset() {
37                 return CHARSET;
38         }
39
40         
41
42         /**
43          * Load a <code>Motor</code> from a RockSim motor definition file specified by the 
44          * <code>Reader</code>. The <code>Reader</code> is responsible for using the correct 
45          * charset.
46          * <p>
47          * If automatic CG/mass calculation is used, then the CG is assumed to be located at 
48          * the center of the motor casing and the mass is calculated from the thrust curve 
49          * by assuming a constant exhaust velocity.
50          * 
51          * @param reader  the source of the file.
52          * @return                a list of the {@link Motor} objects defined in the file.
53          * @throws IOException  if an I/O error occurs or if the file format is invalid.
54          */
55         @Override
56         public List<Motor> load(Reader reader, String filename) throws IOException {
57                 InputSource source = new InputSource(reader);
58                 RSEHandler handler = new RSEHandler();
59                 WarningSet warnings = new WarningSet();
60                 
61                 try {
62                         SimpleSAX.readXML(source, handler, warnings);
63                         return handler.getMotors();
64                 } catch (SAXException e) {
65                         throw new IOException(e.getMessage(), e);
66                 }
67         }
68         
69         
70         
71         /**
72          * Initial handler for the RockSim engine files.
73          */
74         private static class RSEHandler extends ElementHandler {
75                 private final List<Motor> motors = new ArrayList<Motor>();
76                 
77                 private RSEMotorHandler motorHandler;
78                 
79                 public List<Motor> getMotors() {
80                         return motors;
81                 }
82                 
83                 @Override
84                 public ElementHandler openElement(String element,
85                                 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
86
87                         if (element.equals("engine-database") ||
88                                         element.equals("engine-list")) {
89                                 // Ignore <engine-database> and <engine-list> elements
90                                 return this;
91                         }
92                         
93                         if (element.equals("version")) {
94                                 // Ignore <version> elements completely
95                                 return null;
96                         }
97                         
98                         if (element.equals("engine")) {
99                                 motorHandler = new RSEMotorHandler(attributes);
100                                 return motorHandler;
101                         }
102                         
103                         return null;
104                 }
105
106                 @Override
107                 public void closeElement(String element, HashMap<String, String> attributes,
108                                 String content, WarningSet warnings) throws SAXException {
109
110                         if (element.equals("engine")) {
111                                 Motor motor = motorHandler.getMotor();
112                                 motors.add(motor);
113                         }
114                 }
115         }
116         
117         
118         /**
119          * Handler for a RockSim engine file <motor> element.
120          */
121         private static class RSEMotorHandler extends ElementHandler {
122
123                 private final String manufacturer;
124                 private final String designation;
125                 private final double[] delays;
126                 private final double diameter;
127                 private final double length;
128                 private final double initMass;
129                 private final double propMass;
130                 private final Motor.Type type;
131                 private boolean calculateMass;
132                 private boolean calculateCG;
133                 
134                 private String description = "";
135                 
136                 private List<Double> time;
137                 private List<Double> force;
138                 private List<Double> mass;
139                 private List<Double> cg;
140                 
141                 private RSEMotorDataHandler dataHandler = null;
142
143                 
144                 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
145                         String str;
146                         
147                         // Manufacturer
148                         str = attributes.get("mfg");
149                         if (str == null)
150                                 throw new SAXException("Manufacturer missing");
151                         manufacturer = str;
152                         
153                         // Designation
154                         str = attributes.get("code");
155                         if (str == null)
156                                 throw new SAXException("Designation missing");
157                         designation = removeDelay(str);
158                         
159                         // Delays
160                         ArrayList<Double> delayList = new ArrayList<Double>();
161                         str = attributes.get("delays");
162                         if (str != null) {
163                                 String[] split = str.split(",");
164                                 for (String delay: split) {
165                                         try {
166                                                 
167                                                 double d = Double.parseDouble(delay);
168                                                 if (d >= DELAY_LIMIT)
169                                                         d = Motor.PLUGGED;
170                                                 delayList.add(d);
171                                                 
172                                         } catch (NumberFormatException e) {
173                                                 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
174                                                         delayList.add(Motor.PLUGGED);
175                                                 }
176                                         }
177                                 }
178                         }
179                         delays = new double[delayList.size()];
180                         for (int i=0; i<delayList.size(); i++) {
181                                 delays[i] = delayList.get(i);
182                         }
183                         
184                         // Diameter
185                         str = attributes.get("dia");
186                         if (str == null)
187                                 throw new SAXException("Diameter missing");
188                         try {
189                                 diameter = Double.parseDouble(str) / 1000.0;
190                         } catch (NumberFormatException e) {
191                                 throw new SAXException("Invalid diameter " + str);
192                         }
193                         
194                         // Length
195                         str = attributes.get("len");
196                         if (str == null)
197                                 throw new SAXException("Length missing");
198                         try {
199                                 length = Double.parseDouble(str) / 1000.0;
200                         } catch (NumberFormatException e) {
201                                 throw new SAXException("Invalid length " + str);
202                         }
203                         
204                         // Initial mass
205                         str = attributes.get("initWt");
206                         if (str == null)
207                                 throw new SAXException("Initial mass missing");
208                         try {
209                                 initMass = Double.parseDouble(str) / 1000.0;
210                         } catch (NumberFormatException e) {
211                                 throw new SAXException("Invalid initial mass " + str);
212                         }
213                         
214                         // Propellant mass
215                         str = attributes.get("propWt");
216                         if (str == null)
217                                 throw new SAXException("Propellant mass missing");
218                         try {
219                                 propMass = Double.parseDouble(str) / 1000.0;
220                         } catch (NumberFormatException e) {
221                                 throw new SAXException("Invalid propellant mass " + str);
222                         }
223                         
224                         if (propMass > initMass) {
225                                 throw new SAXException("Propellant weight exceeds total weight in " +
226                                                 "RockSim engine format");
227                         }
228                         
229                         // Motor type
230                         str = attributes.get("Type");
231                         if (str != null && str.equalsIgnoreCase("single-use")) {
232                                 type = Motor.Type.SINGLE;
233                         } else if (str != null && str.equalsIgnoreCase("hybrid")) {
234                                 type = Motor.Type.HYBRID;
235                         } else if (str != null && str.equalsIgnoreCase("reloadable")) {
236                                 type = Motor.Type.RELOAD;
237                         } else {
238                                 type = Motor.Type.UNKNOWN;
239                         }
240                         
241                         // Calculate mass
242                         str = attributes.get("auto-calc-mass");
243                         if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
244                                 calculateMass = false;
245                         } else {
246                                 calculateMass = true;
247                         }
248                         
249                         // Calculate CG
250                         str = attributes.get("auto-calc-cg");
251                         if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
252                                 calculateCG = false;
253                         } else {
254                                 calculateCG = true;
255                         }
256                 }
257                 
258                 @Override
259                 public ElementHandler openElement(String element,
260                                 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
261
262                         if (element.equals("comments")) {
263                                 return PlainTextHandler.INSTANCE;
264                         }
265                         
266                         if (element.equals("data")) {
267                                 if (dataHandler != null) {
268                                         throw new SAXException("Multiple data elements encountered in motor " +
269                                                         "definition");
270                                 }
271                                 dataHandler = new RSEMotorDataHandler();
272                                 return dataHandler;
273                         }
274
275                         warnings.add("Unknown element '" + element + "' encountered, ignoring.");
276                         return null;
277                 }
278
279                 @Override
280                 public void closeElement(String element, HashMap<String, String> attributes,
281                                 String content, WarningSet warnings) {
282
283                         if (element.equals("comments")) {
284                                 if (description.length() > 0) {
285                                         description = description + "\n\n" + content.trim();
286                                 } else {
287                                         description = content.trim();
288                                 }
289                                 return;
290                         }
291                         
292                         if (element.equals("data")) {
293                                 time = dataHandler.getTime();
294                                 force = dataHandler.getForce();
295                                 mass = dataHandler.getMass();
296                                 cg = dataHandler.getCG();
297                                 
298                                 sortLists(time, force, mass, cg);
299                                 
300                                 for (double d: mass) {
301                                         if (Double.isNaN(d)) {
302                                                 calculateMass = true;
303                                                 break;
304                                         }
305                                 }
306                                 for (double d: cg) {
307                                         if (Double.isNaN(d)) {
308                                                 calculateCG = true;
309                                                 break;
310                                         }
311                                 }
312                                 return;
313                         }
314                 }
315                 
316                 public Motor getMotor() throws SAXException {
317                         if (time == null || time.size() == 0)
318                                 throw new SAXException("Illegal motor data");
319
320                         
321                         finalizeThrustCurve(time, force, mass, cg);
322                         final int n = time.size();
323                         
324                         if (calculateMass) {
325                                 mass = calculateMass(time, force, initMass, propMass);
326                         }
327                         if (calculateCG) {
328                                 for (int i=0; i < n; i++) {
329                                         cg.set(i, length/2);
330                                 }
331                         }
332                         
333                         double[] timeArray = new double[n];
334                         double[] thrustArray = new double[n];
335                         Coordinate[] cgArray = new Coordinate[n];
336                         for (int i=0; i < n; i++) {
337                                 timeArray[i] = time.get(i);
338                                 thrustArray[i] = force.get(i);
339                                 cgArray[i] = new Coordinate(cg.get(i),0,0,mass.get(i));
340                         }
341                         
342                         try {
343                                 return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
344                                                 designation, description, type,
345                                                 delays, diameter, length, timeArray, thrustArray, cgArray);
346                         } catch (IllegalArgumentException e) {
347                                 throw new SAXException("Illegal motor data", e);
348                         }
349                 }
350         }
351         
352         
353         /**
354          * Handler for the <data> element in a RockSim engine file motor definition.
355          */
356         private static class RSEMotorDataHandler extends ElementHandler {
357                 
358                 private final List<Double> time = new ArrayList<Double>();
359                 private final List<Double> force = new ArrayList<Double>();
360                 private final List<Double> mass = new ArrayList<Double>();
361                 private final List<Double> cg = new ArrayList<Double>();
362                 
363                 
364                 public List<Double> getTime() {
365                         return time;
366                 }
367                 public List<Double> getForce() {
368                         return force;
369                 }
370                 public List<Double> getMass() {
371                         return mass;
372                 }
373                 public List<Double> getCG() {
374                         return cg;
375                 }
376                 
377                 
378                 @Override
379                 public ElementHandler openElement(String element,
380                                 HashMap<String, String> attributes, WarningSet warnings) {
381
382                         if (element.equals("eng-data")) {
383                                 return NullElementHandler.INSTANCE;
384                         }
385                         
386                         warnings.add("Unknown element '" + element + "' encountered, ignoring.");
387                         return null;
388                 }
389
390                 @Override
391                 public void closeElement(String element, HashMap<String, String> attributes,
392                                 String content, WarningSet warnings) throws SAXException {
393
394                         double t = parseDouble(attributes.get("t"));
395                         double f = parseDouble(attributes.get("f"));
396                         double m = parseDouble(attributes.get("m")) / 1000.0;
397                         double g = parseDouble(attributes.get("cg")) / 1000.0;
398                         
399                         if (Double.isNaN(t) || Double.isNaN(f)) {
400                                 throw new SAXException("Illegal motor data point encountered");
401                         }
402                         
403                         time.add(t);
404                         force.add(f);
405                         mass.add(m);
406                         cg.add(g);
407                 }
408                 
409                 
410                 private double parseDouble(String str) {
411                         if (str == null)
412                                 return Double.NaN;
413                         try {
414                                 return Double.parseDouble(str);
415                         } catch (NumberFormatException e) {
416                                 return Double.NaN;
417                         }
418                 }
419         }
420 }