1 package net.sf.openrocket.file;
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.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;
20 import org.xml.sax.InputSource;
21 import org.xml.sax.SAXException;
23 public class RockSimMotorLoader extends MotorLoader {
25 public static final String CHARSET_NAME = "UTF-8";
27 public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
30 /** Any delay longed than this will be interpreted as a plugged motor. */
31 private static final int DELAY_LIMIT = 90;
36 protected Charset getDefaultCharset() {
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
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.
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.
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();
62 SimpleSAX.readXML(source, handler, warnings);
63 return handler.getMotors();
64 } catch (SAXException e) {
65 throw new IOException(e.getMessage(), e);
72 * Initial handler for the RockSim engine files.
74 private static class RSEHandler extends ElementHandler {
75 private final List<Motor> motors = new ArrayList<Motor>();
77 private RSEMotorHandler motorHandler;
79 public List<Motor> getMotors() {
84 public ElementHandler openElement(String element,
85 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
87 if (element.equals("engine-database") ||
88 element.equals("engine-list")) {
89 // Ignore <engine-database> and <engine-list> elements
93 if (element.equals("version")) {
94 // Ignore <version> elements completely
98 if (element.equals("engine")) {
99 motorHandler = new RSEMotorHandler(attributes);
107 public void closeElement(String element, HashMap<String, String> attributes,
108 String content, WarningSet warnings) throws SAXException {
110 if (element.equals("engine")) {
111 Motor motor = motorHandler.getMotor();
119 * Handler for a RockSim engine file <motor> element.
121 private static class RSEMotorHandler extends ElementHandler {
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;
134 private String description = "";
136 private List<Double> time;
137 private List<Double> force;
138 private List<Double> mass;
139 private List<Double> cg;
141 private RSEMotorDataHandler dataHandler = null;
144 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
148 str = attributes.get("mfg");
150 throw new SAXException("Manufacturer missing");
154 str = attributes.get("code");
156 throw new SAXException("Designation missing");
157 designation = removeDelay(str);
160 ArrayList<Double> delayList = new ArrayList<Double>();
161 str = attributes.get("delays");
163 String[] split = str.split(",");
164 for (String delay: split) {
167 double d = Double.parseDouble(delay);
168 if (d >= DELAY_LIMIT)
172 } catch (NumberFormatException e) {
173 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
174 delayList.add(Motor.PLUGGED);
179 delays = new double[delayList.size()];
180 for (int i=0; i<delayList.size(); i++) {
181 delays[i] = delayList.get(i);
185 str = attributes.get("dia");
187 throw new SAXException("Diameter missing");
189 diameter = Double.parseDouble(str) / 1000.0;
190 } catch (NumberFormatException e) {
191 throw new SAXException("Invalid diameter " + str);
195 str = attributes.get("len");
197 throw new SAXException("Length missing");
199 length = Double.parseDouble(str) / 1000.0;
200 } catch (NumberFormatException e) {
201 throw new SAXException("Invalid length " + str);
205 str = attributes.get("initWt");
207 throw new SAXException("Initial mass missing");
209 initMass = Double.parseDouble(str) / 1000.0;
210 } catch (NumberFormatException e) {
211 throw new SAXException("Invalid initial mass " + str);
215 str = attributes.get("propWt");
217 throw new SAXException("Propellant mass missing");
219 propMass = Double.parseDouble(str) / 1000.0;
220 } catch (NumberFormatException e) {
221 throw new SAXException("Invalid propellant mass " + str);
224 if (propMass > initMass) {
225 throw new SAXException("Propellant weight exceeds total weight in " +
226 "RockSim engine format");
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;
238 type = Motor.Type.UNKNOWN;
242 str = attributes.get("auto-calc-mass");
243 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
244 calculateMass = false;
246 calculateMass = true;
250 str = attributes.get("auto-calc-cg");
251 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
259 public ElementHandler openElement(String element,
260 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
262 if (element.equals("comments")) {
263 return PlainTextHandler.INSTANCE;
266 if (element.equals("data")) {
267 if (dataHandler != null) {
268 throw new SAXException("Multiple data elements encountered in motor " +
271 dataHandler = new RSEMotorDataHandler();
275 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
280 public void closeElement(String element, HashMap<String, String> attributes,
281 String content, WarningSet warnings) {
283 if (element.equals("comments")) {
284 if (description.length() > 0) {
285 description = description + "\n\n" + content.trim();
287 description = content.trim();
292 if (element.equals("data")) {
293 time = dataHandler.getTime();
294 force = dataHandler.getForce();
295 mass = dataHandler.getMass();
296 cg = dataHandler.getCG();
298 sortLists(time, force, mass, cg);
300 for (double d: mass) {
301 if (Double.isNaN(d)) {
302 calculateMass = true;
307 if (Double.isNaN(d)) {
316 public Motor getMotor() throws SAXException {
317 if (time == null || time.size() == 0)
318 throw new SAXException("Illegal motor data");
321 finalizeThrustCurve(time, force, mass, cg);
322 final int n = time.size();
325 mass = calculateMass(time, force, initMass, propMass);
328 for (int i=0; i < n; i++) {
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));
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);
354 * Handler for the <data> element in a RockSim engine file motor definition.
356 private static class RSEMotorDataHandler extends ElementHandler {
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>();
364 public List<Double> getTime() {
367 public List<Double> getForce() {
370 public List<Double> getMass() {
373 public List<Double> getCG() {
379 public ElementHandler openElement(String element,
380 HashMap<String, String> attributes, WarningSet warnings) {
382 if (element.equals("eng-data")) {
383 return NullElementHandler.INSTANCE;
386 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
391 public void closeElement(String element, HashMap<String, String> attributes,
392 String content, WarningSet warnings) throws SAXException {
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;
399 if (Double.isNaN(t) || Double.isNaN(f)) {
400 throw new SAXException("Illegal motor data point encountered");
410 private double parseDouble(String str) {
414 return Double.parseDouble(str);
415 } catch (NumberFormatException e) {