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