refactored file package
[debian/openrocket] / src / net / sf / openrocket / file / motor / RockSimMotorLoader.java
diff --git a/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java b/src/net/sf/openrocket/file/motor/RockSimMotorLoader.java
new file mode 100644 (file)
index 0000000..0596ba1
--- /dev/null
@@ -0,0 +1,462 @@
+package net.sf.openrocket.file.motor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.file.MotorLoader;
+import net.sf.openrocket.file.simplesax.ElementHandler;
+import net.sf.openrocket.file.simplesax.NullElementHandler;
+import net.sf.openrocket.file.simplesax.PlainTextHandler;
+import net.sf.openrocket.file.simplesax.SimpleSAX;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.motor.MotorDigest.DataType;
+import net.sf.openrocket.util.Coordinate;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class RockSimMotorLoader extends MotorLoader {
+       
+       public static final String CHARSET_NAME = "UTF-8";
+       
+       public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
+
+       
+       /** Any delay longed than this will be interpreted as a plugged motor. */
+       private static final int DELAY_LIMIT = 90;
+       
+
+       
+       @Override
+       protected Charset getDefaultCharset() {
+               return CHARSET;
+       }
+
+       
+
+       /**
+        * Load a <code>Motor</code> from a RockSim motor definition file specified by the 
+        * <code>Reader</code>. The <code>Reader</code> is responsible for using the correct 
+        * charset.
+        * <p>
+        * If automatic CG/mass calculation is used, then the CG is assumed to be located at 
+        * the center of the motor casing and the mass is calculated from the thrust curve 
+        * by assuming a constant exhaust velocity.
+        * 
+        * @param reader  the source of the file.
+        * @return                a list of the {@link Motor} objects defined in the file.
+        * @throws IOException  if an I/O error occurs or if the file format is invalid.
+        */
+       @Override
+       public List<Motor> load(Reader reader, String filename) throws IOException {
+               InputSource source = new InputSource(reader);
+               RSEHandler handler = new RSEHandler();
+               WarningSet warnings = new WarningSet();
+               
+               try {
+                       SimpleSAX.readXML(source, handler, warnings);
+                       return handler.getMotors();
+               } catch (SAXException e) {
+                       throw new IOException(e.getMessage(), e);
+               }
+       }
+       
+       
+       
+       /**
+        * Initial handler for the RockSim engine files.
+        */
+       private static class RSEHandler extends ElementHandler {
+               private final List<Motor> motors = new ArrayList<Motor>();
+               
+               private RSEMotorHandler motorHandler;
+               
+               public List<Motor> getMotors() {
+                       return motors;
+               }
+               
+               @Override
+               public ElementHandler openElement(String element,
+                               HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
+
+                       if (element.equals("engine-database") ||
+                                       element.equals("engine-list")) {
+                               // Ignore <engine-database> and <engine-list> elements
+                               return this;
+                       }
+                       
+                       if (element.equals("version")) {
+                               // Ignore <version> elements completely
+                               return null;
+                       }
+                       
+                       if (element.equals("engine")) {
+                               motorHandler = new RSEMotorHandler(attributes);
+                               return motorHandler;
+                       }
+                       
+                       return null;
+               }
+
+               @Override
+               public void closeElement(String element, HashMap<String, String> attributes,
+                               String content, WarningSet warnings) throws SAXException {
+
+                       if (element.equals("engine")) {
+                               Motor motor = motorHandler.getMotor();
+                               motors.add(motor);
+                       }
+               }
+       }
+       
+       
+       /**
+        * Handler for a RockSim engine file <motor> element.
+        */
+       private static class RSEMotorHandler extends ElementHandler {
+
+               private final String manufacturer;
+               private final String designation;
+               private final double[] delays;
+               private final double diameter;
+               private final double length;
+               private final double initMass;
+               private final double propMass;
+               private final Motor.Type type;
+               private boolean calculateMass;
+               private boolean calculateCG;
+               
+               private String description = "";
+               
+               private List<Double> time;
+               private List<Double> force;
+               private List<Double> mass;
+               private List<Double> cg;
+               
+               private RSEMotorDataHandler dataHandler = null;
+
+               
+               public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
+                       String str;
+                       
+                       // Manufacturer
+                       str = attributes.get("mfg");
+                       if (str == null)
+                               throw new SAXException("Manufacturer missing");
+                       manufacturer = str;
+                       
+                       // Designation
+                       str = attributes.get("code");
+                       if (str == null)
+                               throw new SAXException("Designation missing");
+                       designation = removeDelay(str);
+                       
+                       // Delays
+                       ArrayList<Double> delayList = new ArrayList<Double>();
+                       str = attributes.get("delays");
+                       if (str != null) {
+                               String[] split = str.split(",");
+                               for (String delay: split) {
+                                       try {
+                                               
+                                               double d = Double.parseDouble(delay);
+                                               if (d >= DELAY_LIMIT)
+                                                       d = Motor.PLUGGED;
+                                               delayList.add(d);
+                                               
+                                       } catch (NumberFormatException e) {
+                                               if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
+                                                       delayList.add(Motor.PLUGGED);
+                                               }
+                                       }
+                               }
+                       }
+                       delays = new double[delayList.size()];
+                       for (int i=0; i<delayList.size(); i++) {
+                               delays[i] = delayList.get(i);
+                       }
+                       
+                       // Diameter
+                       str = attributes.get("dia");
+                       if (str == null)
+                               throw new SAXException("Diameter missing");
+                       try {
+                               diameter = Double.parseDouble(str) / 1000.0;
+                       } catch (NumberFormatException e) {
+                               throw new SAXException("Invalid diameter " + str);
+                       }
+                       
+                       // Length
+                       str = attributes.get("len");
+                       if (str == null)
+                               throw new SAXException("Length missing");
+                       try {
+                               length = Double.parseDouble(str) / 1000.0;
+                       } catch (NumberFormatException e) {
+                               throw new SAXException("Invalid length " + str);
+                       }
+                       
+                       // Initial mass
+                       str = attributes.get("initWt");
+                       if (str == null)
+                               throw new SAXException("Initial mass missing");
+                       try {
+                               initMass = Double.parseDouble(str) / 1000.0;
+                       } catch (NumberFormatException e) {
+                               throw new SAXException("Invalid initial mass " + str);
+                       }
+                       
+                       // Propellant mass
+                       str = attributes.get("propWt");
+                       if (str == null)
+                               throw new SAXException("Propellant mass missing");
+                       try {
+                               propMass = Double.parseDouble(str) / 1000.0;
+                       } catch (NumberFormatException e) {
+                               throw new SAXException("Invalid propellant mass " + str);
+                       }
+                       
+                       if (propMass > initMass) {
+                               throw new SAXException("Propellant weight exceeds total weight in " +
+                                               "RockSim engine format");
+                       }
+                       
+                       // Motor type
+                       str = attributes.get("Type");
+                       if (str != null && str.equalsIgnoreCase("single-use")) {
+                               type = Motor.Type.SINGLE;
+                       } else if (str != null && str.equalsIgnoreCase("hybrid")) {
+                               type = Motor.Type.HYBRID;
+                       } else if (str != null && str.equalsIgnoreCase("reloadable")) {
+                               type = Motor.Type.RELOAD;
+                       } else {
+                               type = Motor.Type.UNKNOWN;
+                       }
+                       
+                       // Calculate mass
+                       str = attributes.get("auto-calc-mass");
+                       if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
+                               calculateMass = false;
+                       } else {
+                               calculateMass = true;
+                       }
+                       
+                       // Calculate CG
+                       str = attributes.get("auto-calc-cg");
+                       if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
+                               calculateCG = false;
+                       } else {
+                               calculateCG = true;
+                       }
+               }
+               
+               @Override
+               public ElementHandler openElement(String element,
+                               HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
+
+                       if (element.equals("comments")) {
+                               return PlainTextHandler.INSTANCE;
+                       }
+                       
+                       if (element.equals("data")) {
+                               if (dataHandler != null) {
+                                       throw new SAXException("Multiple data elements encountered in motor " +
+                                                       "definition");
+                               }
+                               dataHandler = new RSEMotorDataHandler();
+                               return dataHandler;
+                       }
+
+                       warnings.add("Unknown element '" + element + "' encountered, ignoring.");
+                       return null;
+               }
+
+               @Override
+               public void closeElement(String element, HashMap<String, String> attributes,
+                               String content, WarningSet warnings) {
+
+                       if (element.equals("comments")) {
+                               if (description.length() > 0) {
+                                       description = description + "\n\n" + content.trim();
+                               } else {
+                                       description = content.trim();
+                               }
+                               return;
+                       }
+                       
+                       if (element.equals("data")) {
+                               time = dataHandler.getTime();
+                               force = dataHandler.getForce();
+                               mass = dataHandler.getMass();
+                               cg = dataHandler.getCG();
+                               
+                               sortLists(time, force, mass, cg);
+                               
+                               for (double d: mass) {
+                                       if (Double.isNaN(d)) {
+                                               calculateMass = true;
+                                               break;
+                                       }
+                               }
+                               for (double d: cg) {
+                                       if (Double.isNaN(d)) {
+                                               calculateCG = true;
+                                               break;
+                                       }
+                               }
+                               return;
+                       }
+               }
+               
+               public Motor getMotor() throws SAXException {
+                       if (time == null || time.size() == 0)
+                               throw new SAXException("Illegal motor data");
+
+                       
+                       finalizeThrustCurve(time, force, mass, cg);
+                       final int n = time.size();
+                       
+                       if (hasIllegalValue(mass))
+                               calculateMass = true;
+                       if (hasIllegalValue(cg))
+                               calculateCG = true;
+                       
+                       if (calculateMass) {
+                               mass = calculateMass(time, force, initMass, propMass);
+                       }
+                       if (calculateCG) {
+                               for (int i=0; i < n; i++) {
+                                       cg.set(i, length/2);
+                               }
+                       }
+                       
+                       double[] timeArray = toArray(time);
+                       double[] thrustArray = toArray(force);
+                       Coordinate[] cgArray = new Coordinate[n];
+                       for (int i=0; i < n; i++) {
+                               cgArray[i] = new Coordinate(cg.get(i),0,0,mass.get(i));
+                       }
+                       
+
+                       // Create the motor digest from all data available in the file
+                       MotorDigest motorDigest = new MotorDigest();
+                       motorDigest.update(DataType.TIME_ARRAY, timeArray);
+                       if (!calculateMass) {
+                               motorDigest.update(DataType.MASS_PER_TIME, toArray(mass));
+                       } else {
+                               motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass-propMass);
+                       }
+                       if (!calculateCG) {
+                               motorDigest.update(DataType.CG_PER_TIME, toArray(cg));
+                       }
+                       motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
+                       final String digest = motorDigest.getDigest();
+                       
+                       
+                       try {
+                               return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
+                                               designation, description, type,
+                                               delays, diameter, length, timeArray, thrustArray, cgArray, digest);
+                       } catch (IllegalArgumentException e) {
+                               throw new SAXException("Illegal motor data", e);
+                       }
+               }
+       }
+       
+       
+       /**
+        * Handler for the <data> element in a RockSim engine file motor definition.
+        */
+       private static class RSEMotorDataHandler extends ElementHandler {
+               
+               private final List<Double> time = new ArrayList<Double>();
+               private final List<Double> force = new ArrayList<Double>();
+               private final List<Double> mass = new ArrayList<Double>();
+               private final List<Double> cg = new ArrayList<Double>();
+               
+               
+               public List<Double> getTime() {
+                       return time;
+               }
+               public List<Double> getForce() {
+                       return force;
+               }
+               public List<Double> getMass() {
+                       return mass;
+               }
+               public List<Double> getCG() {
+                       return cg;
+               }
+               
+               
+               @Override
+               public ElementHandler openElement(String element,
+                               HashMap<String, String> attributes, WarningSet warnings) {
+
+                       if (element.equals("eng-data")) {
+                               return NullElementHandler.INSTANCE;
+                       }
+                       
+                       warnings.add("Unknown element '" + element + "' encountered, ignoring.");
+                       return null;
+               }
+
+               @Override
+               public void closeElement(String element, HashMap<String, String> attributes,
+                               String content, WarningSet warnings) throws SAXException {
+
+                       double t = parseDouble(attributes.get("t"));
+                       double f = parseDouble(attributes.get("f"));
+                       double m = parseDouble(attributes.get("m")) / 1000.0;
+                       double g = parseDouble(attributes.get("cg")) / 1000.0;
+                       
+                       if (Double.isNaN(t) || Double.isNaN(f)) {
+                               throw new SAXException("Illegal motor data point encountered");
+                       }
+                       
+                       time.add(t);
+                       force.add(f);
+                       mass.add(m);
+                       cg.add(g);
+               }
+               
+               
+               private double parseDouble(String str) {
+                       if (str == null)
+                               return Double.NaN;
+                       try {
+                               return Double.parseDouble(str);
+                       } catch (NumberFormatException e) {
+                               return Double.NaN;
+                       }
+               }
+       }
+       
+       
+       
+       private static boolean hasIllegalValue(List<Double> list) {
+               for (Double d: list) {
+                       if (d == null || d.isNaN() || d.isInfinite()) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
+       private static double[] toArray(List<Double> list) {
+               final int n = list.size();
+               double[] array = new double[n];
+               for (int i=0; i < n; i++) {
+                       array[i] = list.get(i);
+               }
+               return array;
+       }
+}