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