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