1 package net.sf.openrocket.file.motor;
3 import java.io.IOException;
5 import java.nio.charset.Charset;
6 import java.util.ArrayList;
7 import java.util.HashMap;
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;
23 import org.xml.sax.InputSource;
24 import org.xml.sax.SAXException;
26 public class RockSimMotorLoader extends MotorLoader {
28 public static final String CHARSET_NAME = "UTF-8";
30 public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
33 /** Any delay longed than this will be interpreted as a plugged motor. */
34 private static final int DELAY_LIMIT = 90;
39 protected Charset getDefaultCharset() {
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
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.
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.
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();
65 SimpleSAX.readXML(source, handler, warnings);
66 return handler.getMotors();
67 } catch (SAXException e) {
68 throw new IOException(e.getMessage(), e);
75 * Initial handler for the RockSim engine files.
77 private static class RSEHandler extends ElementHandler {
78 private final List<Motor> motors = new ArrayList<Motor>();
80 private RSEMotorHandler motorHandler;
82 public List<Motor> getMotors() {
87 public ElementHandler openElement(String element,
88 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
90 if (element.equals("engine-database") ||
91 element.equals("engine-list")) {
92 // Ignore <engine-database> and <engine-list> elements
96 if (element.equals("version")) {
97 // Ignore <version> elements completely
101 if (element.equals("engine")) {
102 motorHandler = new RSEMotorHandler(attributes);
110 public void closeElement(String element, HashMap<String, String> attributes,
111 String content, WarningSet warnings) throws SAXException {
113 if (element.equals("engine")) {
114 Motor motor = motorHandler.getMotor();
122 * Handler for a RockSim engine file <motor> element.
124 private static class RSEMotorHandler extends ElementHandler {
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;
137 private String description = "";
139 private List<Double> time;
140 private List<Double> force;
141 private List<Double> mass;
142 private List<Double> cg;
144 private RSEMotorDataHandler dataHandler = null;
147 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
151 str = attributes.get("mfg");
153 throw new SAXException("Manufacturer missing");
157 str = attributes.get("code");
159 throw new SAXException("Designation missing");
160 designation = removeDelay(str);
163 ArrayList<Double> delayList = new ArrayList<Double>();
164 str = attributes.get("delays");
166 String[] split = str.split(",");
167 for (String delay : split) {
170 double d = Double.parseDouble(delay);
171 if (d >= DELAY_LIMIT)
175 } catch (NumberFormatException e) {
176 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
177 delayList.add(Motor.PLUGGED);
182 delays = new double[delayList.size()];
183 for (int i = 0; i < delayList.size(); i++) {
184 delays[i] = delayList.get(i);
188 str = attributes.get("dia");
190 throw new SAXException("Diameter missing");
192 diameter = Double.parseDouble(str) / 1000.0;
193 } catch (NumberFormatException e) {
194 throw new SAXException("Invalid diameter " + str);
198 str = attributes.get("len");
200 throw new SAXException("Length missing");
202 length = Double.parseDouble(str) / 1000.0;
203 } catch (NumberFormatException e) {
204 throw new SAXException("Invalid length " + str);
208 str = attributes.get("initWt");
210 throw new SAXException("Initial mass missing");
212 initMass = Double.parseDouble(str) / 1000.0;
213 } catch (NumberFormatException e) {
214 throw new SAXException("Invalid initial mass " + str);
218 str = attributes.get("propWt");
220 throw new SAXException("Propellant mass missing");
222 propMass = Double.parseDouble(str) / 1000.0;
223 } catch (NumberFormatException e) {
224 throw new SAXException("Invalid propellant mass " + str);
227 if (propMass > initMass) {
228 throw new SAXException("Propellant weight exceeds total weight in " +
229 "RockSim engine format");
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;
241 type = Motor.Type.UNKNOWN;
245 str = attributes.get("auto-calc-mass");
246 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
247 calculateMass = false;
249 calculateMass = true;
253 str = attributes.get("auto-calc-cg");
254 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
262 public ElementHandler openElement(String element,
263 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
265 if (element.equals("comments")) {
266 return PlainTextHandler.INSTANCE;
269 if (element.equals("data")) {
270 if (dataHandler != null) {
271 throw new SAXException("Multiple data elements encountered in motor " +
274 dataHandler = new RSEMotorDataHandler();
278 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
283 public void closeElement(String element, HashMap<String, String> attributes,
284 String content, WarningSet warnings) {
286 if (element.equals("comments")) {
287 if (description.length() > 0) {
288 description = description + "\n\n" + content.trim();
290 description = content.trim();
295 if (element.equals("data")) {
296 time = dataHandler.getTime();
297 force = dataHandler.getForce();
298 mass = dataHandler.getMass();
299 cg = dataHandler.getCG();
301 sortLists(time, force, mass, cg);
303 for (double d : mass) {
304 if (Double.isNaN(d)) {
305 calculateMass = true;
309 for (double d : cg) {
310 if (Double.isNaN(d)) {
319 public Motor getMotor() throws SAXException {
320 if (time == null || time.size() == 0)
321 throw new SAXException("Illegal motor data");
324 finalizeThrustCurve(time, force, mass, cg);
325 final int n = time.size();
327 if (hasIllegalValue(mass))
328 calculateMass = true;
329 if (hasIllegalValue(cg))
333 mass = calculateMass(time, force, initMass, propMass);
336 for (int i = 0; i < n; i++) {
337 cg.set(i, length / 2);
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));
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));
355 motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass - propMass);
358 motorDigest.update(DataType.CG_PER_TIME, toArray(cg));
360 motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
361 // TODO: HIGH: Motor digest?
362 // final String digest = motorDigest.getDigest();
366 return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer),
367 designation, description, type,
368 delays, diameter, length, timeArray, thrustArray, cgArray);
369 } catch (IllegalArgumentException e) {
370 throw new SAXException("Illegal motor data", e);
377 * Handler for the <data> element in a RockSim engine file motor definition.
379 private static class RSEMotorDataHandler extends ElementHandler {
381 private final List<Double> time = new ArrayList<Double>();
382 private final List<Double> force = new ArrayList<Double>();
383 private final List<Double> mass = new ArrayList<Double>();
384 private final List<Double> cg = new ArrayList<Double>();
387 public List<Double> getTime() {
391 public List<Double> getForce() {
395 public List<Double> getMass() {
399 public List<Double> getCG() {
405 public ElementHandler openElement(String element,
406 HashMap<String, String> attributes, WarningSet warnings) {
408 if (element.equals("eng-data")) {
409 return NullElementHandler.INSTANCE;
412 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
417 public void closeElement(String element, HashMap<String, String> attributes,
418 String content, WarningSet warnings) throws SAXException {
420 double t = parseDouble(attributes.get("t"));
421 double f = parseDouble(attributes.get("f"));
422 double m = parseDouble(attributes.get("m")) / 1000.0;
423 double g = parseDouble(attributes.get("cg")) / 1000.0;
425 if (Double.isNaN(t) || Double.isNaN(f)) {
426 throw new SAXException("Illegal motor data point encountered");
436 private double parseDouble(String str) {
440 return Double.parseDouble(str);
441 } catch (NumberFormatException e) {
449 private static boolean hasIllegalValue(List<Double> list) {
450 for (Double d : list) {
451 if (d == null || d.isNaN() || d.isInfinite()) {
458 private static double[] toArray(List<Double> list) {
459 final int n = list.size();
460 double[] array = new double[n];
461 for (int i = 0; i < n; i++) {
462 array[i] = list.get(i);