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.rocketcomponent.Motor;
16 import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
17 import net.sf.openrocket.util.Coordinate;
19 import org.xml.sax.InputSource;
20 import org.xml.sax.SAXException;
22 public class RockSimMotorLoader extends MotorLoader {
24 public static final String CHARSET_NAME = "UTF-8";
26 public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
29 /** Any delay longed than this will be interpreted as a plugged motor. */
30 private static final int DELAY_LIMIT = 90;
35 protected Charset getDefaultCharset() {
42 * Load a <code>Motor</code> from a RockSim motor definition file specified by the
43 * <code>Reader</code>. The <code>Reader</code> is responsible for using the correct
46 * If automatic CG/mass calculation is used, then the CG is assumed to be located at
47 * the center of the motor casing and the mass is calculated from the thrust curve
48 * by assuming a constant exhaust velocity.
50 * @param reader the source of the file.
51 * @return a list of the {@link Motor} objects defined in the file.
52 * @throws IOException if an I/O error occurs or if the file format is invalid.
55 public List<Motor> load(Reader reader, String filename) throws IOException {
56 InputSource source = new InputSource(reader);
57 RSEHandler handler = new RSEHandler();
58 WarningSet warnings = new WarningSet();
61 SimpleSAX.readXML(source, handler, warnings);
62 return handler.getMotors();
63 } catch (SAXException e) {
64 throw new IOException(e.getMessage(), e);
71 * Initial handler for the RockSim engine files.
73 private static class RSEHandler extends ElementHandler {
74 private final List<Motor> motors = new ArrayList<Motor>();
76 private RSEMotorHandler motorHandler;
78 public List<Motor> getMotors() {
83 public ElementHandler openElement(String element,
84 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
86 if (element.equals("engine-database") ||
87 element.equals("engine-list")) {
88 // Ignore <engine-database> and <engine-list> elements
92 if (element.equals("version")) {
93 // Ignore <version> elements completely
97 if (element.equals("engine")) {
98 motorHandler = new RSEMotorHandler(attributes);
106 public void closeElement(String element, HashMap<String, String> attributes,
107 String content, WarningSet warnings) throws SAXException {
109 if (element.equals("engine")) {
110 Motor motor = motorHandler.getMotor();
118 * Handler for a RockSim engine file <motor> element.
120 private static class RSEMotorHandler extends ElementHandler {
122 private final String manufacturer;
123 private final String designation;
124 private final double[] delays;
125 private final double diameter;
126 private final double length;
127 private final double initMass;
128 private final double propMass;
129 private final Motor.Type type;
130 private boolean calculateMass;
131 private boolean calculateCG;
133 private String description = "";
135 private List<Double> time;
136 private List<Double> force;
137 private List<Double> mass;
138 private List<Double> cg;
140 private RSEMotorDataHandler dataHandler = null;
143 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
147 str = attributes.get("mfg");
149 throw new SAXException("Manufacturer missing");
150 manufacturer = convertManufacturer(str);
153 str = attributes.get("code");
155 throw new SAXException("Designation missing");
156 designation = removeDelay(str);
159 ArrayList<Double> delayList = new ArrayList<Double>();
160 str = attributes.get("delays");
162 String[] split = str.split(",");
163 for (String delay: split) {
166 double d = Double.parseDouble(delay);
167 if (d >= DELAY_LIMIT)
171 } catch (NumberFormatException e) {
172 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
173 delayList.add(Motor.PLUGGED);
178 delays = new double[delayList.size()];
179 for (int i=0; i<delayList.size(); i++) {
180 delays[i] = delayList.get(i);
184 str = attributes.get("dia");
186 throw new SAXException("Diameter missing");
188 diameter = Double.parseDouble(str) / 1000.0;
189 } catch (NumberFormatException e) {
190 throw new SAXException("Invalid diameter " + str);
194 str = attributes.get("len");
196 throw new SAXException("Length missing");
198 length = Double.parseDouble(str) / 1000.0;
199 } catch (NumberFormatException e) {
200 throw new SAXException("Invalid length " + str);
204 str = attributes.get("initWt");
206 throw new SAXException("Initial mass missing");
208 initMass = Double.parseDouble(str) / 1000.0;
209 } catch (NumberFormatException e) {
210 throw new SAXException("Invalid initial mass " + str);
214 str = attributes.get("propWt");
216 throw new SAXException("Propellant mass missing");
218 propMass = Double.parseDouble(str) / 1000.0;
219 } catch (NumberFormatException e) {
220 throw new SAXException("Invalid propellant mass " + str);
223 if (propMass > initMass) {
224 throw new SAXException("Propellant weight exceeds total weight in " +
225 "RockSim engine format");
229 str = attributes.get("Type");
230 if (str != null && str.equalsIgnoreCase("single-use")) {
231 type = Motor.Type.SINGLE;
232 } else if (str != null && str.equalsIgnoreCase("hybrid")) {
233 type = Motor.Type.HYBRID;
234 } else if (str != null && str.equalsIgnoreCase("reloadable")) {
235 type = Motor.Type.RELOAD;
237 type = Motor.Type.UNKNOWN;
241 str = attributes.get("auto-calc-mass");
242 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
243 calculateMass = false;
245 calculateMass = true;
249 str = attributes.get("auto-calc-cg");
250 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
258 public ElementHandler openElement(String element,
259 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
261 if (element.equals("comments")) {
262 return PlainTextHandler.INSTANCE;
265 if (element.equals("data")) {
266 if (dataHandler != null) {
267 throw new SAXException("Multiple data elements encountered in motor " +
270 dataHandler = new RSEMotorDataHandler();
274 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
279 public void closeElement(String element, HashMap<String, String> attributes,
280 String content, WarningSet warnings) {
282 if (element.equals("comments")) {
283 if (description.length() > 0) {
284 description = description + "\n\n" + content.trim();
286 description = content.trim();
291 if (element.equals("data")) {
292 time = dataHandler.getTime();
293 force = dataHandler.getForce();
294 mass = dataHandler.getMass();
295 cg = dataHandler.getCG();
297 sortLists(time, force, mass, cg);
299 for (double d: mass) {
300 if (Double.isNaN(d)) {
301 calculateMass = true;
306 if (Double.isNaN(d)) {
315 public Motor getMotor() throws SAXException {
316 if (time == null || time.size() == 0)
317 throw new SAXException("Illegal motor data");
320 finalizeThrustCurve(time, force, mass, cg);
321 final int n = time.size();
324 mass = calculateMass(time, force, initMass, propMass);
327 for (int i=0; i < n; i++) {
332 double[] timeArray = new double[n];
333 double[] thrustArray = new double[n];
334 Coordinate[] cgArray = new Coordinate[n];
335 for (int i=0; i < n; i++) {
336 timeArray[i] = time.get(i);
337 thrustArray[i] = force.get(i);
338 cgArray[i] = new Coordinate(cg.get(i),0,0,mass.get(i));
342 return new ThrustCurveMotor(manufacturer, designation, description, type,
343 delays, diameter, length, timeArray, thrustArray, cgArray);
344 } catch (IllegalArgumentException e) {
345 throw new SAXException("Illegal motor data", e);
352 * Handler for the <data> element in a RockSim engine file motor definition.
354 private static class RSEMotorDataHandler extends ElementHandler {
356 private final List<Double> time = new ArrayList<Double>();
357 private final List<Double> force = new ArrayList<Double>();
358 private final List<Double> mass = new ArrayList<Double>();
359 private final List<Double> cg = new ArrayList<Double>();
362 public List<Double> getTime() {
365 public List<Double> getForce() {
368 public List<Double> getMass() {
371 public List<Double> getCG() {
377 public ElementHandler openElement(String element,
378 HashMap<String, String> attributes, WarningSet warnings) {
380 if (element.equals("eng-data")) {
381 return NullElementHandler.INSTANCE;
384 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
389 public void closeElement(String element, HashMap<String, String> attributes,
390 String content, WarningSet warnings) throws SAXException {
392 double t = parseDouble(attributes.get("t"));
393 double f = parseDouble(attributes.get("f"));
394 double m = parseDouble(attributes.get("m")) / 1000.0;
395 double g = parseDouble(attributes.get("cg")) / 1000.0;
397 if (Double.isNaN(t) || Double.isNaN(f)) {
398 throw new SAXException("Illegal motor data point encountered");
408 private double parseDouble(String str) {
412 return Double.parseDouble(str);
413 } catch (NumberFormatException e) {