refactored file package
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 26 Nov 2009 15:56:35 +0000 (15:56 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 26 Nov 2009 15:56:35 +0000 (15:56 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@33 180e2498-e6e9-4542-8430-84ac67f01cd8

81 files changed:
TODO
html/actions/updates.php
html/download.html
html/index.html
releasing.txt
src/net/sf/openrocket/communication/UpdateInfoRetriever.java
src/net/sf/openrocket/file/GeneralMotorLoader.java
src/net/sf/openrocket/file/GeneralRocketLoader.java
src/net/sf/openrocket/file/OpenRocketLoader.java [deleted file]
src/net/sf/openrocket/file/OpenRocketSaver.java [deleted file]
src/net/sf/openrocket/file/RASPMotorLoader.java [deleted file]
src/net/sf/openrocket/file/RockSimMotorLoader.java [deleted file]
src/net/sf/openrocket/file/motor/RASPMotorLoader.java [new file with mode: 0644]
src/net/sf/openrocket/file/motor/RockSimMotorLoader.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/BulkheadSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/FinSetSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/MassComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/MassObjectSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/NoseConeSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/ParachuteSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/RingComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/RocketSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/ShockCordSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/StageSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/StreamerSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/TransitionSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java [deleted file]
src/net/sf/openrocket/file/openrocket/savers/BodyComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/BulkheadSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/CenteringRingSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/ComponentAssemblySaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/EllipticalFinSetSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/EngineBlockSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/ExternalComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/FinSetSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/FreeformFinSetSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/InnerTubeSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/InternalComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/ParachuteSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RadiusRingComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RecoveryDeviceSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RingComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/ShockCordSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/StageSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/StreamerSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/StructuralComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/SymmetricComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/ThicknessRingComponentSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/TrapezoidFinSetSaver.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/TubeCouplerSaver.java [new file with mode: 0644]
src/net/sf/openrocket/gui/StorageOptionChooser.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/rocketcomponent/CenteringRing.java

diff --git a/TODO b/TODO
index 67f030c71ef623f389ff98119aad15ab555458f1..ec071f934ae9819e79370926c39eb64b0a1ac4a1 100644 (file)
--- a/TODO
+++ b/TODO
@@ -26,6 +26,7 @@ Postponed:
 - Reading thrust curves from external directory
 - Plot motor thrust curve
 
+- Screw weights for nose cones / transitions
 
 - Windows executable wrapper (launch4j)
 - Allow only one instance of OpenRocket running (RMI communication)
index e4981450136cb0be343a555e2a6cfcf0894780cb..55e3e147293b30f3e3ef4fe536154f16e07cb9ad 100644 (file)
@@ -60,6 +60,6 @@ header("Content-type: text/plain; charset=utf-8");
 $version = $_GET["version"];
 
 // No updates available
-header("HTTP/1.0 202 No Content");
+header("HTTP/1.0 204 No Content");
 
 ?>
\ No newline at end of file
index d3497cac503abb6df8843c1981a260240d8a870b..856c23861ab4d3068738f215e70da06937df5a28 100644 (file)
@@ -56,7 +56,7 @@
     later.  The Sun JRE is recommended.</em></p>
 
     <p class="download">
-    <a href="https://sourceforge.net/projects/openrocket/files/openrocket/OpenRocket-0.9.3.jar/download">Download OpenRocket 0.9.3</a></p>
+    <a href="https://sourceforge.net/projects/openrocket/files/openrocket/OpenRocket-0.9.4.jar/download">Download OpenRocket 0.9.4</a></p>
 
     <p>OpenRocket is still considered <strong>beta software</strong>.
     If you encounter any problems, please 
@@ -65,7 +65,7 @@
     <p>OpenRocket can be started in graphical environments (such as
     Windows) by double-clicking the package icon.  No installation is
     required.  From the command line it can be started by</p>
-    <pre class="quote">$ java -jar OpenRocket-0.9.3.jar</pre>
+    <pre class="quote">$ java -jar OpenRocket-0.9.4.jar</pre>
 
     <p>Older packages and source code are available from the
     <a href="https://sourceforge.net/project/showfiles.php?group_id=260357&amp;package_id=319743">SourceForge repository</a>.</p>
index c2d39944638ce19f0b529b3b24186f17efd3a825..864a273693f9decafd1b8e4b4908da1b19167ff4 100644 (file)
 
     <h2>News</h2>
 
+    <p><strong>24.11.2009:</strong> Version 0.9.4 is
+      <a href="download.html">released</a>!</p>
+
+    <p>This version adds support for through-the-wall fin tabs,
+    attaching components to coupler tubes, material editing, automatic
+    update checking, in addition to fixing numerous bugs.</p>
+
     <p><strong>1.9.2009:</strong> Version 0.9.3 is
       <a href="download.html">released</a>!</p>
 
index 389c310c6e958fadeb61b2600b877fcdea695c84..63164c4f8df71e4e131c5f4812cdc1378097d6cd 100644 (file)
@@ -6,13 +6,21 @@ Steps for making a release:
 3. Update ChangeLog
 4. ant dist
 5. Test new features (not in project directory)
-6. Copy distribution files into dist/
+6. Copy distribution files into dists/
 7. Update Eclipse project and commit files to SVN
-8. Tag the version in SVN
+8. Tag the version in SVN, URL:
+   https://openrocket.svn.sourceforge.net/svnroot/openrocket/tags/Release_0.9.x
 9. Upload JAR and source distribution and ReleaseNotes to Sourceforge 
-   (Project Admin -> File Manager, create new version directory + upload)
+    - Project Admin -> File Manager
+    - create new version directory under /openrocket
+    - upload JAR, ZIP and ReleaseNotes
+    - select ReleaseNotes properties, set as release notes
+    - select JAR properties, set release note file and default downloads
+    - select ZIP properties, set release note file
 10. Update HTML: index.html (release notes) download.html (version number)
-11. Update HTML to web server
-12. Send email about new release
+11. Update HTML to web server:
+    scp * plaa,openrocket@web.sourceforge.net:htdocs/
+12. Test downloading from Sourceforge and web site
 13. Update build.properties to "pre" version + commit
+14. Send email about new release to openrocket-announce@lists.sourceforge.net
 
index d67ea3354da90f086bcb3f9a70d94a65189a1634..082f2b9f7491e85a62882a6a8209aeb1313b377e 100644 (file)
@@ -124,6 +124,7 @@ public class UpdateInfoRetriever {
                        try {
                                doConnection();
                        } catch (IOException e) {
+                               System.out.println("fetching update failed: " + e);
                                return;
                        }
                }
@@ -158,6 +159,8 @@ public class UpdateInfoRetriever {
                        try {
                                connection.connect();
                                
+                               System.out.println("response code: " + connection.getResponseCode());
+                               
                                if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
                                        // No updates are available
                                        info = new UpdateInfo();
index 5981c2d0879350a9c456573ef1e9efbf535bd10e..117672f53598c3e4fdb56a8b2dacd961075f3261 100644 (file)
@@ -6,6 +6,8 @@ import java.io.Reader;
 import java.nio.charset.Charset;
 import java.util.List;
 
+import net.sf.openrocket.file.motor.RASPMotorLoader;
+import net.sf.openrocket.file.motor.RockSimMotorLoader;
 import net.sf.openrocket.motor.Motor;
 
 /**
index ce997dfe5bdbf57151fe0063b842a015e0c01f5a..1dc3329a5477030bf9bca29d0bf66cdaf9c5bbfc 100644 (file)
@@ -7,6 +7,7 @@ import java.nio.charset.Charset;
 import java.util.zip.GZIPInputStream;
 
 import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.openrocket.OpenRocketLoader;
 
 
 /**
diff --git a/src/net/sf/openrocket/file/OpenRocketLoader.java b/src/net/sf/openrocket/file/OpenRocketLoader.java
deleted file mode 100644 (file)
index c5d5f54..0000000
+++ /dev/null
@@ -1,1976 +0,0 @@
-package net.sf.openrocket.file;
-
-import java.awt.Color;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import net.sf.openrocket.aerodynamics.Warning;
-import net.sf.openrocket.aerodynamics.WarningSet;
-import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.document.StorageOptions;
-import net.sf.openrocket.document.Simulation.Status;
-import net.sf.openrocket.file.simplesax.ElementHandler;
-import net.sf.openrocket.file.simplesax.PlainTextHandler;
-import net.sf.openrocket.file.simplesax.SimpleSAX;
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Bulkhead;
-import net.sf.openrocket.rocketcomponent.CenteringRing;
-import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
-import net.sf.openrocket.rocketcomponent.Clusterable;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.EngineBlock;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.InternalComponent;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassComponent;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.Parachute;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RecoveryDevice;
-import net.sf.openrocket.rocketcomponent.ReferenceType;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.ShockCord;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Streamer;
-import net.sf.openrocket.rocketcomponent.StructuralComponent;
-import net.sf.openrocket.rocketcomponent.SymmetricComponent;
-import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
-import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
-import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
-import net.sf.openrocket.simulation.FlightData;
-import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.SimulationConditions;
-import net.sf.openrocket.simulation.FlightEvent.Type;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.LineStyle;
-import net.sf.openrocket.util.Reflection;
-
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-
-/**
- * Class that loads a rocket definition from an OpenRocket rocket file.
- * <p>
- * This class uses SAX to read the XML file format.  The 
- * {@link #loadFromStream(InputStream)} method simply sets the system up and 
- * starts the parsing, while the actual logic is in the private inner class
- * <code>OpenRocketHandler</code>.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class OpenRocketLoader extends RocketLoader {
-       
-       @Override
-       public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
-                       IOException {
-               InputSource xmlSource = new InputSource(source);
-               OpenRocketHandler handler = new OpenRocketHandler();
-
-               
-               try {
-                       SimpleSAX.readXML(xmlSource, handler, warnings);
-               } catch (SAXException e) {
-                       throw new RocketLoadException("Malformed XML in input.", e);
-               }
-
-               
-               OpenRocketDocument doc = handler.getDocument();
-               doc.getDefaultConfiguration().setAllStages();
-               
-               // Deduce suitable time skip
-               double timeSkip = StorageOptions.SIMULATION_DATA_NONE;
-               for (Simulation s: doc.getSimulations()) {
-                       if (s.getStatus() == Simulation.Status.EXTERNAL ||
-                                       s.getStatus() == Simulation.Status.NOT_SIMULATED)
-                               continue;
-                       if (s.getSimulatedData() == null)
-                               continue;
-                       if (s.getSimulatedData().getBranchCount() == 0)
-                               continue;
-                       FlightDataBranch branch = s.getSimulatedData().getBranch(0);
-                       if (branch == null)
-                               continue;
-                       List<Double> list = branch.get(FlightDataBranch.TYPE_TIME);
-                       if (list == null)
-                               continue;
-                       
-                       double previousTime = Double.NaN;
-                       for (double time: list) {
-                               if (time - previousTime < timeSkip)
-                                       timeSkip = time-previousTime;
-                               previousTime = time;
-                       }
-               }
-               // Round value
-               timeSkip = Math.rint(timeSkip*100)/100;
-
-               doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip);
-               doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed
-               doc.getDefaultStorageOptions().setExplicitlySet(false);
-               
-               doc.clearUndo();
-               return doc;
-       }
-
-}
-
-
-
-class DocumentConfig {
-
-       /* Remember to update OpenRocketSaver as well! */
-       public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1" };
-
-
-       ////////  Component constructors
-       static final HashMap<String, Constructor<? extends RocketComponent>> constructors = new HashMap<String, Constructor<? extends RocketComponent>>();
-       static {
-               try {
-                       // External components
-                       constructors.put("bodytube", BodyTube.class.getConstructor(new Class<?>[0]));
-                       constructors.put("transition", Transition.class.getConstructor(new Class<?>[0]));
-                       constructors.put("nosecone", NoseCone.class.getConstructor(new Class<?>[0]));
-                       constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
-                       constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
-                       constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
-                       constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
-
-                       // Internal components
-                       constructors.put("engineblock", EngineBlock.class.getConstructor(new Class<?>[0]));
-                       constructors.put("innertube", InnerTube.class.getConstructor(new Class<?>[0]));
-                       constructors.put("tubecoupler", TubeCoupler.class.getConstructor(new Class<?>[0]));
-                       constructors.put("bulkhead", Bulkhead.class.getConstructor(new Class<?>[0]));
-                       constructors.put("centeringring", CenteringRing.class.getConstructor(new Class<?>[0]));
-                       
-                       constructors.put("masscomponent", MassComponent.class.getConstructor(new Class<?>[0]));
-                       constructors.put("shockcord", ShockCord.class.getConstructor(new Class<?>[0]));
-                       constructors.put("parachute", Parachute.class.getConstructor(new Class<?>[0]));
-                       constructors.put("streamer", Streamer.class.getConstructor(new Class<?>[0]));
-                       
-                       // Other
-                       constructors.put("stage", Stage.class.getConstructor(new Class<?>[0]));
-                       
-               } catch (NoSuchMethodException e) {
-                       throw new RuntimeException(
-                                       "Error in constructing the 'constructors' HashMap.");
-               }
-       }
-
-
-       ////////  Parameter setters
-       /*
-        * The keys are of the form Class:param, where Class is the class name and param
-        * the element name.  Setters are searched for in descending class order.
-        * A setter of null means setting the parameter is not allowed.
-        */
-       static final HashMap<String, Setter> setters = new HashMap<String, Setter>();
-       static {
-               // RocketComponent
-               setters.put("RocketComponent:name", new StringSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setName", String.class)));
-               setters.put("RocketComponent:color", new ColorSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setColor", Color.class)));
-               setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
-                               Reflection.findMethodStatic(RocketComponent.class, "setLineStyle", LineStyle.class),
-                               LineStyle.class));
-               setters.put("RocketComponent:position", new PositionSetter());
-               setters.put("RocketComponent:overridemass", new OverrideSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideMass", double.class),
-                               Reflection.findMethodStatic(RocketComponent.class, "setMassOverridden", boolean.class)));
-               setters.put("RocketComponent:overridecg", new OverrideSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideCGX", double.class),
-                               Reflection.findMethodStatic(RocketComponent.class, "setCGOverridden", boolean.class)));
-               setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
-               setters.put("RocketComponent:comment", new StringSetter(
-                               Reflection.findMethodStatic(RocketComponent.class, "setComment", String.class)));
-               
-               // ExternalComponent
-               setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
-                               Reflection.findMethodStatic(ExternalComponent.class, "setFinish", Finish.class),
-                               Finish.class));
-               setters.put("ExternalComponent:material", new MaterialSetter(
-                               Reflection.findMethodStatic(ExternalComponent.class, "setMaterial", Material.class),
-                               Material.Type.BULK));
-                               
-               // BodyComponent
-               setters.put("BodyComponent:length", new DoubleSetter(
-                               Reflection.findMethodStatic(BodyComponent.class, "setLength", double.class)));
-               
-               // SymmetricComponent
-               setters.put("SymmetricComponent:thickness", new DoubleSetter(
-                               Reflection.findMethodStatic(SymmetricComponent.class,"setThickness", double.class), 
-                               "filled", 
-                               Reflection.findMethodStatic(SymmetricComponent.class,"setFilled", boolean.class)));
-               
-               // BodyTube
-               setters.put("BodyTube:radius", new DoubleSetter(
-                               Reflection.findMethodStatic(BodyTube.class, "setRadius", double.class), 
-                               "auto",
-                               Reflection.findMethodStatic(BodyTube.class,"setRadiusAutomatic", boolean.class)));
-                               
-               // Transition
-               setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
-                               Reflection.findMethodStatic(Transition.class, "setType", Transition.Shape.class),
-                               Transition.Shape.class));
-               setters.put("Transition:shapeclipped", new BooleanSetter(
-                               Reflection.findMethodStatic(Transition.class, "setClipped", boolean.class)));
-               setters.put("Transition:shapeparameter", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setShapeParameter", double.class)));
-                               
-               setters.put("Transition:foreradius", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setForeRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(Transition.class, "setForeRadiusAutomatic", boolean.class)));
-               setters.put("Transition:aftradius", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setAftRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(Transition.class, "setAftRadiusAutomatic", boolean.class)));
-
-               setters.put("Transition:foreshoulderradius", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setForeShoulderRadius", double.class)));
-               setters.put("Transition:foreshoulderlength", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setForeShoulderLength", double.class)));
-               setters.put("Transition:foreshoulderthickness", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setForeShoulderThickness", double.class)));
-               setters.put("Transition:foreshouldercapped", new BooleanSetter(
-                               Reflection.findMethodStatic(Transition.class, "setForeShoulderCapped", boolean.class)));
-               
-               setters.put("Transition:aftshoulderradius", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setAftShoulderRadius", double.class)));
-               setters.put("Transition:aftshoulderlength", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setAftShoulderLength", double.class)));
-               setters.put("Transition:aftshoulderthickness", new DoubleSetter(
-                               Reflection.findMethodStatic(Transition.class, "setAftShoulderThickness", double.class)));
-               setters.put("Transition:aftshouldercapped", new BooleanSetter(
-                               Reflection.findMethodStatic(Transition.class, "setAftShoulderCapped", boolean.class)));
-               
-               // NoseCone - disable disallowed elements
-               setters.put("NoseCone:foreradius", null);
-               setters.put("NoseCone:foreshoulderradius", null);
-               setters.put("NoseCone:foreshoulderlength", null);
-               setters.put("NoseCone:foreshoulderthickness", null);
-               setters.put("NoseCone:foreshouldercapped", null);
-               
-               // FinSet
-               setters.put("FinSet:fincount", new IntSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setFinCount", int.class)));
-               setters.put("FinSet:rotation", new DoubleSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setBaseRotation", double.class), Math.PI/180.0));
-               setters.put("FinSet:thickness", new DoubleSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setThickness", double.class)));
-               setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
-                               Reflection.findMethodStatic(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
-                               FinSet.CrossSection.class));
-               setters.put("FinSet:cant", new DoubleSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setCantAngle", double.class), Math.PI/180.0));
-               setters.put("FinSet:tabheight", new DoubleSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setTabHeight", double.class)));
-               setters.put("FinSet:tablength", new DoubleSetter(
-                               Reflection.findMethodStatic(FinSet.class, "setTabLength", double.class)));
-               setters.put("FinSet:tabposition", new FinTabPositionSetter());
-               
-               // TrapezoidFinSet
-               setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
-                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setRootChord", double.class)));
-               setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
-                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setTipChord", double.class)));
-               setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
-                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setSweep", double.class)));
-               setters.put("TrapezoidFinSet:height", new DoubleSetter(
-                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setHeight", double.class)));
-
-               // EllipticalFinSet
-               setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
-                               Reflection.findMethodStatic(EllipticalFinSet.class, "setLength", double.class)));
-               setters.put("EllipticalFinSet:height", new DoubleSetter(
-                               Reflection.findMethodStatic(EllipticalFinSet.class, "setHeight", double.class)));
-               
-               // FreeformFinSet points handled as a special handler
-               
-               // LaunchLug
-               setters.put("LaunchLug:radius", new DoubleSetter(
-                               Reflection.findMethodStatic(LaunchLug.class, "setRadius", double.class)));
-               setters.put("LaunchLug:length", new DoubleSetter(
-                               Reflection.findMethodStatic(LaunchLug.class, "setLength", double.class)));
-               setters.put("LaunchLug:thickness", new DoubleSetter(
-                               Reflection.findMethodStatic(LaunchLug.class, "setThickness", double.class)));
-               setters.put("LaunchLug:radialdirection", new DoubleSetter(
-                               Reflection.findMethodStatic(LaunchLug.class, "setRadialDirection", double.class),
-                               Math.PI/180.0));
-               
-               // InternalComponent - nothing
-               
-               // StructuralComponent
-               setters.put("StructuralComponent:material", new MaterialSetter(
-                               Reflection.findMethodStatic(StructuralComponent.class, "setMaterial", Material.class),
-                               Material.Type.BULK));
-               
-               // RingComponent
-               setters.put("RingComponent:length", new DoubleSetter(
-                               Reflection.findMethodStatic(RingComponent.class, "setLength", double.class)));
-               setters.put("RingComponent:radialposition", new DoubleSetter(
-                               Reflection.findMethodStatic(RingComponent.class, "setRadialPosition", double.class)));
-               setters.put("RingComponent:radialdirection", new DoubleSetter(
-                               Reflection.findMethodStatic(RingComponent.class, "setRadialDirection", double.class),
-                               Math.PI / 180.0));
-               
-               // ThicknessRingComponent - radius on separate components due to differing automatics
-               setters.put("ThicknessRingComponent:thickness", new DoubleSetter(
-                               Reflection.findMethodStatic(ThicknessRingComponent.class, "setThickness", double.class)));
-
-               // EngineBlock
-               setters.put("EngineBlock:outerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(EngineBlock.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
-
-               // TubeCoupler
-               setters.put("TubeCoupler:outerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // InnerTube
-               setters.put("InnerTube:outerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(InnerTube.class, "setOuterRadius", double.class)));
-               setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
-               setters.put("InnerTube:clusterscale", new DoubleSetter(
-                               Reflection.findMethodStatic(InnerTube.class, "setClusterScale", double.class)));
-               setters.put("InnerTube:clusterrotation", new DoubleSetter(
-                               Reflection.findMethodStatic(InnerTube.class, "setClusterRotation", double.class),
-                               Math.PI / 180.0));
-               
-               // RadiusRingComponent
-               
-               // Bulkhead
-               setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(RadiusRingComponent.class, "setInnerRadius", double.class)));
-               setters.put("Bulkhead:outerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(Bulkhead.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // CenteringRing
-               setters.put("CenteringRing:innerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(CenteringRing.class, "setInnerRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
-               setters.put("CenteringRing:outerradius", new DoubleSetter(
-                               Reflection.findMethodStatic(CenteringRing.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               
-               // MassObject
-               setters.put("MassObject:packedlength", new DoubleSetter(
-                               Reflection.findMethodStatic(MassObject.class, "setLength", double.class)));
-               setters.put("MassObject:packedradius", new DoubleSetter(
-                               Reflection.findMethodStatic(MassObject.class, "setRadius", double.class)));
-               setters.put("MassObject:radialposition", new DoubleSetter(
-                               Reflection.findMethodStatic(MassObject.class, "setRadialPosition", double.class)));
-               setters.put("MassObject:radialdirection", new DoubleSetter(
-                               Reflection.findMethodStatic(MassObject.class, "setRadialDirection", double.class),
-                               Math.PI / 180.0));
-               
-               // MassComponent
-               setters.put("MassComponent:mass", new DoubleSetter(
-                               Reflection.findMethodStatic(MassComponent.class, "setComponentMass", double.class)));
-               
-               // ShockCord
-               setters.put("ShockCord:cordlength", new DoubleSetter(
-                               Reflection.findMethodStatic(ShockCord.class, "setCordLength", double.class)));
-               setters.put("ShockCord:material", new MaterialSetter(
-                               Reflection.findMethodStatic(ShockCord.class, "setMaterial", Material.class),
-                               Material.Type.LINE));
-               
-               // RecoveryDevice
-               setters.put("RecoveryDevice:cd", new DoubleSetter(
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setCD", double.class),
-                               "auto",
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
-               setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
-                               RecoveryDevice.DeployEvent.class));
-               setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployAltitude", double.class)));
-               setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployDelay", double.class)));
-               setters.put("RecoveryDevice:material", new MaterialSetter(
-                               Reflection.findMethodStatic(RecoveryDevice.class, "setMaterial", Material.class),
-                               Material.Type.SURFACE));
-               
-               // Parachute
-               setters.put("Parachute:diameter", new DoubleSetter(
-                               Reflection.findMethodStatic(Parachute.class, "setDiameter", double.class)));
-               setters.put("Parachute:linecount", new IntSetter(
-                               Reflection.findMethodStatic(Parachute.class, "setLineCount", int.class)));
-               setters.put("Parachute:linelength", new DoubleSetter(
-                               Reflection.findMethodStatic(Parachute.class, "setLineLength", double.class)));
-               setters.put("Parachute:linematerial", new MaterialSetter(
-                               Reflection.findMethodStatic(Parachute.class, "setLineMaterial", Material.class),
-                               Material.Type.LINE));
-
-               // Streamer
-               setters.put("Streamer:striplength", new DoubleSetter(
-                               Reflection.findMethodStatic(Streamer.class, "setStripLength", double.class)));
-               setters.put("Streamer:stripwidth", new DoubleSetter(
-                               Reflection.findMethodStatic(Streamer.class, "setStripWidth", double.class)));
-               
-               // Rocket
-               // <motorconfiguration> handled by separate handler
-               setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
-                               Reflection.findMethodStatic(Rocket.class, "setReferenceType", ReferenceType.class),
-                               ReferenceType.class));
-               setters.put("Rocket:customreference", new DoubleSetter(
-                               Reflection.findMethodStatic(Rocket.class, "setCustomReferenceLength", double.class)));
-               setters.put("Rocket:designer", new StringSetter(
-                               Reflection.findMethodStatic(Rocket.class, "setDesigner", String.class)));
-               setters.put("Rocket:revision", new StringSetter(
-                               Reflection.findMethodStatic(Rocket.class, "setRevision", String.class)));
-       }
-       
-       
-       /**
-        * Search for a enum value that has the corresponding name as an XML value.  The current
-        * conversion from enum name to XML value is to lowercase the name and strip out all 
-        * underscore characters.  This method returns a match to these criteria, or <code>null</code>
-        * if no such enum exists.
-        * 
-        * @param <T>                   then enum type.
-        * @param name                  the XML value, null ok.
-        * @param enumClass             the class of the enum.
-        * @return                              the found enum value, or <code>null</code>.
-        */
-       public static <T extends Enum<T>> Enum<T> findEnum(String name, 
-                       Class<? extends Enum<T>> enumClass) {
-               
-               if (name == null)
-                       return null;
-               name = name.trim();
-               for (Enum<T> e: enumClass.getEnumConstants()) {
-                       if (e.name().toLowerCase().replace("_", "").equals(name)) {
-                               return e;
-                       }
-               }
-               return null;
-       }
-       
-       
-       /**
-        * Convert a string to a double including formatting specifications of the OpenRocket
-        * file format.  This accepts all formatting that is valid for 
-        * <code>Double.parseDouble(s)</code> and a few others as well ("Inf", "-Inf").
-        * 
-        * @param s             the string to parse.
-        * @return              the numerical value.
-        * @throws NumberFormatException        the the string cannot be parsed.
-        */
-       public static double stringToDouble(String s) throws NumberFormatException {
-               if (s == null)
-                       throw new NumberFormatException("null string");
-               if (s.equalsIgnoreCase("NaN"))
-                       return Double.NaN;
-               if (s.equalsIgnoreCase("Inf"))
-                       return Double.POSITIVE_INFINITY;
-               if (s.equalsIgnoreCase("-Inf"))
-                       return Double.NEGATIVE_INFINITY;
-               return Double.parseDouble(s);
-       }
-}
-
-
-
-
-
-/**
- * The starting point of the handlers.  Accepts a single <openrocket> element and hands
- * the contents to be read by a OpenRocketContentsHandler.
- */
-class OpenRocketHandler extends ElementHandler {
-       private OpenRocketContentHandler handler = null;
-
-       /**
-        * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
-        * has not been read yet.
-        * 
-        * @return      the document read, or null.
-        */
-       public OpenRocketDocument getDocument() {
-               return handler.getDocument();
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               // Check for unknown elements
-               if (!element.equals("openrocket")) {
-                       warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
-                       return null;
-               }
-
-               // Check for first call
-               if (handler != null) {
-                       warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
-                                                       + "ones."));
-                       return null;
-               }
-
-               // Check version number
-               String version = null;
-               String creator = attributes.remove("creator");
-               String docVersion = attributes.remove("version");
-               for (String v : DocumentConfig.SUPPORTED_VERSIONS) {
-                       if (v.equals(docVersion)) {
-                               version = v;
-                               break;
-                       }
-               }
-               if (version == null) {
-                       String str = "Unsupported document version";
-                       if (docVersion != null)
-                               str += " " + docVersion;
-                       if (creator != null && !creator.trim().equals(""))
-                               str += " (written using '" + creator.trim() + "')";
-                       str += ", attempting to read file anyway.";
-                       warnings.add(str);
-               }
-
-               handler = new OpenRocketContentHandler();
-               return handler;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-               attributes.remove("version");
-               attributes.remove("creator");
-               super.closeElement(element, attributes, content, warnings);
-       }
-       
-       
-}
-
-
-/**
- * Handles the content of the <openrocket> tag.
- */
-class OpenRocketContentHandler extends ElementHandler {
-       private final OpenRocketDocument doc;
-       private final Rocket rocket;
-
-       private boolean rocketDefined = false;
-       private boolean simulationsDefined = false;
-
-       public OpenRocketContentHandler() {
-               this.rocket = new Rocket();
-               this.doc = new OpenRocketDocument(rocket);
-       }
-
-
-       public OpenRocketDocument getDocument() {
-               if (!rocketDefined)
-                       return null;
-               return doc;
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               if (element.equals("rocket")) {
-                       if (rocketDefined) {
-                               warnings.add(Warning
-                                               .fromString("Multiple rocket designs within one document, "
-                                                               + "ignoring later ones."));
-                               return null;
-                       }
-                       rocketDefined = true;
-                       return new ComponentParameterHandler(rocket);
-               }
-               
-               if (element.equals("simulations")) {
-                       if (simulationsDefined) {
-                               warnings.add(Warning
-                                               .fromString("Multiple simulation definitions within one document, "
-                                                               + "ignoring later ones."));
-                               return null;
-                       }
-                       simulationsDefined = true;
-                       return new SimulationsHandler(doc);
-               }
-
-               warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
-
-               return null;
-       }
-}
-
-
-
-
-/**
- * A handler that creates components from the corresponding elements.  The control of the
- * contents is passed on to ComponentParameterHandler.
- */
-class ComponentHandler extends ElementHandler {
-       private final RocketComponent parent;
-
-       public ComponentHandler(RocketComponent parent) {
-               this.parent = parent;
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               // Attempt to construct new component
-               Constructor<? extends RocketComponent> constructor = DocumentConfig.constructors
-                               .get(element);
-               if (constructor == null) {
-                       warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
-                       return null;
-               }
-
-               RocketComponent c;
-               try {
-                       c = constructor.newInstance();
-               } catch (InstantiationException e) {
-                       throw new RuntimeException("Error constructing component.", e);
-               } catch (IllegalAccessException e) {
-                       throw new RuntimeException("Error constructing component.", e);
-               } catch (InvocationTargetException e) {
-                       throw new RuntimeException("Error constructing component.", e);
-               }
-
-               parent.addChild(c);
-
-               return new ComponentParameterHandler(c);
-       }
-}
-
-
-/**
- * A handler that populates the parameters of a previously constructed rocket component.
- * This uses the setters, or delegates the handling to another handler for specific
- * elements.
- */
-class ComponentParameterHandler extends ElementHandler {
-       private final RocketComponent component;
-
-       public ComponentParameterHandler(RocketComponent c) {
-               this.component = c;
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               // Check for specific elements that contain other elements
-               if (element.equals("subcomponents")) {
-                       return new ComponentHandler(component);
-               }
-               if (element.equals("motormount")) {
-                       if (!(component instanceof MotorMount)) {
-                               warnings.add(Warning.fromString("Illegal component defined as motor mount."));
-                               return null;
-                       }
-                       return new MotorMountHandler((MotorMount)component);
-               }
-               if (element.equals("finpoints")) {
-                       if (!(component instanceof FreeformFinSet)) {
-                               warnings.add(Warning.fromString("Illegal component defined for fin points."));
-                               return null;
-                       }
-                       return new FinSetPointHandler((FreeformFinSet)component);
-               }
-               if (element.equals("motorconfiguration")) {
-                       if (!(component instanceof Rocket)) {
-                               warnings.add(Warning.fromString("Illegal component defined for motor configuration."));
-                               return null;
-                       }
-                       return new MotorConfigurationHandler((Rocket)component);
-               }
-               
-               
-               return PlainTextHandler.INSTANCE;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-
-               if (element.equals("subcomponents") || element.equals("motormount") ||
-                               element.equals("finpoints") || element.equals("motorconfiguration")) {
-                       return;
-               }
-               
-               // Search for the correct setter class
-
-               Class<?> c;
-               for (c = component.getClass(); c != null; c = c.getSuperclass()) {
-                       String setterKey = c.getSimpleName() + ":" + element;
-                       Setter s = DocumentConfig.setters.get(setterKey);
-                       if (s != null) {
-                               // Setter found
-                               System.out.println("Calling with key "+setterKey);
-                               s.set(component, content, attributes, warnings);
-                               break;
-                       }
-                       if (DocumentConfig.setters.containsKey(setterKey)) {
-                               // Key exists but is null -> invalid parameter
-                               c = null;
-                               break;
-                       }
-               }
-               if (c == null) {
-                       warnings.add(Warning.fromString("Unknown parameter type '" + element + "' for "
-                                       + component.getComponentName() + ", ignoring."));
-               }
-       }
-}
-
-
-/**
- * A handler that reads the <point> specifications within the freeformfinset's
- * <finpoints> elements.
- */
-class FinSetPointHandler extends ElementHandler {
-       private final FreeformFinSet finset;
-       private final ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
-       
-       public FinSetPointHandler(FreeformFinSet finset) {
-               this.finset = finset;
-       }
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               return PlainTextHandler.INSTANCE;
-       }
-       
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-
-               String strx = attributes.remove("x");
-               String stry = attributes.remove("y");
-               if (strx == null || stry == null) {
-                       warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
-                       return;
-               }
-               try {
-                       double x = Double.parseDouble(strx);
-                       double y = Double.parseDouble(stry);
-                       coordinates.add(new Coordinate(x,y));
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
-                       return;
-               }
-               
-               super.closeElement(element, attributes, content, warnings);
-       }
-       
-       @Override
-       public void endHandler(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-               try {
-                       finset.setPoints(coordinates.toArray(new Coordinate[0]));
-               } catch (IllegalFinPointException e) {
-                       warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
-               }
-       }
-}
-
-
-class MotorMountHandler extends ElementHandler {
-       private final MotorMount mount;
-       private MotorHandler motorHandler;
-       
-       public MotorMountHandler(MotorMount mount) {
-               this.mount = mount;
-               mount.setMotorMount(true);
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               if (element.equals("motor")) {
-                       motorHandler = new MotorHandler();
-                       return motorHandler;
-               }
-               
-               if (element.equals("ignitionevent") ||
-                               element.equals("ignitiondelay") ||
-                               element.equals("overhang")) {
-                       return PlainTextHandler.INSTANCE;
-               }
-               
-               warnings.add(Warning.fromString("Unknown element '"+element+"' encountered, ignoring."));
-               return null;
-       }
-       
-       
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-
-               if (element.equals("motor")) {
-                       String id = attributes.get("configid");
-                       if (id == null || id.equals("")) {
-                               warnings.add(Warning.fromString("Illegal motor specification, ignoring."));
-                               return;
-                       }
-
-                       Motor motor = motorHandler.getMotor(warnings);
-                       mount.setMotor(id, motor);
-                       mount.setMotorDelay(id, motorHandler.getDelay(warnings));
-                       return;
-               }
-
-               if (element.equals("ignitionevent")) { 
-                       MotorMount.IgnitionEvent event = null;
-                       for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) {
-                               if (e.name().toLowerCase().replaceAll("_", "").equals(content)) {
-                                       event = e;
-                                       break;
-                               }
-                       }
-                       if (event == null) {
-                               warnings.add(Warning.fromString("Unknown ignition event type '"+content+"', ignoring."));
-                               return;
-                       }
-                       mount.setIgnitionEvent(event);
-                       return;
-               }
-
-               if (element.equals("ignitiondelay")) {
-                       double d;
-                       try {
-                               d = Double.parseDouble(content);
-                       } catch (NumberFormatException nfe) {
-                               warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
-                               return;
-                       }
-                       mount.setIgnitionDelay(d);
-                       return;
-               }
-               
-               if (element.equals("overhang")) {
-                       double d;
-                       try {
-                               d = Double.parseDouble(content);
-                       } catch (NumberFormatException nfe) {
-                               warnings.add(Warning.fromString("Illegal overhang specified, ignoring."));
-                               return;
-                       }
-                       mount.setMotorOverhang(d);
-                       return;
-               }
-               
-               super.closeElement(element, attributes, content, warnings);
-       }
-}
-
-
-
-
-class MotorConfigurationHandler extends ElementHandler {
-       private final Rocket rocket;
-       private String name = null;
-       private boolean inNameElement = false;
-       
-       public MotorConfigurationHandler(Rocket rocket) {
-               this.rocket = rocket;
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               if (inNameElement || !element.equals("name")) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return null;
-               }
-               inNameElement = true;
-               
-               return PlainTextHandler.INSTANCE;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-               name = content;
-       }
-
-       @Override
-       public void endHandler(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-
-               String configid = attributes.remove("configid");
-               if (configid == null || configid.equals("")) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-               
-               if (!rocket.addMotorConfigurationID(configid)) {
-                       warnings.add("Duplicate motor configuration ID used.");
-                       return;
-               }
-               
-               if (name != null && name.trim().length() > 0) {
-                       rocket.setMotorConfigurationName(configid, name);
-               }
-               
-               if ("true".equals(attributes.remove("default"))) {
-                       rocket.getDefaultConfiguration().setMotorConfigurationID(configid);
-               }
-               
-               super.closeElement(element, attributes, content, warnings);
-       }
-}
-
-
-class MotorHandler extends ElementHandler {
-       private Motor.Type type = null;
-       private String manufacturer = null;
-       private String designation = null;
-       private double diameter = Double.NaN;
-       private double length = Double.NaN;
-       private double delay = Double.NaN;
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               return PlainTextHandler.INSTANCE;
-       }
-       
-       
-       /**
-        * Return the motor to use, or null.
-        */
-       public Motor getMotor(WarningSet warnings) {
-               if (designation == null) {
-                       warnings.add(Warning.fromString("No motor specified, ignoring."));
-                       return null;
-               }
-               Motor[] motors = Databases.findMotors(type, manufacturer, designation, diameter, length);
-               if (motors.length == 0) {
-                       String str = "No motor with designation '"+designation+"'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       warnings.add(Warning.fromString(str + " found."));
-                       return null;
-               }
-               if (motors.length > 1) {
-                       String str = "Multiple motors with designation '"+designation+"'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       warnings.add(Warning.fromString(str + " found, one chosen arbitrarily."));
-               }
-               return motors[0];
-       }
-       
-       
-       /**
-        * Return the delay to use for the motor.
-        */
-       public double getDelay(WarningSet warnings) {
-               if (Double.isNaN(delay)) {
-                       warnings.add(Warning.fromString("Motor delay not specified, assuming no ejection charge."));
-                       return Motor.PLUGGED;
-               }
-               return delay;
-       }
-       
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-               
-               content = content.trim();
-               
-               if (element.equals("type")) {
-                       
-                       // Motor type
-                       type = null;
-                       for (Motor.Type t: Motor.Type.values()) {
-                               if (t.name().toLowerCase().equals(content)) {
-                                       type = t;
-                                       break;
-                               }
-                       }
-                       if (type == null) {
-                               warnings.add(Warning.fromString("Unknown motor type '"+content+"', ignoring."));
-                       }
-                       
-               } else if (element.equals("manufacturer")) {
-                       
-                       // Manufacturer
-                       manufacturer = content;
-
-               } else if (element.equals("designation")) {
-                       
-                       // Designation
-                       designation = content;
-
-               } else if (element.equals("diameter")) {
-               
-                       // Diameter
-                       diameter = Double.NaN;
-                       try {
-                               diameter = Double.parseDouble(content);
-                       } catch (NumberFormatException e) {
-                               // Ignore
-                       }
-                       if (Double.isNaN(diameter)) {
-                               warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
-                       }
-                       
-               } else if (element.equals("length")) {
-
-                       // Length
-                       length = Double.NaN;
-                       try {
-                               length = Double.parseDouble(content);
-                       } catch (NumberFormatException ignore) { }
-                       
-                       if (Double.isNaN(length)) {
-                               warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
-                       }
-                       
-               } else if (element.equals("delay")) {
-                       
-                       // Delay
-                       delay = Double.NaN;
-                       if (content.equals("none")) {
-                               delay = Motor.PLUGGED;
-                       } else {
-                               try {
-                                       delay = Double.parseDouble(content);
-                               } catch (NumberFormatException ignore) { }
-                               
-                               if (Double.isNaN(delay)) {
-                                       warnings.add(Warning.fromString("Illegal motor delay specified, ignoring."));
-                               }
-                               
-                       }
-
-               } else {
-                       super.closeElement(element, attributes, content, warnings);
-               }
-       }
-       
-}
-
-
-
-class SimulationsHandler extends ElementHandler {
-       private final OpenRocketDocument doc;
-       private SingleSimulationHandler handler;
-       
-       public SimulationsHandler(OpenRocketDocument doc) {
-               this.doc = doc;
-       }
-
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               if (!element.equals("simulation")) {
-                       warnings.add("Unknown element '"+element+"', ignoring.");
-                       return null;
-               }
-               
-               handler = new SingleSimulationHandler(doc);
-               return handler;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-               attributes.remove("status");
-               super.closeElement(element, attributes, content, warnings);
-       }
-       
-       
-}
-
-class SingleSimulationHandler extends ElementHandler {
-
-       private final OpenRocketDocument doc;
-       
-       private String name;
-       
-       private SimulationConditionsHandler conditionHandler;
-       private FlightDataHandler dataHandler;
-       
-       private final List<String> listeners = new ArrayList<String>();
-       
-       public SingleSimulationHandler(OpenRocketDocument doc) {
-               this.doc = doc;
-       }
-       
-       
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               if (element.equals("name") || element.equals("simulator") || 
-                               element.equals("calculator") || element.equals("listener")) {
-                       return PlainTextHandler.INSTANCE;
-               } else if (element.equals("conditions")) {
-                       conditionHandler = new SimulationConditionsHandler(doc.getRocket());
-                       return conditionHandler;
-               } else if (element.equals("flightdata")) {
-                       dataHandler = new FlightDataHandler();
-                       return dataHandler;
-               } else {
-                       warnings.add("Unknown element '"+element+"', ignoring.");
-                       return null;
-               }
-       }
-       
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-               
-               if (element.equals("name")) {
-                       name = content;
-               } else if (element.equals("simulator")) {
-                       if (!content.trim().equals("RK4Simulator")) {
-                               warnings.add("Unknown simulator '" + content.trim() + "' specified, ignoring.");
-                       }
-               } else if (element.equals("calculator")) {
-                       if (!content.trim().equals("BarrowmanCalculator")) {
-                               warnings.add("Unknown calculator '" + content.trim() + "' specified, ignoring.");
-                       }
-               } else if (element.equals("listener") && content.trim().length() > 0) {
-                       listeners.add(content.trim());
-               }
-
-       }
-
-       @Override
-       public void endHandler(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-
-               String s = attributes.get("status");
-               Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
-               if (status == null) {
-                       warnings.add("Simulation status unknown, assuming outdated.");
-                       status = Simulation.Status.OUTDATED;
-               }
-               
-               SimulationConditions conditions;
-               if (conditionHandler != null) {
-                       conditions = conditionHandler.getConditions();
-               } else {
-                       warnings.add("Simulation conditions not defined, using defaults.");
-                       conditions = new SimulationConditions(doc.getRocket());
-               }
-               
-               if (name == null)
-                       name = "Simulation";
-               
-               FlightData data;
-               if (dataHandler == null)
-                       data = null;
-               else
-                       data = dataHandler.getFlightData();
-
-               Simulation simulation = new Simulation(doc.getRocket(), status, name,
-                               conditions, listeners, data);
-               
-               doc.addSimulation(simulation);
-       }
-}
-
-
-
-class SimulationConditionsHandler extends ElementHandler {
-       private SimulationConditions conditions;
-       private AtmosphereHandler atmosphereHandler;
-       
-       public SimulationConditionsHandler(Rocket rocket) {
-               conditions = new SimulationConditions(rocket);
-       }
-       
-       public SimulationConditions getConditions() {
-               return conditions;
-       }
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               if (element.equals("atmosphere")) {
-                       atmosphereHandler = new AtmosphereHandler(attributes.get("model"));
-                       return atmosphereHandler;
-               }
-               return PlainTextHandler.INSTANCE;
-       }       
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-               
-               double d = Double.NaN;
-               try {
-                       d = Double.parseDouble(content);
-               } catch (NumberFormatException ignore) { }
-               
-
-               if (element.equals("configid")) {
-                       if (content.equals("")) {
-                               conditions.setMotorConfigurationID(null);
-                       } else {
-                               conditions.setMotorConfigurationID(content);
-                       }
-               } else if (element.equals("launchrodlength")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch rod length defined, ignoring.");
-                       } else {
-                               conditions.setLaunchRodLength(d);
-                       }
-               } else if (element.equals("launchrodangle")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch rod angle defined, ignoring.");
-                       } else {
-                               conditions.setLaunchRodAngle(d*Math.PI/180);
-                       }
-               } else if (element.equals("launchroddirection")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch rod direction defined, ignoring.");
-                       } else {
-                               conditions.setLaunchRodDirection(d*Math.PI/180);
-                       }
-               } else if (element.equals("windaverage")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal average windspeed defined, ignoring.");
-                       } else {
-                               conditions.setWindSpeedAverage(d);
-                       }
-               } else if (element.equals("windturbulence")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal wind turbulence intensity defined, ignoring.");
-                       } else {
-                               conditions.setWindTurbulenceIntensity(d);
-                       }
-               } else if (element.equals("launchaltitude")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch altitude defined, ignoring.");
-                       } else {
-                               conditions.setLaunchAltitude(d);
-                       }
-               } else if (element.equals("launchlatitude")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch latitude defined, ignoring.");
-                       } else {
-                               conditions.setLaunchLatitude(d);
-                       }
-               } else if (element.equals("atmosphere")) {
-                       atmosphereHandler.storeSettings(conditions, warnings);
-               } else if (element.equals("timestep")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal time step defined, ignoring.");
-                       } else {
-                               conditions.setTimeStep(d);
-                       }
-               }
-       }
-}
-
-
-class AtmosphereHandler extends ElementHandler {
-       private final String model;
-       private double temperature = Double.NaN;
-       private double pressure = Double.NaN;
-       
-       public AtmosphereHandler(String model) {
-               this.model = model;
-       }
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               return PlainTextHandler.INSTANCE;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) throws SAXException {
-
-               double d = Double.NaN;
-               try {
-                       d = Double.parseDouble(content);
-               } catch (NumberFormatException ignore) { }
-               
-               if (element.equals("basetemperature")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal base temperature specified, ignoring.");
-                       }
-                       temperature = d;
-               } else if (element.equals("basepressure")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal base pressure specified, ignoring.");
-                       }
-                       pressure = d;
-               } else {
-                       super.closeElement(element, attributes, content, warnings);
-               }
-       }
-
-       
-       public void storeSettings(SimulationConditions cond, WarningSet warnings) {
-               if (!Double.isNaN(pressure)) {
-                       cond.setLaunchPressure(pressure);
-               }
-               if (!Double.isNaN(temperature)) {
-                       cond.setLaunchTemperature(temperature);
-               }
-               
-               if ("isa".equals(model)) {
-                       cond.setISAAtmosphere(true);
-               } else if ("extendedisa".equals(model)){
-                       cond.setISAAtmosphere(false);
-               } else {
-                       cond.setISAAtmosphere(true);
-                       warnings.add("Unknown atmospheric model, using ISA.");
-               }
-       }
-       
-}
-
-
-class FlightDataHandler extends ElementHandler {
-       
-       private FlightDataBranchHandler dataHandler;
-       private WarningSet warningSet = new WarningSet();
-       private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
-
-       private FlightData data;
-       
-       public FlightData getFlightData() {
-               return data;
-       }
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               if (element.equals("warning")) {
-                       return PlainTextHandler.INSTANCE;
-               }
-               if (element.equals("databranch")) {
-                       if (attributes.get("name") == null || attributes.get("types")==null) {
-                               warnings.add("Illegal flight data definition, ignoring.");
-                               return null;
-                       }
-                       dataHandler =  new FlightDataBranchHandler(attributes.get("name"),
-                                       attributes.get("types"));
-                       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("databranch")) {
-                       FlightDataBranch branch = dataHandler.getBranch();
-                       if (branch.getLength() > 0) {
-                               branches.add(branch);
-                       }
-               } else if (element.equals("warning")) {
-                       warningSet.add(Warning.fromString(content));
-               }
-       }
-
-
-       @Override
-       public void endHandler(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-
-               if (branches.size() > 0) {
-                       data = new FlightData(branches.toArray(new FlightDataBranch[0]));
-               } else {
-                       double maxAltitude = Double.NaN;
-                       double maxVelocity = Double.NaN;
-                       double maxAcceleration = Double.NaN;
-                       double maxMach = Double.NaN;
-                       double timeToApogee = Double.NaN;
-                       double flightTime = Double.NaN;
-                       double groundHitVelocity = Double.NaN;
-                       
-                       try { 
-                               maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               maxVelocity = DocumentConfig.stringToDouble(attributes.get("maxvelocity"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               maxAcceleration = DocumentConfig.stringToDouble(attributes.get("maxacceleration"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               maxMach = DocumentConfig.stringToDouble(attributes.get("maxmach"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               timeToApogee = DocumentConfig.stringToDouble(attributes.get("timetoapogee"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               flightTime = DocumentConfig.stringToDouble(attributes.get("flighttime"));
-                       } catch (NumberFormatException ignore) { }
-                       try { 
-                               groundHitVelocity = 
-                                       DocumentConfig.stringToDouble(attributes.get("groundhitvelocity"));
-                       } catch (NumberFormatException ignore) { }
-                       
-                       data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
-                                       timeToApogee, flightTime, groundHitVelocity);
-               }
-               
-               data.getWarningSet().addAll(warningSet);
-       }
-       
-       
-}
-
-
-class FlightDataBranchHandler extends ElementHandler {
-       private final FlightDataBranch.Type[] types;
-       private final FlightDataBranch branch;
-       
-       public FlightDataBranchHandler(String name, String typeList) {
-               String[] split = typeList.split(",");
-               types = new FlightDataBranch.Type[split.length];
-               for (int i=0; i < split.length; i++) {
-                       types[i] = FlightDataBranch.getType(split[i], UnitGroup.UNITS_NONE);
-               }
-               
-               // TODO: LOW: May throw an IllegalArgumentException
-               branch = new FlightDataBranch(name, types);
-       }
-
-       public FlightDataBranch getBranch() {
-               branch.immute();
-               return branch;
-       }
-       
-       @Override
-       public ElementHandler openElement(String element, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               if (element.equals("datapoint"))
-                       return PlainTextHandler.INSTANCE;
-               if (element.equals("event"))
-                       return PlainTextHandler.INSTANCE;
-               
-               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("event")) {
-                       double time;
-                       FlightEvent.Type type;
-                       
-                       try {
-                               time = DocumentConfig.stringToDouble(attributes.get("time"));
-                       } catch (NumberFormatException e) {
-                               warnings.add("Illegal event specification, ignoring.");
-                               return;
-                       }
-                       
-                       type = (Type) DocumentConfig.findEnum(attributes.get("type"), FlightEvent.Type.class);
-                       if (type == null) {
-                               warnings.add("Illegal event specification, ignoring.");
-                               return;
-                       }
-
-                       branch.addEvent(time, new FlightEvent(type, time));
-                       return;
-               }
-               
-               if (!element.equals("datapoint")) {
-                       warnings.add("Unknown element '"+element+"' encountered, ignoring.");
-                       return;
-               }
-
-               // element == "datapoint"
-               
-               
-               // Check line format
-               String[] split = content.split(",");
-               if (split.length != types.length) {
-                       warnings.add("Data point did not contain correct amount of values, ignoring point.");
-                       return;
-               }
-               
-               // Parse the doubles
-               double[] values = new double[split.length];
-               for (int i=0; i < values.length; i++) {
-                       try {
-                               values[i] = DocumentConfig.stringToDouble(split[i]);
-                       } catch (NumberFormatException e) {
-                               warnings.add("Data point format error, ignoring point.");
-                               return;
-                       }
-               }
-               
-               // Add point to branch
-               branch.addPoint();
-               for (int i=0; i < types.length; i++) {
-                       branch.setValue(types[i], values[i]);
-               }
-       }
-}
-
-
-
-
-
-
-/////////////////    Setters implementation
-
-
-////  Interface
-interface Setter {
-       /**
-        * Set the specified value to the given component.
-        * 
-        * @param component             the component to which to set.
-        * @param value                 the value within the element.
-        * @param attributes    attributes for the element.
-        * @param warnings              the warning set to use.
-        */
-       public void set(RocketComponent component, String value,
-                       HashMap<String, String> attributes, WarningSet warnings);
-}
-
-
-////  StringSetter - sets the value to the contained String
-class StringSetter implements Setter {
-       private final Reflection.Method setMethod;
-
-       public StringSetter(Reflection.Method set) {
-               setMethod = set;
-       }
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               setMethod.invoke(c, s);
-       }
-}
-
-////  IntSetter - set an integer value
-class IntSetter implements Setter {
-       private final Reflection.Method setMethod;
-
-       public IntSetter(Reflection.Method set) {
-               setMethod = set;
-       }
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               try {
-                       int n = Integer.parseInt(s);
-                       setMethod.invoke(c, n);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-       }
-}
-
-
-//// BooleanSetter - set a boolean value
-class BooleanSetter implements Setter {
-       private final Reflection.Method setMethod;
-
-       public BooleanSetter(Reflection.Method set) {
-               setMethod = set;
-       }
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               s = s.trim();
-               if (s.equalsIgnoreCase("true")) {
-                       setMethod.invoke(c, true);
-               } else if (s.equalsIgnoreCase("false")) {
-                       setMethod.invoke(c, false);
-               } else {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-       }
-}
-
-
-
-////  DoubleSetter - sets a double value or (alternatively) if a specific string is encountered
-////  calls a setXXX(boolean) method.
-class DoubleSetter implements Setter {
-       private final Reflection.Method setMethod;
-       private final String specialString;
-       private final Reflection.Method specialMethod;
-       private final double multiplier;
-
-       /**
-        * Set only the double value.
-        * @param set   set method for the double value. 
-        */
-       public DoubleSetter(Reflection.Method set) {
-               this.setMethod = set;
-               this.specialString = null;
-               this.specialMethod = null;
-               this.multiplier = 1.0;
-       }
-
-       /**
-        * Multiply with the given multiplier and set the double value.
-        * @param set   set method for the double value.
-        * @param mul   multiplier.
-        */
-       public DoubleSetter(Reflection.Method set, double mul) {
-               this.setMethod = set;
-               this.specialString = null;
-               this.specialMethod = null;
-               this.multiplier = mul;
-       }
-
-       /**
-        * Set the double value, or if the value equals the special string, use the
-        * special setter and set it to true.
-        * 
-        * @param set                   double setter.
-        * @param special               special string
-        * @param specialMethod boolean setter.
-        */
-       public DoubleSetter(Reflection.Method set, String special,
-                       Reflection.Method specialMethod) {
-               this.setMethod = set;
-               this.specialString = special;
-               this.specialMethod = specialMethod;
-               this.multiplier = 1.0;
-       }
-
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               s = s.trim();
-
-               // Check for special case
-               if (specialMethod != null && s.equalsIgnoreCase(specialString)) {
-                       specialMethod.invoke(c, true);
-                       return;
-               }
-
-               // Normal case
-               try {
-                       double d = Double.parseDouble(s);
-                       setMethod.invoke(c, d * multiplier);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-       }
-}
-
-
-class OverrideSetter implements Setter {
-       private final Reflection.Method setMethod;
-       private final Reflection.Method enabledMethod;
-
-       public OverrideSetter(Reflection.Method set, Reflection.Method enabledMethod) {
-               this.setMethod = set;
-               this.enabledMethod = enabledMethod;
-       }
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               try {
-                       double d = Double.parseDouble(s);
-                       setMethod.invoke(c, d);
-                       enabledMethod.invoke(c, true);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-       }
-}
-
-////  EnumSetter  -  sets a generic enum type
-class EnumSetter<T extends Enum<T>> implements Setter {
-       private final Reflection.Method setter;
-       private final Class<T> enumClass;
-
-       public EnumSetter(Reflection.Method set, Class<T> enumClass) {
-               this.setter = set;
-               this.enumClass = enumClass;
-       }
-
-       @Override
-       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               Enum<?> setEnum = DocumentConfig.findEnum(name, enumClass);
-               if (setEnum == null) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-
-               setter.invoke(c, setEnum);
-       }
-}
-
-
-////  ColorSetter  -  sets a Color value
-class ColorSetter implements Setter {
-       private final Reflection.Method setMethod;
-
-       public ColorSetter(Reflection.Method set) {
-               setMethod = set;
-       }
-
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-
-               String red = attributes.get("red");
-               String green = attributes.get("green");
-               String blue = attributes.get("blue");
-
-               if (red == null || green == null || blue == null) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-               
-               int r, g, b;
-               try {
-                       r = Integer.parseInt(red);
-                       g = Integer.parseInt(green);
-                       b = Integer.parseInt(blue);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-               
-               if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-
-               Color color = new Color(r, g, b);
-               setMethod.invoke(c, color);
-               
-               if (!s.trim().equals("")) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-       }
-}
-
-
-
-class MaterialSetter implements Setter {
-       private final Reflection.Method setMethod;
-       private final Material.Type type;
-
-       public MaterialSetter(Reflection.Method set, Material.Type type) {
-               this.setMethod = set;
-               this.type = type;
-       }
-
-       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               Material mat;
-               
-               // Check name != ""
-               name = name.trim();
-               if (name.equals("")) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
-               
-               // Parse density
-               double density;
-               String str;
-               str = attributes.remove("density");
-               if (str == null) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
-               try {
-                       density = Double.parseDouble(str);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
-
-               // Parse thickness
-//             double thickness = 0;
-//             str = attributes.remove("thickness");
-//             try {
-//                     if (str != null)
-//                             thickness = Double.parseDouble(str);
-//             } catch (NumberFormatException e){
-//                     warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-//                     return;
-//             }
-
-               // Check type if specified
-               str = attributes.remove("type");
-               if (str != null  &&  !type.name().toLowerCase().equals(str)) {
-                       warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
-                       return;
-               }
-
-               mat = Databases.findMaterial(type, name, density, false);
-               
-               setMethod.invoke(c, mat);
-       }
-}
-
-
-
-
-class PositionSetter implements Setter {
-
-       public void set(RocketComponent c, String value, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               RocketComponent.Position type = (Position) DocumentConfig.findEnum(attributes.get("type"), 
-                               RocketComponent.Position.class);
-               if (type == null) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-               
-               double pos;
-               try {
-                       pos = Double.parseDouble(value);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-                       return;
-               }
-               
-               if (c instanceof FinSet) {
-                       ((FinSet)c).setRelativePosition(type);
-                       c.setPositionValue(pos);
-               } else if (c instanceof LaunchLug) {
-                       ((LaunchLug)c).setRelativePosition(type);
-                       c.setPositionValue(pos);
-               } else if (c instanceof InternalComponent) {
-                       ((InternalComponent)c).setRelativePosition(type);
-                       c.setPositionValue(pos);
-               } else {
-                       warnings.add(Warning.FILE_INVALID_PARAMETER);
-               }
-               
-       }
-}
-
-
-class FinTabPositionSetter extends DoubleSetter {
-
-       public FinTabPositionSetter() {
-               super(Reflection.findMethodStatic(FinSet.class, "setTabShift", double.class));
-       }
-
-       @Override
-       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               if (!(c instanceof FinSet)) {
-                       throw new IllegalStateException("FinTabPositionSetter called for component " + c);
-               }
-
-               String relative = attributes.get("relativeto");
-               FinSet.TabRelativePosition position = 
-                       (TabRelativePosition) DocumentConfig.findEnum(relative,
-                                       FinSet.TabRelativePosition.class);
-               
-               if (position != null) {
-                       
-                       ((FinSet)c).setTabRelativePosition(position);
-                       
-               } else {
-                       if (relative == null) {
-                               warnings.add("Required attribute 'relativeto' not found for fin tab position.");
-                       } else {
-                               warnings.add("Illegal attribute value '" + relative + "' encountered.");
-                       }
-               }
-               
-               super.set(c, s, attributes, warnings);
-       }
-
-       
-}
-
-
-class ClusterConfigurationSetter implements Setter {
-
-       public void set(RocketComponent component, String value, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               
-               if (!(component instanceof Clusterable)) {
-                       warnings.add("Illegal component defined as cluster.");
-                       return;
-               }
-               
-               ClusterConfiguration config = null;
-               for (ClusterConfiguration c: ClusterConfiguration.CONFIGURATIONS) {
-                       if (c.getXMLName().equals(value)) {
-                               config = c;
-                               break;
-                       }
-               }
-               
-               if (config == null) {
-                       warnings.add("Illegal cluster configuration specified.");
-                       return;
-               }
-               
-               ((Clusterable)component).setClusterConfiguration(config);
-       }
-}
-
-
diff --git a/src/net/sf/openrocket/file/OpenRocketSaver.java b/src/net/sf/openrocket/file/OpenRocketSaver.java
deleted file mode 100644 (file)
index 312aa0b..0000000
+++ /dev/null
@@ -1,531 +0,0 @@
-package net.sf.openrocket.file;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.zip.GZIPOutputStream;
-
-import net.sf.openrocket.aerodynamics.Warning;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.document.StorageOptions;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.simulation.FlightData;
-import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.SimulationConditions;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Pair;
-import net.sf.openrocket.util.Prefs;
-import net.sf.openrocket.util.Reflection;
-import net.sf.openrocket.util.TextUtil;
-
-public class OpenRocketSaver extends RocketSaver {
-       
-       /**
-        * Divisor used in converting an integer version to the point-represented version.
-        * The integer version divided by this value is the major version and the remainder is
-        * the minor version.  For example 101 corresponds to file version "1.1".
-        */
-       public static final int FILE_VERSION_DIVISOR = 100;
-
-       
-       private static final String OPENROCKET_CHARSET = "UTF-8";
-       
-       private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
-       private static final String METHOD_SUFFIX = "Saver";
-       
-       
-       // Estimated storage used by different portions
-       // These have been hand-estimated from saved files
-       private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
-       private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
-       private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
-       private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
-       private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
-       private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
-       
-       
-       private int indent;
-       private Writer dest;
-       
-       @Override
-       public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
-       throws IOException {
-               
-               if (options.isCompressionEnabled()) {
-                       output = new GZIPOutputStream(output);
-               }
-               
-               dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); 
-               
-               final int fileVersion = calculateNecessaryFileVersion(document, options);
-               final String fileVersionString = 
-                       (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR); 
-               
-               
-               this.indent = 0;
-               
-               System.out.println("Writing...");
-               
-               writeln("<?xml version='1.0' encoding='utf-8'?>");
-               writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
-                               + Prefs.getVersion() + "\">");
-               indent++;
-               
-               // Recursively save the rocket structure
-               saveComponent(document.getRocket());
-               
-               writeln("");
-               
-               // Save all simulations
-               writeln("<simulations>");
-               indent++;
-               boolean first = true;
-               for (Simulation s: document.getSimulations()) {
-                       if (!first)
-                               writeln("");
-                       first = false;
-                       saveSimulation(s, options.getSimulationTimeSkip());
-               }
-               indent--;
-               writeln("</simulations>");
-               
-               indent--;
-               writeln("</openrocket>");
-               
-               dest.flush();
-               if (options.isCompressionEnabled()) {
-                       ((GZIPOutputStream)output).finish();
-               }
-       }
-       
-       
-       
-       @Override
-       public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
-               
-               long size = 0;
-               
-               // Size per component
-               int componentCount = 0;
-               Rocket rocket = doc.getRocket();
-               Iterator<RocketComponent> iterator = rocket.deepIterator(true);
-               while (iterator.hasNext()) {
-                       iterator.next();
-                       componentCount++;
-               }
-               
-               if (options.isCompressionEnabled())
-                       size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
-               else
-                       size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
-               
-               
-               // Size per simulation
-               if (options.isCompressionEnabled())
-                       size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
-               else
-                       size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
-               
-               
-               // Size per flight data point
-               int pointCount = 0;
-               double timeSkip = options.getSimulationTimeSkip();
-               if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
-                       for (Simulation s: doc.getSimulations()) {
-                               FlightData data = s.getSimulatedData();
-                               if (data != null) {
-                                       for (int i=0; i < data.getBranchCount(); i++) {
-                                               pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
-                                       }
-                               }
-                       }
-               }
-               
-               if (options.isCompressionEnabled())
-                       size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
-               else
-                       size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
-               
-               return size;
-       }
-       
-
-       /**
-        * Determine which file version is required in order to store all the features of the
-        * current design.  By default the oldest version that supports all the necessary features
-        * will be used.
-        * 
-        * @param document      the document to output.
-        * @param opts          the storage options.
-        * @return                      the integer file version to use.
-        */
-       private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
-               /*
-                * File version 1.1 is required for:
-                *  - fin tabs
-                *  - components attached to tube coupler
-                * 
-                * Otherwise use version 1.0.
-                */
-               
-               // Check for fin tabs (version 1.1)
-               Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
-               while (iterator.hasNext()) {
-                       RocketComponent c = iterator.next();
-                       
-                       // Check for fin tabs
-                       if (c instanceof FinSet) {
-                               FinSet fin = (FinSet)c;
-                               if (!MathUtil.equals(fin.getTabHeight(),0) &&
-                                               !MathUtil.equals(fin.getTabLength(), 0)) {
-                                       return FILE_VERSION_DIVISOR + 1;
-                               }
-                       }
-                       
-                       // Check for components attached to tube coupler
-                       if (c instanceof TubeCoupler) {
-                               if (c.getChildCount() > 0) {
-                                       return FILE_VERSION_DIVISOR + 1;
-                               }
-                       }
-               }
-               
-               // Default (version 1.0)
-               return FILE_VERSION_DIVISOR + 0;
-       }
-       
-       
-       
-       @SuppressWarnings("unchecked")
-       private void saveComponent(RocketComponent component) throws IOException {
-               
-               Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
-                               "getElements", RocketComponent.class);
-               if (m==null) {
-                       throw new RuntimeException("Unable to find saving class for component "+
-                                       component.getComponentName());
-               }
-
-               // Get the strings to save
-               List<String> list = (List<String>) m.invokeStatic(component);
-               int length = list.size();
-               
-               if (length == 0)  // Nothing to do
-                       return;
-
-               if (length < 2) {
-                       throw new RuntimeException("BUG, component data length less than two lines.");
-               }
-               
-               // Open element
-               writeln(list.get(0));
-               indent++;
-               
-               // Write parameters
-               for (int i=1; i<length-1; i++) {
-                       writeln(list.get(i));
-               }
-               
-               // Recursively write subcomponents
-               if (component.getChildCount() > 0) {
-                       writeln("");
-                       writeln("<subcomponents>");
-                       indent++;
-                       boolean emptyline = false;
-                       for (RocketComponent subcomponent: component) {
-                               if (emptyline)
-                                       writeln("");
-                               emptyline = true;
-                               saveComponent(subcomponent);
-                       }
-                       indent--;
-                       writeln("</subcomponents>");
-               }
-               
-               // Close element
-               indent--;
-               writeln(list.get(length-1));
-       }
-
-       
-       
-       private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
-               SimulationConditions cond = simulation.getConditions();
-               
-               writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
-               indent++;
-               
-               writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
-               // TODO: MEDIUM: Other simulators/calculators
-               writeln("<simulator>RK4Simulator</simulator>");
-               writeln("<calculator>BarrowmanCalculator</calculator>");
-               writeln("<conditions>");
-               indent++;
-               
-               writeElement("configid", cond.getMotorConfigurationID());
-               writeElement("launchrodlength", cond.getLaunchRodLength());
-               writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI); 
-               writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
-               writeElement("windaverage", cond.getWindSpeedAverage());
-               writeElement("windturbulence", cond.getWindTurbulenceIntensity());
-               writeElement("launchaltitude", cond.getLaunchAltitude());
-               writeElement("launchlatitude", cond.getLaunchLatitude());
-               
-               if (cond.isISAAtmosphere()) {
-                       writeln("<atmosphere model=\"isa\"/>");
-               } else {
-                       writeln("<atmosphere model=\"extendedisa\">");
-                       indent++;
-                       writeElement("basetemperature", cond.getLaunchTemperature());
-                       writeElement("basepressure", cond.getLaunchPressure());
-                       indent--;
-                       writeln("</atmosphere>");
-               }
-
-               writeElement("timestep", cond.getTimeStep());
-               
-               indent--;
-               writeln("</conditions>");
-               
-               
-               for (String s: simulation.getSimulationListeners()) {
-                       writeElement("listener", escapeXML(s));
-               }
-               
-               
-               // Write basic simulation data
-               
-               FlightData data = simulation.getSimulatedData();
-               if (data != null) {
-                       String str = "<flightdata";
-                       if (!Double.isNaN(data.getMaxAltitude()))
-                               str += " maxaltitude=\"" + TextUtil.doubleToString(data.getMaxAltitude()) + "\"";
-                       if (!Double.isNaN(data.getMaxVelocity()))
-                               str += " maxvelocity=\"" + TextUtil.doubleToString(data.getMaxVelocity()) + "\"";
-                       if (!Double.isNaN(data.getMaxAcceleration()))
-                               str += " maxacceleration=\"" + TextUtil.doubleToString(data.getMaxAcceleration()) + "\"";
-                       if (!Double.isNaN(data.getMaxMachNumber()))
-                               str += " maxmach=\"" + TextUtil.doubleToString(data.getMaxMachNumber()) + "\"";
-                       if (!Double.isNaN(data.getTimeToApogee()))
-                               str += " timetoapogee=\"" + TextUtil.doubleToString(data.getTimeToApogee()) + "\"";
-                       if (!Double.isNaN(data.getFlightTime()))
-                               str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\"";
-                       if (!Double.isNaN(data.getGroundHitVelocity()))
-                               str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\"";
-                       str += ">";
-                       writeln(str);
-                       indent++;
-                       
-                       for (Warning w: data.getWarningSet()) {
-                               writeElement("warning", escapeXML(w.toString()));
-                       }
-                       
-                       // Check whether to store data
-                       if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
-                               timeSkip = 0;
-                       
-                       if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
-                               for (int i=0; i<data.getBranchCount(); i++) {
-                                       FlightDataBranch branch = data.getBranch(i);
-                                       saveFlightDataBranch(branch, timeSkip);
-                               }
-                       }
-                       
-                       indent--;
-                       writeln("</flightdata>");
-               }
-               
-               indent--;
-               writeln("</simulation>");
-               
-       }
-       
-       
-       
-       private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) 
-       throws IOException {
-               double previousTime = -100000;
-               
-               if (branch == null)
-                       return;
-               
-               // Retrieve the types from the branch
-               FlightDataBranch.Type[] types = branch.getTypes();
-               
-               if (types.length == 0)
-                       return;
-               
-               // Retrieve the data from the branch
-               List<List<Double>> data = new ArrayList<List<Double>>(types.length);
-               for (int i=0; i<types.length; i++) {
-                       data.add(branch.get(types[i]));
-               }
-               List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
-               if (timeData == null) {
-                       // TODO: MEDIUM: External data may not have time data
-                       throw new IllegalArgumentException("Data did not contain time data");
-               }
-               
-               // Build the <databranch> tag
-               StringBuilder sb = new StringBuilder();
-               sb.append("<databranch name=\"");
-               sb.append(escapeXML(branch.getBranchName()));
-               sb.append("\" types=\"");
-               for (int i=0; i<types.length; i++) {
-                       if (i > 0)
-                               sb.append(",");
-                       sb.append(escapeXML(types[i].getName()));
-               }
-               sb.append("\">");
-               writeln(sb.toString());
-               indent++;
-               
-               // Write events
-               for (Pair<Double,FlightEvent> p: branch.getEvents()) {
-                       writeln("<event time=\"" + TextUtil.doubleToString(p.getU())
-                                       + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
-               }
-               
-               // Write the data
-               int length = branch.getLength();
-               if (length > 0) {
-                       writeDataPointString(data, 0, sb);
-                       previousTime = timeData.get(0);
-               }
-               
-               for (int i=1; i < length-1; i++) {
-                       if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
-                                       Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
-                               writeDataPointString(data, i, sb);
-                               previousTime = timeData.get(i);
-                       }
-               }
-               
-               if (length > 1) {
-                       writeDataPointString(data, length-1, sb);
-               }
-               
-               indent--;
-               writeln("</databranch>");
-       }
-       
-       
-       
-       /* TODO: LOW: This is largely duplicated from above! */
-       private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
-               int count = 0;
-
-               double previousTime = -100000;
-               
-               if (branch == null)
-                       return 0;
-               
-               // Retrieve the types from the branch
-               FlightDataBranch.Type[] types = branch.getTypes();
-               
-               if (types.length == 0)
-                       return 0;
-               
-               List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
-               if (timeData == null) {
-                       // TODO: MEDIUM: External data may not have time data
-                       throw new IllegalArgumentException("Data did not contain time data");
-               }
-               
-               // Write the data
-               int length = branch.getLength();
-               if (length > 0) {
-                       count++;
-                       previousTime = timeData.get(0);
-               }
-               
-               for (int i=1; i < length-1; i++) {
-                       if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
-                                       Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
-                               count++;
-                               previousTime = timeData.get(i);
-                       }
-               }
-               
-               if (length > 1) {
-                       count++;
-               }
-
-               return count;
-       }
-       
-       
-       
-       private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
-       throws IOException {
-               sb.setLength(0);
-               sb.append("<datapoint>");
-               for (int j=0; j < data.size(); j++) {
-                       if (j > 0)
-                               sb.append(",");
-                       sb.append(TextUtil.doubleToString(data.get(j).get(index)));
-               }
-               sb.append("</datapoint>");
-               writeln(sb.toString());
-       }
-       
-       
-       
-       private void writeElement(String element, Object content) throws IOException {
-               if (content == null)
-                       content = "";
-               writeln("<"+element+">"+content+"</"+element+">");
-       }
-
-
-       
-       private void writeln(String str) throws IOException {
-               if (str.length() == 0) {
-                       dest.write("\n");
-                       return;
-               }
-               String s="";
-               for (int i=0; i<indent; i++)
-                       s=s+"  ";
-               s = s+str+"\n";
-               dest.write(s);
-       }
-       
-       
-       public static void main(String[] arg) {
-               double d = -0.000000123456789123;
-               
-               
-               for (int i=0; i< 20; i++) {
-                       String str = TextUtil.doubleToString(d);
-                       System.out.println(str + "   ->   " + Double.parseDouble(str));
-                       d *= 10;
-               }
-               
-               
-               System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
-               
-       }
-
-       
-       /**
-        * Return the XML equivalent of an enum name.
-        * 
-        * @param e             the enum to save.
-        * @return              the corresponding XML name.
-        */
-       public static String enumToXMLName(Enum<?> e) {
-               return e.name().toLowerCase().replace("_", "");
-       }
-       
-}
diff --git a/src/net/sf/openrocket/file/RASPMotorLoader.java b/src/net/sf/openrocket/file/RASPMotorLoader.java
deleted file mode 100644 (file)
index 409d218..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-package net.sf.openrocket.file;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-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;
-
-public class RASPMotorLoader extends MotorLoader {
-
-       public static final String CHARSET_NAME = "ISO-8859-1";
-       
-       public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
-       
-       
-       
-       
-       @Override
-       protected Charset getDefaultCharset() {
-               return CHARSET;
-       }
-
-       
-       /**
-        * Load a <code>Motor</code> from a RASP file specified by the <code>Reader</code>.
-        * The <code>Reader</code> is responsible for using the correct charset.
-        * <p>
-        * 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 illegal.
-        */
-       @Override
-       public List<Motor> load(Reader reader, String filename) throws IOException {
-               List<Motor> motors = new ArrayList<Motor>();
-               BufferedReader in = new BufferedReader(reader);
-
-               String manufacturer = "";
-               String designation = "";
-               String comment = "";
-               
-               double length = 0;
-               double diameter = 0;
-               ArrayList<Double> delays = null;
-               
-               List<Double> time = new ArrayList<Double>();
-               List<Double> thrust = new ArrayList<Double>();
-               
-               double propW = 0;
-               double totalW = 0;
-               
-               try {
-                       String line;
-                       String[] pieces, buf;
-
-                       line = in.readLine();
-                       main: while (line != null) {   // Until EOF
-
-                               manufacturer = "";
-                               designation = "";
-                               comment = "";
-                               length = 0;
-                               diameter = 0;
-                               delays = new ArrayList<Double>();
-                               propW = 0;
-                               totalW = 0;
-                               time.clear();
-                               thrust .clear();
-                       
-                               // Read comment
-                               while (line.length()==0 || line.charAt(0)==';') {
-                                       if (line.length() > 0) {
-                                               comment += line.substring(1).trim() + "\n";
-                                       }
-                                       line = in.readLine();
-                                       if (line == null)
-                                               break main;
-                               }
-                               comment = comment.trim();
-                               
-                               // Parse header line, example:
-                               // F32 24 124 5-10-15-P .0377 .0695 RV
-                               // desig diam len delays prop.w tot.w manufacturer
-                               pieces = split(line);
-                               if (pieces.length != 7) {
-                                       throw new IOException("Illegal file format.");
-                               }
-                               
-                               designation = pieces[0];
-                               diameter = Double.parseDouble(pieces[1]) / 1000.0;
-                               length = Double.parseDouble(pieces[2]) / 1000.0;
-                               
-                               if (pieces[3].equalsIgnoreCase("None")) {
-
-                               } else {
-                                       buf = split(pieces[3],"[-,]+");
-                                       for (int i=0; i < buf.length; i++) {
-                                               if (buf[i].equalsIgnoreCase("P") || 
-                                                               buf[i].equalsIgnoreCase("plugged")) {
-                                                       delays.add(Motor.PLUGGED);
-                                               } else {
-                                                       // Many RASP files have "100" as an only delay
-                                                       double d = Double.parseDouble(buf[i]);
-                                                       if (d < 99)
-                                                               delays.add(d);
-                                               }
-                                       }
-                                       Collections.sort(delays);
-                               }
-                               
-                               propW = Double.parseDouble(pieces[4]);
-                               totalW = Double.parseDouble(pieces[5]);
-                               manufacturer = pieces[6];
-                               
-                               if (propW > totalW) {
-                                       throw new IOException("Propellant weight exceeds total weight in " +
-                                                       "RASP file");
-                               }
-                               
-                               // Read the data
-                               for (line = in.readLine(); 
-                                        (line != null) && (line.length()==0 || line.charAt(0) != ';');
-                                        line = in.readLine()) {
-                                       
-                                       buf = split(line);
-                                       if (buf.length == 0) {
-                                               continue;
-                                       } else if (buf.length == 2) {
-                                               
-                                               time.add(Double.parseDouble(buf[0]));
-                                               thrust .add(Double.parseDouble(buf[1]));
-                                               
-                                       } else {
-                                               throw new IOException("Illegal file format.");
-                                       }
-                               }
-                               
-                               // Comment of EOF encountered, marks the start of the next motor
-                               if (time.size() < 2) {
-                                       throw new IOException("Illegal file format, too short thrust-curve.");
-                               }
-                               double[] delayArray = new double[delays.size()];
-                               for (int i=0; i<delays.size(); i++) {
-                                       delayArray[i] = delays.get(i);
-                               }
-                               motors.add(createRASPMotor(manufacturer, designation, comment,
-                                               length, diameter, delayArray, propW, totalW, time, thrust));
-                       }
-                       
-               } catch (NumberFormatException e) {
-                       
-                       throw new IOException("Illegal file format.");
-                       
-               } finally {
-                       
-                       in.close();
-                       
-               }
-               
-               return motors;
-       }
-       
-       
-       /**
-        * Create a motor from RASP file data.
-        * @throws IOException  if the data is illegal for a thrust curve
-        */
-       private static Motor createRASPMotor(String manufacturer, String designation,
-                       String comment, double length, double diameter, double[] delays,
-                       double propW, double totalW, List<Double> time, List<Double> thrust) 
-                       throws IOException {
-               
-               // Add zero time/thrust if necessary
-               sortLists(time, thrust);
-               finalizeThrustCurve(time, thrust);
-               List<Double> mass = calculateMass(time,thrust,totalW,propW);
-               
-               double[] timeArray = new double[time.size()];
-               double[] thrustArray = new double[time.size()];
-               Coordinate[] cgArray = new Coordinate[time.size()];
-               for (int i=0; i < time.size(); i++) {
-                       timeArray[i] = time.get(i);
-                       thrustArray[i] = thrust.get(i);
-                       cgArray[i] = new Coordinate(length/2,0,0,mass.get(i));
-               }
-               
-               designation = removeDelay(designation);
-               
-               // Create the motor digest from data available in RASP files
-               MotorDigest motorDigest = new MotorDigest();
-               motorDigest.update(DataType.TIME_ARRAY, timeArray);
-               motorDigest.update(DataType.MASS_SPECIFIC, totalW, totalW-propW);
-               motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
-               final String digest = motorDigest.getDigest();
-               
-               
-               try {
-                       
-                       return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
-                                       designation, comment, Motor.Type.UNKNOWN,
-                                       delays, diameter, length, timeArray, thrustArray, cgArray, digest);
-                       
-               } catch (IllegalArgumentException e) {
-                       
-                       // Bad data read from file.
-                       throw new IOException("Illegal file format.", e);
-                       
-               }
-       }
-}
diff --git a/src/net/sf/openrocket/file/RockSimMotorLoader.java b/src/net/sf/openrocket/file/RockSimMotorLoader.java
deleted file mode 100644 (file)
index 48a1590..0000000
+++ /dev/null
@@ -1,461 +0,0 @@
-package net.sf.openrocket.file;
-
-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.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;
-       }
-}
diff --git a/src/net/sf/openrocket/file/motor/RASPMotorLoader.java b/src/net/sf/openrocket/file/motor/RASPMotorLoader.java
new file mode 100644 (file)
index 0000000..02feb9b
--- /dev/null
@@ -0,0 +1,222 @@
+package net.sf.openrocket.file.motor;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.openrocket.file.MotorLoader;
+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;
+
+public class RASPMotorLoader extends MotorLoader {
+
+       public static final String CHARSET_NAME = "ISO-8859-1";
+       
+       public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
+       
+       
+       
+       
+       @Override
+       protected Charset getDefaultCharset() {
+               return CHARSET;
+       }
+
+       
+       /**
+        * Load a <code>Motor</code> from a RASP file specified by the <code>Reader</code>.
+        * The <code>Reader</code> is responsible for using the correct charset.
+        * <p>
+        * 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 illegal.
+        */
+       @Override
+       public List<Motor> load(Reader reader, String filename) throws IOException {
+               List<Motor> motors = new ArrayList<Motor>();
+               BufferedReader in = new BufferedReader(reader);
+
+               String manufacturer = "";
+               String designation = "";
+               String comment = "";
+               
+               double length = 0;
+               double diameter = 0;
+               ArrayList<Double> delays = null;
+               
+               List<Double> time = new ArrayList<Double>();
+               List<Double> thrust = new ArrayList<Double>();
+               
+               double propW = 0;
+               double totalW = 0;
+               
+               try {
+                       String line;
+                       String[] pieces, buf;
+
+                       line = in.readLine();
+                       main: while (line != null) {   // Until EOF
+
+                               manufacturer = "";
+                               designation = "";
+                               comment = "";
+                               length = 0;
+                               diameter = 0;
+                               delays = new ArrayList<Double>();
+                               propW = 0;
+                               totalW = 0;
+                               time.clear();
+                               thrust .clear();
+                       
+                               // Read comment
+                               while (line.length()==0 || line.charAt(0)==';') {
+                                       if (line.length() > 0) {
+                                               comment += line.substring(1).trim() + "\n";
+                                       }
+                                       line = in.readLine();
+                                       if (line == null)
+                                               break main;
+                               }
+                               comment = comment.trim();
+                               
+                               // Parse header line, example:
+                               // F32 24 124 5-10-15-P .0377 .0695 RV
+                               // desig diam len delays prop.w tot.w manufacturer
+                               pieces = split(line);
+                               if (pieces.length != 7) {
+                                       throw new IOException("Illegal file format.");
+                               }
+                               
+                               designation = pieces[0];
+                               diameter = Double.parseDouble(pieces[1]) / 1000.0;
+                               length = Double.parseDouble(pieces[2]) / 1000.0;
+                               
+                               if (pieces[3].equalsIgnoreCase("None")) {
+
+                               } else {
+                                       buf = split(pieces[3],"[-,]+");
+                                       for (int i=0; i < buf.length; i++) {
+                                               if (buf[i].equalsIgnoreCase("P") || 
+                                                               buf[i].equalsIgnoreCase("plugged")) {
+                                                       delays.add(Motor.PLUGGED);
+                                               } else {
+                                                       // Many RASP files have "100" as an only delay
+                                                       double d = Double.parseDouble(buf[i]);
+                                                       if (d < 99)
+                                                               delays.add(d);
+                                               }
+                                       }
+                                       Collections.sort(delays);
+                               }
+                               
+                               propW = Double.parseDouble(pieces[4]);
+                               totalW = Double.parseDouble(pieces[5]);
+                               manufacturer = pieces[6];
+                               
+                               if (propW > totalW) {
+                                       throw new IOException("Propellant weight exceeds total weight in " +
+                                                       "RASP file");
+                               }
+                               
+                               // Read the data
+                               for (line = in.readLine(); 
+                                        (line != null) && (line.length()==0 || line.charAt(0) != ';');
+                                        line = in.readLine()) {
+                                       
+                                       buf = split(line);
+                                       if (buf.length == 0) {
+                                               continue;
+                                       } else if (buf.length == 2) {
+                                               
+                                               time.add(Double.parseDouble(buf[0]));
+                                               thrust .add(Double.parseDouble(buf[1]));
+                                               
+                                       } else {
+                                               throw new IOException("Illegal file format.");
+                                       }
+                               }
+                               
+                               // Comment of EOF encountered, marks the start of the next motor
+                               if (time.size() < 2) {
+                                       throw new IOException("Illegal file format, too short thrust-curve.");
+                               }
+                               double[] delayArray = new double[delays.size()];
+                               for (int i=0; i<delays.size(); i++) {
+                                       delayArray[i] = delays.get(i);
+                               }
+                               motors.add(createRASPMotor(manufacturer, designation, comment,
+                                               length, diameter, delayArray, propW, totalW, time, thrust));
+                       }
+                       
+               } catch (NumberFormatException e) {
+                       
+                       throw new IOException("Illegal file format.");
+                       
+               } finally {
+                       
+                       in.close();
+                       
+               }
+               
+               return motors;
+       }
+       
+       
+       /**
+        * Create a motor from RASP file data.
+        * @throws IOException  if the data is illegal for a thrust curve
+        */
+       private static Motor createRASPMotor(String manufacturer, String designation,
+                       String comment, double length, double diameter, double[] delays,
+                       double propW, double totalW, List<Double> time, List<Double> thrust) 
+                       throws IOException {
+               
+               // Add zero time/thrust if necessary
+               sortLists(time, thrust);
+               finalizeThrustCurve(time, thrust);
+               List<Double> mass = calculateMass(time,thrust,totalW,propW);
+               
+               double[] timeArray = new double[time.size()];
+               double[] thrustArray = new double[time.size()];
+               Coordinate[] cgArray = new Coordinate[time.size()];
+               for (int i=0; i < time.size(); i++) {
+                       timeArray[i] = time.get(i);
+                       thrustArray[i] = thrust.get(i);
+                       cgArray[i] = new Coordinate(length/2,0,0,mass.get(i));
+               }
+               
+               designation = removeDelay(designation);
+               
+               // Create the motor digest from data available in RASP files
+               MotorDigest motorDigest = new MotorDigest();
+               motorDigest.update(DataType.TIME_ARRAY, timeArray);
+               motorDigest.update(DataType.MASS_SPECIFIC, totalW, totalW-propW);
+               motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
+               final String digest = motorDigest.getDigest();
+               
+               
+               try {
+                       
+                       return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
+                                       designation, comment, Motor.Type.UNKNOWN,
+                                       delays, diameter, length, timeArray, thrustArray, cgArray, digest);
+                       
+               } catch (IllegalArgumentException e) {
+                       
+                       // Bad data read from file.
+                       throw new IOException("Illegal file format.", e);
+                       
+               }
+       }
+}
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;
+       }
+}
diff --git a/src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java b/src/net/sf/openrocket/file/openrocket/BodyComponentSaver.java
deleted file mode 100644 (file)
index 97dc5eb..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-public class BodyComponentSaver extends ExternalComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               // Body components have a natural length, store it now
-               elements.add("<length>"+((net.sf.openrocket.rocketcomponent.BodyComponent)c).getLength()+"</length>");
-       }
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java b/src/net/sf/openrocket/file/openrocket/BodyTubeSaver.java
deleted file mode 100644 (file)
index 865c6c8..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class BodyTubeSaver extends SymmetricComponentSaver {
-
-       private static final BodyTubeSaver instance = new BodyTubeSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<bodytube>");
-               instance.addParams(c, list);
-               list.add("</bodytube>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube) c;
-
-               if (tube.isRadiusAutomatic())
-                       elements.add("<radius>auto</radius>");
-               else
-                       elements.add("<radius>" + tube.getRadius() + "</radius>");
-
-               if (tube.isMotorMount()) {
-                       elements.addAll(motorMountParams(tube));
-               }
-       }
-
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/BulkheadSaver.java b/src/net/sf/openrocket/file/openrocket/BulkheadSaver.java
deleted file mode 100644 (file)
index c4e5b6a..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class BulkheadSaver extends RadiusRingComponentSaver {
-
-       private static final BulkheadSaver instance = new BulkheadSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<bulkhead>");
-               instance.addParams(c, list);
-               list.add("</bulkhead>");
-
-               return list;
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java b/src/net/sf/openrocket/file/openrocket/CenteringRingSaver.java
deleted file mode 100644 (file)
index 9014fb0..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class CenteringRingSaver extends RadiusRingComponentSaver {
-
-       private static final CenteringRingSaver instance = new CenteringRingSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<centeringring>");
-               instance.addParams(c, list);
-               list.add("</centeringring>");
-
-               return list;
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java b/src/net/sf/openrocket/file/openrocket/ComponentAssemblySaver.java
deleted file mode 100644 (file)
index 8a73471..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-public class ComponentAssemblySaver extends RocketComponentSaver {
-
-       // No-op
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/EllipticalFinSetSaver.java
deleted file mode 100644 (file)
index 8874a7a..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class EllipticalFinSetSaver extends FinSetSaver {
-
-       private static final EllipticalFinSetSaver instance = new EllipticalFinSetSaver();
-       
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-               
-               list.add("<ellipticalfinset>");
-               instance.addParams(c,list);
-               list.add("</ellipticalfinset>");
-               
-               return list;
-       }
-       
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               net.sf.openrocket.rocketcomponent.EllipticalFinSet fins = (net.sf.openrocket.rocketcomponent.EllipticalFinSet)c;
-               elements.add("<rootchord>"+fins.getLength()+"</rootchord>");
-               elements.add("<height>"+fins.getHeight()+"</height>");
-       }
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java b/src/net/sf/openrocket/file/openrocket/EngineBlockSaver.java
deleted file mode 100644 (file)
index 4932322..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class EngineBlockSaver extends ThicknessRingComponentSaver {
-
-       private static final EngineBlockSaver instance = new EngineBlockSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<engineblock>");
-               instance.addParams(c, list);
-               list.add("</engineblock>");
-
-               return list;
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/ExternalComponentSaver.java
deleted file mode 100644 (file)
index 7ca8b8f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-
-
-public class ExternalComponentSaver extends RocketComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               ExternalComponent ext = (ExternalComponent)c;
-               
-               // Finish enum names are currently the same except for case
-               elements.add("<finish>" + ext.getFinish().name().toLowerCase() + "</finish>");
-               
-               // Material
-               elements.add(materialParam(ext.getMaterial()));
-       }
-               
-}
diff --git a/src/net/sf/openrocket/file/openrocket/FinSetSaver.java b/src/net/sf/openrocket/file/openrocket/FinSetSaver.java
deleted file mode 100644 (file)
index 756115e..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.util.MathUtil;
-
-public class FinSetSaver extends ExternalComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               net.sf.openrocket.rocketcomponent.FinSet fins = (net.sf.openrocket.rocketcomponent.FinSet) c;
-               elements.add("<fincount>" + fins.getFinCount() + "</fincount>");
-               elements.add("<rotation>" + (fins.getBaseRotation() * 180.0 / Math.PI) + "</rotation>");
-               elements.add("<thickness>" + fins.getThickness() + "</thickness>");
-               elements.add("<crosssection>" + fins.getCrossSection().name().toLowerCase()
-                               + "</crosssection>");
-               elements.add("<cant>" + (fins.getCantAngle() * 180.0 / Math.PI) + "</cant>");
-               
-               // Save fin tabs only if they exist (compatibility with file version < 1.1)
-               if (!MathUtil.equals(fins.getTabHeight(),0) &&
-                               !MathUtil.equals(fins.getTabLength(), 0)) {
-                       
-                       elements.add("<tabheight>" + fins.getTabHeight() + "</tabheight>");
-                       elements.add("<tablength>" + fins.getTabLength() + "</tablength>");
-                       elements.add("<tabposition relativeto=\"" +
-                                       fins.getTabRelativePosition().name().toLowerCase() + "\">" +
-                                       fins.getTabShift() + "</tabposition>");
-               
-               }
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/FreeformFinSetSaver.java
deleted file mode 100644 (file)
index 6aa1778..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.util.Coordinate;
-
-
-public class FreeformFinSetSaver extends FinSetSaver {
-
-       private static final FreeformFinSetSaver instance = new FreeformFinSetSaver();
-       
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-               
-               list.add("<freeformfinset>");
-               instance.addParams(c,list);
-               list.add("</freeformfinset>");
-               
-               return list;
-       }
-       
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               FreeformFinSet fins = (FreeformFinSet)c;
-               elements.add("<finpoints>");
-               for (Coordinate p: fins.getFinPoints()) {
-                       elements.add("  <point x=\"" + p.x + "\" y=\"" + p.y + "\"/>");
-               }
-               elements.add("</finpoints>");
-       }
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java b/src/net/sf/openrocket/file/openrocket/InnerTubeSaver.java
deleted file mode 100644 (file)
index f706917..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.InnerTube;
-
-
-public class InnerTubeSaver extends ThicknessRingComponentSaver {
-
-       private static final InnerTubeSaver instance = new InnerTubeSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<innertube>");
-               instance.addParams(c, list);
-               list.add("</innertube>");
-
-               return list;
-       }
-
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               InnerTube tube = (InnerTube) c;
-
-               elements.add("<clusterconfiguration>" + tube.getClusterConfiguration().getXMLName()
-                               + "</clusterconfiguration>");
-               elements.add("<clusterscale>" + tube.getClusterScale() + "</clusterscale>");
-               elements.add("<clusterrotation>" + (tube.getClusterRotation() * 180.0 / Math.PI)
-                               + "</clusterrotation>");
-
-               if (tube.isMotorMount()) {
-                       elements.addAll(motorMountParams(tube));
-               }
-
-
-       }
-
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/InternalComponentSaver.java
deleted file mode 100644 (file)
index 45c0a56..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-public class InternalComponentSaver extends RocketComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               // Nothing to save
-       }
-               
-}
diff --git a/src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java b/src/net/sf/openrocket/file/openrocket/LaunchLugSaver.java
deleted file mode 100644 (file)
index 06d3da5..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-
-
-public class LaunchLugSaver extends ExternalComponentSaver {
-
-       private static final LaunchLugSaver instance = new LaunchLugSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<launchlug>");
-               instance.addParams(c, list);
-               list.add("</launchlug>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               LaunchLug lug = (LaunchLug) c;
-
-               elements.add("<radius>" + lug.getRadius() + "</radius>");
-               elements.add("<length>" + lug.getLength() + "</length>");
-               elements.add("<thickness>" + lug.getThickness() + "</thickness>");
-               elements.add("<radialdirection>" + (lug.getRadialDirection()*180.0/Math.PI) + "</radialdirection>");
-       }
-
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/MassComponentSaver.java b/src/net/sf/openrocket/file/openrocket/MassComponentSaver.java
deleted file mode 100644 (file)
index 11a5ce6..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.MassComponent;
-
-
-public class MassComponentSaver extends MassObjectSaver {
-
-       private static final MassComponentSaver instance = new MassComponentSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<masscomponent>");
-               instance.addParams(c, list);
-               list.add("</masscomponent>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               MassComponent mass = (MassComponent) c;
-
-               elements.add("<mass>" + mass.getMass() + "</mass>");
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/MassObjectSaver.java b/src/net/sf/openrocket/file/openrocket/MassObjectSaver.java
deleted file mode 100644 (file)
index f1ee440..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.MassObject;
-
-
-public class MassObjectSaver extends InternalComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               MassObject mass = (MassObject) c;
-
-               elements.add("<packedlength>" + mass.getLength() + "</packedlength>");
-               elements.add("<packedradius>" + mass.getRadius() + "</packedradius>");
-               elements.add("<radialposition>" + mass.getRadialPosition() + "</radialposition>");
-               elements.add("<radialdirection>" + (mass.getRadialDirection() * 180.0 / Math.PI)
-                               + "</radialdirection>");
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/NoseConeSaver.java b/src/net/sf/openrocket/file/openrocket/NoseConeSaver.java
deleted file mode 100644 (file)
index 95feafb..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class NoseConeSaver extends TransitionSaver {
-
-       private static final NoseConeSaver instance = new NoseConeSaver();
-       
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-               
-               list.add("<nosecone>");
-               instance.addParams(c,list);
-               list.add("</nosecone>");
-               
-               return list;
-       }
-       
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               // Transition handles nose cone saving as well
-       }
-}
diff --git a/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java
new file mode 100644 (file)
index 0000000..3b7b23d
--- /dev/null
@@ -0,0 +1,1978 @@
+package net.sf.openrocket.file.openrocket;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import net.sf.openrocket.aerodynamics.Warning;
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.database.Databases;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.document.Simulation.Status;
+import net.sf.openrocket.file.RocketLoadException;
+import net.sf.openrocket.file.RocketLoader;
+import net.sf.openrocket.file.simplesax.ElementHandler;
+import net.sf.openrocket.file.simplesax.PlainTextHandler;
+import net.sf.openrocket.file.simplesax.SimpleSAX;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.BodyComponent;
+import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.Bulkhead;
+import net.sf.openrocket.rocketcomponent.CenteringRing;
+import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
+import net.sf.openrocket.rocketcomponent.Clusterable;
+import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.rocketcomponent.EngineBlock;
+import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
+import net.sf.openrocket.rocketcomponent.InnerTube;
+import net.sf.openrocket.rocketcomponent.InternalComponent;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.MassObject;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.Parachute;
+import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.rocketcomponent.ReferenceType;
+import net.sf.openrocket.rocketcomponent.RingComponent;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.ShockCord;
+import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.rocketcomponent.Streamer;
+import net.sf.openrocket.rocketcomponent.StructuralComponent;
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.TubeCoupler;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
+import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
+import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.simulation.SimulationConditions;
+import net.sf.openrocket.simulation.FlightEvent.Type;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.LineStyle;
+import net.sf.openrocket.util.Reflection;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Class that loads a rocket definition from an OpenRocket rocket file.
+ * <p>
+ * This class uses SAX to read the XML file format.  The 
+ * {@link #loadFromStream(InputStream)} method simply sets the system up and 
+ * starts the parsing, while the actual logic is in the private inner class
+ * <code>OpenRocketHandler</code>.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class OpenRocketLoader extends RocketLoader {
+       
+       @Override
+       public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
+                       IOException {
+               InputSource xmlSource = new InputSource(source);
+               OpenRocketHandler handler = new OpenRocketHandler();
+
+               
+               try {
+                       SimpleSAX.readXML(xmlSource, handler, warnings);
+               } catch (SAXException e) {
+                       throw new RocketLoadException("Malformed XML in input.", e);
+               }
+
+               
+               OpenRocketDocument doc = handler.getDocument();
+               doc.getDefaultConfiguration().setAllStages();
+               
+               // Deduce suitable time skip
+               double timeSkip = StorageOptions.SIMULATION_DATA_NONE;
+               for (Simulation s: doc.getSimulations()) {
+                       if (s.getStatus() == Simulation.Status.EXTERNAL ||
+                                       s.getStatus() == Simulation.Status.NOT_SIMULATED)
+                               continue;
+                       if (s.getSimulatedData() == null)
+                               continue;
+                       if (s.getSimulatedData().getBranchCount() == 0)
+                               continue;
+                       FlightDataBranch branch = s.getSimulatedData().getBranch(0);
+                       if (branch == null)
+                               continue;
+                       List<Double> list = branch.get(FlightDataBranch.TYPE_TIME);
+                       if (list == null)
+                               continue;
+                       
+                       double previousTime = Double.NaN;
+                       for (double time: list) {
+                               if (time - previousTime < timeSkip)
+                                       timeSkip = time-previousTime;
+                               previousTime = time;
+                       }
+               }
+               // Round value
+               timeSkip = Math.rint(timeSkip*100)/100;
+
+               doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip);
+               doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed
+               doc.getDefaultStorageOptions().setExplicitlySet(false);
+               
+               doc.clearUndo();
+               return doc;
+       }
+
+}
+
+
+
+class DocumentConfig {
+
+       /* Remember to update OpenRocketSaver as well! */
+       public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1" };
+
+
+       ////////  Component constructors
+       static final HashMap<String, Constructor<? extends RocketComponent>> constructors = new HashMap<String, Constructor<? extends RocketComponent>>();
+       static {
+               try {
+                       // External components
+                       constructors.put("bodytube", BodyTube.class.getConstructor(new Class<?>[0]));
+                       constructors.put("transition", Transition.class.getConstructor(new Class<?>[0]));
+                       constructors.put("nosecone", NoseCone.class.getConstructor(new Class<?>[0]));
+                       constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
+                       constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
+                       constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
+                       constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
+
+                       // Internal components
+                       constructors.put("engineblock", EngineBlock.class.getConstructor(new Class<?>[0]));
+                       constructors.put("innertube", InnerTube.class.getConstructor(new Class<?>[0]));
+                       constructors.put("tubecoupler", TubeCoupler.class.getConstructor(new Class<?>[0]));
+                       constructors.put("bulkhead", Bulkhead.class.getConstructor(new Class<?>[0]));
+                       constructors.put("centeringring", CenteringRing.class.getConstructor(new Class<?>[0]));
+                       
+                       constructors.put("masscomponent", MassComponent.class.getConstructor(new Class<?>[0]));
+                       constructors.put("shockcord", ShockCord.class.getConstructor(new Class<?>[0]));
+                       constructors.put("parachute", Parachute.class.getConstructor(new Class<?>[0]));
+                       constructors.put("streamer", Streamer.class.getConstructor(new Class<?>[0]));
+                       
+                       // Other
+                       constructors.put("stage", Stage.class.getConstructor(new Class<?>[0]));
+                       
+               } catch (NoSuchMethodException e) {
+                       throw new RuntimeException(
+                                       "Error in constructing the 'constructors' HashMap.");
+               }
+       }
+
+
+       ////////  Parameter setters
+       /*
+        * The keys are of the form Class:param, where Class is the class name and param
+        * the element name.  Setters are searched for in descending class order.
+        * A setter of null means setting the parameter is not allowed.
+        */
+       static final HashMap<String, Setter> setters = new HashMap<String, Setter>();
+       static {
+               // RocketComponent
+               setters.put("RocketComponent:name", new StringSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setName", String.class)));
+               setters.put("RocketComponent:color", new ColorSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setColor", Color.class)));
+               setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
+                               Reflection.findMethodStatic(RocketComponent.class, "setLineStyle", LineStyle.class),
+                               LineStyle.class));
+               setters.put("RocketComponent:position", new PositionSetter());
+               setters.put("RocketComponent:overridemass", new OverrideSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideMass", double.class),
+                               Reflection.findMethodStatic(RocketComponent.class, "setMassOverridden", boolean.class)));
+               setters.put("RocketComponent:overridecg", new OverrideSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideCGX", double.class),
+                               Reflection.findMethodStatic(RocketComponent.class, "setCGOverridden", boolean.class)));
+               setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
+               setters.put("RocketComponent:comment", new StringSetter(
+                               Reflection.findMethodStatic(RocketComponent.class, "setComment", String.class)));
+               
+               // ExternalComponent
+               setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
+                               Reflection.findMethodStatic(ExternalComponent.class, "setFinish", Finish.class),
+                               Finish.class));
+               setters.put("ExternalComponent:material", new MaterialSetter(
+                               Reflection.findMethodStatic(ExternalComponent.class, "setMaterial", Material.class),
+                               Material.Type.BULK));
+                               
+               // BodyComponent
+               setters.put("BodyComponent:length", new DoubleSetter(
+                               Reflection.findMethodStatic(BodyComponent.class, "setLength", double.class)));
+               
+               // SymmetricComponent
+               setters.put("SymmetricComponent:thickness", new DoubleSetter(
+                               Reflection.findMethodStatic(SymmetricComponent.class,"setThickness", double.class), 
+                               "filled", 
+                               Reflection.findMethodStatic(SymmetricComponent.class,"setFilled", boolean.class)));
+               
+               // BodyTube
+               setters.put("BodyTube:radius", new DoubleSetter(
+                               Reflection.findMethodStatic(BodyTube.class, "setRadius", double.class), 
+                               "auto",
+                               Reflection.findMethodStatic(BodyTube.class,"setRadiusAutomatic", boolean.class)));
+                               
+               // Transition
+               setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
+                               Reflection.findMethodStatic(Transition.class, "setType", Transition.Shape.class),
+                               Transition.Shape.class));
+               setters.put("Transition:shapeclipped", new BooleanSetter(
+                               Reflection.findMethodStatic(Transition.class, "setClipped", boolean.class)));
+               setters.put("Transition:shapeparameter", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setShapeParameter", double.class)));
+                               
+               setters.put("Transition:foreradius", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setForeRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(Transition.class, "setForeRadiusAutomatic", boolean.class)));
+               setters.put("Transition:aftradius", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setAftRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(Transition.class, "setAftRadiusAutomatic", boolean.class)));
+
+               setters.put("Transition:foreshoulderradius", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setForeShoulderRadius", double.class)));
+               setters.put("Transition:foreshoulderlength", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setForeShoulderLength", double.class)));
+               setters.put("Transition:foreshoulderthickness", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setForeShoulderThickness", double.class)));
+               setters.put("Transition:foreshouldercapped", new BooleanSetter(
+                               Reflection.findMethodStatic(Transition.class, "setForeShoulderCapped", boolean.class)));
+               
+               setters.put("Transition:aftshoulderradius", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setAftShoulderRadius", double.class)));
+               setters.put("Transition:aftshoulderlength", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setAftShoulderLength", double.class)));
+               setters.put("Transition:aftshoulderthickness", new DoubleSetter(
+                               Reflection.findMethodStatic(Transition.class, "setAftShoulderThickness", double.class)));
+               setters.put("Transition:aftshouldercapped", new BooleanSetter(
+                               Reflection.findMethodStatic(Transition.class, "setAftShoulderCapped", boolean.class)));
+               
+               // NoseCone - disable disallowed elements
+               setters.put("NoseCone:foreradius", null);
+               setters.put("NoseCone:foreshoulderradius", null);
+               setters.put("NoseCone:foreshoulderlength", null);
+               setters.put("NoseCone:foreshoulderthickness", null);
+               setters.put("NoseCone:foreshouldercapped", null);
+               
+               // FinSet
+               setters.put("FinSet:fincount", new IntSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setFinCount", int.class)));
+               setters.put("FinSet:rotation", new DoubleSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setBaseRotation", double.class), Math.PI/180.0));
+               setters.put("FinSet:thickness", new DoubleSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setThickness", double.class)));
+               setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
+                               Reflection.findMethodStatic(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
+                               FinSet.CrossSection.class));
+               setters.put("FinSet:cant", new DoubleSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setCantAngle", double.class), Math.PI/180.0));
+               setters.put("FinSet:tabheight", new DoubleSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setTabHeight", double.class)));
+               setters.put("FinSet:tablength", new DoubleSetter(
+                               Reflection.findMethodStatic(FinSet.class, "setTabLength", double.class)));
+               setters.put("FinSet:tabposition", new FinTabPositionSetter());
+               
+               // TrapezoidFinSet
+               setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
+                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setRootChord", double.class)));
+               setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
+                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setTipChord", double.class)));
+               setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
+                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setSweep", double.class)));
+               setters.put("TrapezoidFinSet:height", new DoubleSetter(
+                               Reflection.findMethodStatic(TrapezoidFinSet.class, "setHeight", double.class)));
+
+               // EllipticalFinSet
+               setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
+                               Reflection.findMethodStatic(EllipticalFinSet.class, "setLength", double.class)));
+               setters.put("EllipticalFinSet:height", new DoubleSetter(
+                               Reflection.findMethodStatic(EllipticalFinSet.class, "setHeight", double.class)));
+               
+               // FreeformFinSet points handled as a special handler
+               
+               // LaunchLug
+               setters.put("LaunchLug:radius", new DoubleSetter(
+                               Reflection.findMethodStatic(LaunchLug.class, "setRadius", double.class)));
+               setters.put("LaunchLug:length", new DoubleSetter(
+                               Reflection.findMethodStatic(LaunchLug.class, "setLength", double.class)));
+               setters.put("LaunchLug:thickness", new DoubleSetter(
+                               Reflection.findMethodStatic(LaunchLug.class, "setThickness", double.class)));
+               setters.put("LaunchLug:radialdirection", new DoubleSetter(
+                               Reflection.findMethodStatic(LaunchLug.class, "setRadialDirection", double.class),
+                               Math.PI/180.0));
+               
+               // InternalComponent - nothing
+               
+               // StructuralComponent
+               setters.put("StructuralComponent:material", new MaterialSetter(
+                               Reflection.findMethodStatic(StructuralComponent.class, "setMaterial", Material.class),
+                               Material.Type.BULK));
+               
+               // RingComponent
+               setters.put("RingComponent:length", new DoubleSetter(
+                               Reflection.findMethodStatic(RingComponent.class, "setLength", double.class)));
+               setters.put("RingComponent:radialposition", new DoubleSetter(
+                               Reflection.findMethodStatic(RingComponent.class, "setRadialPosition", double.class)));
+               setters.put("RingComponent:radialdirection", new DoubleSetter(
+                               Reflection.findMethodStatic(RingComponent.class, "setRadialDirection", double.class),
+                               Math.PI / 180.0));
+               
+               // ThicknessRingComponent - radius on separate components due to differing automatics
+               setters.put("ThicknessRingComponent:thickness", new DoubleSetter(
+                               Reflection.findMethodStatic(ThicknessRingComponent.class, "setThickness", double.class)));
+
+               // EngineBlock
+               setters.put("EngineBlock:outerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(EngineBlock.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
+
+               // TubeCoupler
+               setters.put("TubeCoupler:outerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // InnerTube
+               setters.put("InnerTube:outerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(InnerTube.class, "setOuterRadius", double.class)));
+               setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
+               setters.put("InnerTube:clusterscale", new DoubleSetter(
+                               Reflection.findMethodStatic(InnerTube.class, "setClusterScale", double.class)));
+               setters.put("InnerTube:clusterrotation", new DoubleSetter(
+                               Reflection.findMethodStatic(InnerTube.class, "setClusterRotation", double.class),
+                               Math.PI / 180.0));
+               
+               // RadiusRingComponent
+               
+               // Bulkhead
+               setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(RadiusRingComponent.class, "setInnerRadius", double.class)));
+               setters.put("Bulkhead:outerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(Bulkhead.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // CenteringRing
+               setters.put("CenteringRing:innerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(CenteringRing.class, "setInnerRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
+               setters.put("CenteringRing:outerradius", new DoubleSetter(
+                               Reflection.findMethodStatic(CenteringRing.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               
+               // MassObject
+               setters.put("MassObject:packedlength", new DoubleSetter(
+                               Reflection.findMethodStatic(MassObject.class, "setLength", double.class)));
+               setters.put("MassObject:packedradius", new DoubleSetter(
+                               Reflection.findMethodStatic(MassObject.class, "setRadius", double.class)));
+               setters.put("MassObject:radialposition", new DoubleSetter(
+                               Reflection.findMethodStatic(MassObject.class, "setRadialPosition", double.class)));
+               setters.put("MassObject:radialdirection", new DoubleSetter(
+                               Reflection.findMethodStatic(MassObject.class, "setRadialDirection", double.class),
+                               Math.PI / 180.0));
+               
+               // MassComponent
+               setters.put("MassComponent:mass", new DoubleSetter(
+                               Reflection.findMethodStatic(MassComponent.class, "setComponentMass", double.class)));
+               
+               // ShockCord
+               setters.put("ShockCord:cordlength", new DoubleSetter(
+                               Reflection.findMethodStatic(ShockCord.class, "setCordLength", double.class)));
+               setters.put("ShockCord:material", new MaterialSetter(
+                               Reflection.findMethodStatic(ShockCord.class, "setMaterial", Material.class),
+                               Material.Type.LINE));
+               
+               // RecoveryDevice
+               setters.put("RecoveryDevice:cd", new DoubleSetter(
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setCD", double.class),
+                               "auto",
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
+               setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
+                               RecoveryDevice.DeployEvent.class));
+               setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployAltitude", double.class)));
+               setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setDeployDelay", double.class)));
+               setters.put("RecoveryDevice:material", new MaterialSetter(
+                               Reflection.findMethodStatic(RecoveryDevice.class, "setMaterial", Material.class),
+                               Material.Type.SURFACE));
+               
+               // Parachute
+               setters.put("Parachute:diameter", new DoubleSetter(
+                               Reflection.findMethodStatic(Parachute.class, "setDiameter", double.class)));
+               setters.put("Parachute:linecount", new IntSetter(
+                               Reflection.findMethodStatic(Parachute.class, "setLineCount", int.class)));
+               setters.put("Parachute:linelength", new DoubleSetter(
+                               Reflection.findMethodStatic(Parachute.class, "setLineLength", double.class)));
+               setters.put("Parachute:linematerial", new MaterialSetter(
+                               Reflection.findMethodStatic(Parachute.class, "setLineMaterial", Material.class),
+                               Material.Type.LINE));
+
+               // Streamer
+               setters.put("Streamer:striplength", new DoubleSetter(
+                               Reflection.findMethodStatic(Streamer.class, "setStripLength", double.class)));
+               setters.put("Streamer:stripwidth", new DoubleSetter(
+                               Reflection.findMethodStatic(Streamer.class, "setStripWidth", double.class)));
+               
+               // Rocket
+               // <motorconfiguration> handled by separate handler
+               setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
+                               Reflection.findMethodStatic(Rocket.class, "setReferenceType", ReferenceType.class),
+                               ReferenceType.class));
+               setters.put("Rocket:customreference", new DoubleSetter(
+                               Reflection.findMethodStatic(Rocket.class, "setCustomReferenceLength", double.class)));
+               setters.put("Rocket:designer", new StringSetter(
+                               Reflection.findMethodStatic(Rocket.class, "setDesigner", String.class)));
+               setters.put("Rocket:revision", new StringSetter(
+                               Reflection.findMethodStatic(Rocket.class, "setRevision", String.class)));
+       }
+       
+       
+       /**
+        * Search for a enum value that has the corresponding name as an XML value.  The current
+        * conversion from enum name to XML value is to lowercase the name and strip out all 
+        * underscore characters.  This method returns a match to these criteria, or <code>null</code>
+        * if no such enum exists.
+        * 
+        * @param <T>                   then enum type.
+        * @param name                  the XML value, null ok.
+        * @param enumClass             the class of the enum.
+        * @return                              the found enum value, or <code>null</code>.
+        */
+       public static <T extends Enum<T>> Enum<T> findEnum(String name, 
+                       Class<? extends Enum<T>> enumClass) {
+               
+               if (name == null)
+                       return null;
+               name = name.trim();
+               for (Enum<T> e: enumClass.getEnumConstants()) {
+                       if (e.name().toLowerCase().replace("_", "").equals(name)) {
+                               return e;
+                       }
+               }
+               return null;
+       }
+       
+       
+       /**
+        * Convert a string to a double including formatting specifications of the OpenRocket
+        * file format.  This accepts all formatting that is valid for 
+        * <code>Double.parseDouble(s)</code> and a few others as well ("Inf", "-Inf").
+        * 
+        * @param s             the string to parse.
+        * @return              the numerical value.
+        * @throws NumberFormatException        the the string cannot be parsed.
+        */
+       public static double stringToDouble(String s) throws NumberFormatException {
+               if (s == null)
+                       throw new NumberFormatException("null string");
+               if (s.equalsIgnoreCase("NaN"))
+                       return Double.NaN;
+               if (s.equalsIgnoreCase("Inf"))
+                       return Double.POSITIVE_INFINITY;
+               if (s.equalsIgnoreCase("-Inf"))
+                       return Double.NEGATIVE_INFINITY;
+               return Double.parseDouble(s);
+       }
+}
+
+
+
+
+
+/**
+ * The starting point of the handlers.  Accepts a single <openrocket> element and hands
+ * the contents to be read by a OpenRocketContentsHandler.
+ */
+class OpenRocketHandler extends ElementHandler {
+       private OpenRocketContentHandler handler = null;
+
+       /**
+        * Return the OpenRocketDocument read from the file, or <code>null</code> if a document
+        * has not been read yet.
+        * 
+        * @return      the document read, or null.
+        */
+       public OpenRocketDocument getDocument() {
+               return handler.getDocument();
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               // Check for unknown elements
+               if (!element.equals("openrocket")) {
+                       warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
+                       return null;
+               }
+
+               // Check for first call
+               if (handler != null) {
+                       warnings.add(Warning.fromString("Multiple document elements found, ignoring later "
+                                                       + "ones."));
+                       return null;
+               }
+
+               // Check version number
+               String version = null;
+               String creator = attributes.remove("creator");
+               String docVersion = attributes.remove("version");
+               for (String v : DocumentConfig.SUPPORTED_VERSIONS) {
+                       if (v.equals(docVersion)) {
+                               version = v;
+                               break;
+                       }
+               }
+               if (version == null) {
+                       String str = "Unsupported document version";
+                       if (docVersion != null)
+                               str += " " + docVersion;
+                       if (creator != null && !creator.trim().equals(""))
+                               str += " (written using '" + creator.trim() + "')";
+                       str += ", attempting to read file anyway.";
+                       warnings.add(str);
+               }
+
+               handler = new OpenRocketContentHandler();
+               return handler;
+       }
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+               attributes.remove("version");
+               attributes.remove("creator");
+               super.closeElement(element, attributes, content, warnings);
+       }
+       
+       
+}
+
+
+/**
+ * Handles the content of the <openrocket> tag.
+ */
+class OpenRocketContentHandler extends ElementHandler {
+       private final OpenRocketDocument doc;
+       private final Rocket rocket;
+
+       private boolean rocketDefined = false;
+       private boolean simulationsDefined = false;
+
+       public OpenRocketContentHandler() {
+               this.rocket = new Rocket();
+               this.doc = new OpenRocketDocument(rocket);
+       }
+
+
+       public OpenRocketDocument getDocument() {
+               if (!rocketDefined)
+                       return null;
+               return doc;
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               if (element.equals("rocket")) {
+                       if (rocketDefined) {
+                               warnings.add(Warning
+                                               .fromString("Multiple rocket designs within one document, "
+                                                               + "ignoring later ones."));
+                               return null;
+                       }
+                       rocketDefined = true;
+                       return new ComponentParameterHandler(rocket);
+               }
+               
+               if (element.equals("simulations")) {
+                       if (simulationsDefined) {
+                               warnings.add(Warning
+                                               .fromString("Multiple simulation definitions within one document, "
+                                                               + "ignoring later ones."));
+                               return null;
+                       }
+                       simulationsDefined = true;
+                       return new SimulationsHandler(doc);
+               }
+
+               warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
+
+               return null;
+       }
+}
+
+
+
+
+/**
+ * A handler that creates components from the corresponding elements.  The control of the
+ * contents is passed on to ComponentParameterHandler.
+ */
+class ComponentHandler extends ElementHandler {
+       private final RocketComponent parent;
+
+       public ComponentHandler(RocketComponent parent) {
+               this.parent = parent;
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               // Attempt to construct new component
+               Constructor<? extends RocketComponent> constructor = DocumentConfig.constructors
+                               .get(element);
+               if (constructor == null) {
+                       warnings.add(Warning.fromString("Unknown element " + element + ", ignoring."));
+                       return null;
+               }
+
+               RocketComponent c;
+               try {
+                       c = constructor.newInstance();
+               } catch (InstantiationException e) {
+                       throw new RuntimeException("Error constructing component.", e);
+               } catch (IllegalAccessException e) {
+                       throw new RuntimeException("Error constructing component.", e);
+               } catch (InvocationTargetException e) {
+                       throw new RuntimeException("Error constructing component.", e);
+               }
+
+               parent.addChild(c);
+
+               return new ComponentParameterHandler(c);
+       }
+}
+
+
+/**
+ * A handler that populates the parameters of a previously constructed rocket component.
+ * This uses the setters, or delegates the handling to another handler for specific
+ * elements.
+ */
+class ComponentParameterHandler extends ElementHandler {
+       private final RocketComponent component;
+
+       public ComponentParameterHandler(RocketComponent c) {
+               this.component = c;
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               // Check for specific elements that contain other elements
+               if (element.equals("subcomponents")) {
+                       return new ComponentHandler(component);
+               }
+               if (element.equals("motormount")) {
+                       if (!(component instanceof MotorMount)) {
+                               warnings.add(Warning.fromString("Illegal component defined as motor mount."));
+                               return null;
+                       }
+                       return new MotorMountHandler((MotorMount)component);
+               }
+               if (element.equals("finpoints")) {
+                       if (!(component instanceof FreeformFinSet)) {
+                               warnings.add(Warning.fromString("Illegal component defined for fin points."));
+                               return null;
+                       }
+                       return new FinSetPointHandler((FreeformFinSet)component);
+               }
+               if (element.equals("motorconfiguration")) {
+                       if (!(component instanceof Rocket)) {
+                               warnings.add(Warning.fromString("Illegal component defined for motor configuration."));
+                               return null;
+                       }
+                       return new MotorConfigurationHandler((Rocket)component);
+               }
+               
+               
+               return PlainTextHandler.INSTANCE;
+       }
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+
+               if (element.equals("subcomponents") || element.equals("motormount") ||
+                               element.equals("finpoints") || element.equals("motorconfiguration")) {
+                       return;
+               }
+               
+               // Search for the correct setter class
+
+               Class<?> c;
+               for (c = component.getClass(); c != null; c = c.getSuperclass()) {
+                       String setterKey = c.getSimpleName() + ":" + element;
+                       Setter s = DocumentConfig.setters.get(setterKey);
+                       if (s != null) {
+                               // Setter found
+                               System.out.println("Calling with key "+setterKey);
+                               s.set(component, content, attributes, warnings);
+                               break;
+                       }
+                       if (DocumentConfig.setters.containsKey(setterKey)) {
+                               // Key exists but is null -> invalid parameter
+                               c = null;
+                               break;
+                       }
+               }
+               if (c == null) {
+                       warnings.add(Warning.fromString("Unknown parameter type '" + element + "' for "
+                                       + component.getComponentName() + ", ignoring."));
+               }
+       }
+}
+
+
+/**
+ * A handler that reads the <point> specifications within the freeformfinset's
+ * <finpoints> elements.
+ */
+class FinSetPointHandler extends ElementHandler {
+       private final FreeformFinSet finset;
+       private final ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
+       
+       public FinSetPointHandler(FreeformFinSet finset) {
+               this.finset = finset;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               return PlainTextHandler.INSTANCE;
+       }
+       
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+
+               String strx = attributes.remove("x");
+               String stry = attributes.remove("y");
+               if (strx == null || stry == null) {
+                       warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
+                       return;
+               }
+               try {
+                       double x = Double.parseDouble(strx);
+                       double y = Double.parseDouble(stry);
+                       coordinates.add(new Coordinate(x,y));
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.fromString("Illegal fin points specification, ignoring."));
+                       return;
+               }
+               
+               super.closeElement(element, attributes, content, warnings);
+       }
+       
+       @Override
+       public void endHandler(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+               try {
+                       finset.setPoints(coordinates.toArray(new Coordinate[0]));
+               } catch (IllegalFinPointException e) {
+                       warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
+               }
+       }
+}
+
+
+class MotorMountHandler extends ElementHandler {
+       private final MotorMount mount;
+       private MotorHandler motorHandler;
+       
+       public MotorMountHandler(MotorMount mount) {
+               this.mount = mount;
+               mount.setMotorMount(true);
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               if (element.equals("motor")) {
+                       motorHandler = new MotorHandler();
+                       return motorHandler;
+               }
+               
+               if (element.equals("ignitionevent") ||
+                               element.equals("ignitiondelay") ||
+                               element.equals("overhang")) {
+                       return PlainTextHandler.INSTANCE;
+               }
+               
+               warnings.add(Warning.fromString("Unknown element '"+element+"' encountered, ignoring."));
+               return null;
+       }
+       
+       
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+
+               if (element.equals("motor")) {
+                       String id = attributes.get("configid");
+                       if (id == null || id.equals("")) {
+                               warnings.add(Warning.fromString("Illegal motor specification, ignoring."));
+                               return;
+                       }
+
+                       Motor motor = motorHandler.getMotor(warnings);
+                       mount.setMotor(id, motor);
+                       mount.setMotorDelay(id, motorHandler.getDelay(warnings));
+                       return;
+               }
+
+               if (element.equals("ignitionevent")) { 
+                       MotorMount.IgnitionEvent event = null;
+                       for (MotorMount.IgnitionEvent e : MotorMount.IgnitionEvent.values()) {
+                               if (e.name().toLowerCase().replaceAll("_", "").equals(content)) {
+                                       event = e;
+                                       break;
+                               }
+                       }
+                       if (event == null) {
+                               warnings.add(Warning.fromString("Unknown ignition event type '"+content+"', ignoring."));
+                               return;
+                       }
+                       mount.setIgnitionEvent(event);
+                       return;
+               }
+
+               if (element.equals("ignitiondelay")) {
+                       double d;
+                       try {
+                               d = Double.parseDouble(content);
+                       } catch (NumberFormatException nfe) {
+                               warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
+                               return;
+                       }
+                       mount.setIgnitionDelay(d);
+                       return;
+               }
+               
+               if (element.equals("overhang")) {
+                       double d;
+                       try {
+                               d = Double.parseDouble(content);
+                       } catch (NumberFormatException nfe) {
+                               warnings.add(Warning.fromString("Illegal overhang specified, ignoring."));
+                               return;
+                       }
+                       mount.setMotorOverhang(d);
+                       return;
+               }
+               
+               super.closeElement(element, attributes, content, warnings);
+       }
+}
+
+
+
+
+class MotorConfigurationHandler extends ElementHandler {
+       private final Rocket rocket;
+       private String name = null;
+       private boolean inNameElement = false;
+       
+       public MotorConfigurationHandler(Rocket rocket) {
+               this.rocket = rocket;
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               if (inNameElement || !element.equals("name")) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return null;
+               }
+               inNameElement = true;
+               
+               return PlainTextHandler.INSTANCE;
+       }
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+               name = content;
+       }
+
+       @Override
+       public void endHandler(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+
+               String configid = attributes.remove("configid");
+               if (configid == null || configid.equals("")) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+               
+               if (!rocket.addMotorConfigurationID(configid)) {
+                       warnings.add("Duplicate motor configuration ID used.");
+                       return;
+               }
+               
+               if (name != null && name.trim().length() > 0) {
+                       rocket.setMotorConfigurationName(configid, name);
+               }
+               
+               if ("true".equals(attributes.remove("default"))) {
+                       rocket.getDefaultConfiguration().setMotorConfigurationID(configid);
+               }
+               
+               super.closeElement(element, attributes, content, warnings);
+       }
+}
+
+
+class MotorHandler extends ElementHandler {
+       private Motor.Type type = null;
+       private String manufacturer = null;
+       private String designation = null;
+       private double diameter = Double.NaN;
+       private double length = Double.NaN;
+       private double delay = Double.NaN;
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               return PlainTextHandler.INSTANCE;
+       }
+       
+       
+       /**
+        * Return the motor to use, or null.
+        */
+       public Motor getMotor(WarningSet warnings) {
+               if (designation == null) {
+                       warnings.add(Warning.fromString("No motor specified, ignoring."));
+                       return null;
+               }
+               Motor[] motors = Databases.findMotors(type, manufacturer, designation, diameter, length);
+               if (motors.length == 0) {
+                       String str = "No motor with designation '"+designation+"'";
+                       if (manufacturer != null)
+                               str += " for manufacturer '" + manufacturer + "'";
+                       warnings.add(Warning.fromString(str + " found."));
+                       return null;
+               }
+               if (motors.length > 1) {
+                       String str = "Multiple motors with designation '"+designation+"'";
+                       if (manufacturer != null)
+                               str += " for manufacturer '" + manufacturer + "'";
+                       warnings.add(Warning.fromString(str + " found, one chosen arbitrarily."));
+               }
+               return motors[0];
+       }
+       
+       
+       /**
+        * Return the delay to use for the motor.
+        */
+       public double getDelay(WarningSet warnings) {
+               if (Double.isNaN(delay)) {
+                       warnings.add(Warning.fromString("Motor delay not specified, assuming no ejection charge."));
+                       return Motor.PLUGGED;
+               }
+               return delay;
+       }
+       
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+               
+               content = content.trim();
+               
+               if (element.equals("type")) {
+                       
+                       // Motor type
+                       type = null;
+                       for (Motor.Type t: Motor.Type.values()) {
+                               if (t.name().toLowerCase().equals(content)) {
+                                       type = t;
+                                       break;
+                               }
+                       }
+                       if (type == null) {
+                               warnings.add(Warning.fromString("Unknown motor type '"+content+"', ignoring."));
+                       }
+                       
+               } else if (element.equals("manufacturer")) {
+                       
+                       // Manufacturer
+                       manufacturer = content;
+
+               } else if (element.equals("designation")) {
+                       
+                       // Designation
+                       designation = content;
+
+               } else if (element.equals("diameter")) {
+               
+                       // Diameter
+                       diameter = Double.NaN;
+                       try {
+                               diameter = Double.parseDouble(content);
+                       } catch (NumberFormatException e) {
+                               // Ignore
+                       }
+                       if (Double.isNaN(diameter)) {
+                               warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
+                       }
+                       
+               } else if (element.equals("length")) {
+
+                       // Length
+                       length = Double.NaN;
+                       try {
+                               length = Double.parseDouble(content);
+                       } catch (NumberFormatException ignore) { }
+                       
+                       if (Double.isNaN(length)) {
+                               warnings.add(Warning.fromString("Illegal motor diameter specified, ignoring."));
+                       }
+                       
+               } else if (element.equals("delay")) {
+                       
+                       // Delay
+                       delay = Double.NaN;
+                       if (content.equals("none")) {
+                               delay = Motor.PLUGGED;
+                       } else {
+                               try {
+                                       delay = Double.parseDouble(content);
+                               } catch (NumberFormatException ignore) { }
+                               
+                               if (Double.isNaN(delay)) {
+                                       warnings.add(Warning.fromString("Illegal motor delay specified, ignoring."));
+                               }
+                               
+                       }
+
+               } else {
+                       super.closeElement(element, attributes, content, warnings);
+               }
+       }
+       
+}
+
+
+
+class SimulationsHandler extends ElementHandler {
+       private final OpenRocketDocument doc;
+       private SingleSimulationHandler handler;
+       
+       public SimulationsHandler(OpenRocketDocument doc) {
+               this.doc = doc;
+       }
+
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               if (!element.equals("simulation")) {
+                       warnings.add("Unknown element '"+element+"', ignoring.");
+                       return null;
+               }
+               
+               handler = new SingleSimulationHandler(doc);
+               return handler;
+       }
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+               attributes.remove("status");
+               super.closeElement(element, attributes, content, warnings);
+       }
+       
+       
+}
+
+class SingleSimulationHandler extends ElementHandler {
+
+       private final OpenRocketDocument doc;
+       
+       private String name;
+       
+       private SimulationConditionsHandler conditionHandler;
+       private FlightDataHandler dataHandler;
+       
+       private final List<String> listeners = new ArrayList<String>();
+       
+       public SingleSimulationHandler(OpenRocketDocument doc) {
+               this.doc = doc;
+       }
+       
+       
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               if (element.equals("name") || element.equals("simulator") || 
+                               element.equals("calculator") || element.equals("listener")) {
+                       return PlainTextHandler.INSTANCE;
+               } else if (element.equals("conditions")) {
+                       conditionHandler = new SimulationConditionsHandler(doc.getRocket());
+                       return conditionHandler;
+               } else if (element.equals("flightdata")) {
+                       dataHandler = new FlightDataHandler();
+                       return dataHandler;
+               } else {
+                       warnings.add("Unknown element '"+element+"', ignoring.");
+                       return null;
+               }
+       }
+       
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+               
+               if (element.equals("name")) {
+                       name = content;
+               } else if (element.equals("simulator")) {
+                       if (!content.trim().equals("RK4Simulator")) {
+                               warnings.add("Unknown simulator '" + content.trim() + "' specified, ignoring.");
+                       }
+               } else if (element.equals("calculator")) {
+                       if (!content.trim().equals("BarrowmanCalculator")) {
+                               warnings.add("Unknown calculator '" + content.trim() + "' specified, ignoring.");
+                       }
+               } else if (element.equals("listener") && content.trim().length() > 0) {
+                       listeners.add(content.trim());
+               }
+
+       }
+
+       @Override
+       public void endHandler(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+
+               String s = attributes.get("status");
+               Simulation.Status status = (Status) DocumentConfig.findEnum(s, Simulation.Status.class);
+               if (status == null) {
+                       warnings.add("Simulation status unknown, assuming outdated.");
+                       status = Simulation.Status.OUTDATED;
+               }
+               
+               SimulationConditions conditions;
+               if (conditionHandler != null) {
+                       conditions = conditionHandler.getConditions();
+               } else {
+                       warnings.add("Simulation conditions not defined, using defaults.");
+                       conditions = new SimulationConditions(doc.getRocket());
+               }
+               
+               if (name == null)
+                       name = "Simulation";
+               
+               FlightData data;
+               if (dataHandler == null)
+                       data = null;
+               else
+                       data = dataHandler.getFlightData();
+
+               Simulation simulation = new Simulation(doc.getRocket(), status, name,
+                               conditions, listeners, data);
+               
+               doc.addSimulation(simulation);
+       }
+}
+
+
+
+class SimulationConditionsHandler extends ElementHandler {
+       private SimulationConditions conditions;
+       private AtmosphereHandler atmosphereHandler;
+       
+       public SimulationConditionsHandler(Rocket rocket) {
+               conditions = new SimulationConditions(rocket);
+       }
+       
+       public SimulationConditions getConditions() {
+               return conditions;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               if (element.equals("atmosphere")) {
+                       atmosphereHandler = new AtmosphereHandler(attributes.get("model"));
+                       return atmosphereHandler;
+               }
+               return PlainTextHandler.INSTANCE;
+       }       
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+               
+               double d = Double.NaN;
+               try {
+                       d = Double.parseDouble(content);
+               } catch (NumberFormatException ignore) { }
+               
+
+               if (element.equals("configid")) {
+                       if (content.equals("")) {
+                               conditions.setMotorConfigurationID(null);
+                       } else {
+                               conditions.setMotorConfigurationID(content);
+                       }
+               } else if (element.equals("launchrodlength")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch rod length defined, ignoring.");
+                       } else {
+                               conditions.setLaunchRodLength(d);
+                       }
+               } else if (element.equals("launchrodangle")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch rod angle defined, ignoring.");
+                       } else {
+                               conditions.setLaunchRodAngle(d*Math.PI/180);
+                       }
+               } else if (element.equals("launchroddirection")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch rod direction defined, ignoring.");
+                       } else {
+                               conditions.setLaunchRodDirection(d*Math.PI/180);
+                       }
+               } else if (element.equals("windaverage")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal average windspeed defined, ignoring.");
+                       } else {
+                               conditions.setWindSpeedAverage(d);
+                       }
+               } else if (element.equals("windturbulence")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal wind turbulence intensity defined, ignoring.");
+                       } else {
+                               conditions.setWindTurbulenceIntensity(d);
+                       }
+               } else if (element.equals("launchaltitude")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch altitude defined, ignoring.");
+                       } else {
+                               conditions.setLaunchAltitude(d);
+                       }
+               } else if (element.equals("launchlatitude")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch latitude defined, ignoring.");
+                       } else {
+                               conditions.setLaunchLatitude(d);
+                       }
+               } else if (element.equals("atmosphere")) {
+                       atmosphereHandler.storeSettings(conditions, warnings);
+               } else if (element.equals("timestep")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal time step defined, ignoring.");
+                       } else {
+                               conditions.setTimeStep(d);
+                       }
+               }
+       }
+}
+
+
+class AtmosphereHandler extends ElementHandler {
+       private final String model;
+       private double temperature = Double.NaN;
+       private double pressure = Double.NaN;
+       
+       public AtmosphereHandler(String model) {
+               this.model = model;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               return PlainTextHandler.INSTANCE;
+       }
+
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) throws SAXException {
+
+               double d = Double.NaN;
+               try {
+                       d = Double.parseDouble(content);
+               } catch (NumberFormatException ignore) { }
+               
+               if (element.equals("basetemperature")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal base temperature specified, ignoring.");
+                       }
+                       temperature = d;
+               } else if (element.equals("basepressure")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal base pressure specified, ignoring.");
+                       }
+                       pressure = d;
+               } else {
+                       super.closeElement(element, attributes, content, warnings);
+               }
+       }
+
+       
+       public void storeSettings(SimulationConditions cond, WarningSet warnings) {
+               if (!Double.isNaN(pressure)) {
+                       cond.setLaunchPressure(pressure);
+               }
+               if (!Double.isNaN(temperature)) {
+                       cond.setLaunchTemperature(temperature);
+               }
+               
+               if ("isa".equals(model)) {
+                       cond.setISAAtmosphere(true);
+               } else if ("extendedisa".equals(model)){
+                       cond.setISAAtmosphere(false);
+               } else {
+                       cond.setISAAtmosphere(true);
+                       warnings.add("Unknown atmospheric model, using ISA.");
+               }
+       }
+       
+}
+
+
+class FlightDataHandler extends ElementHandler {
+       
+       private FlightDataBranchHandler dataHandler;
+       private WarningSet warningSet = new WarningSet();
+       private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
+
+       private FlightData data;
+       
+       public FlightData getFlightData() {
+               return data;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               if (element.equals("warning")) {
+                       return PlainTextHandler.INSTANCE;
+               }
+               if (element.equals("databranch")) {
+                       if (attributes.get("name") == null || attributes.get("types")==null) {
+                               warnings.add("Illegal flight data definition, ignoring.");
+                               return null;
+                       }
+                       dataHandler =  new FlightDataBranchHandler(attributes.get("name"),
+                                       attributes.get("types"));
+                       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("databranch")) {
+                       FlightDataBranch branch = dataHandler.getBranch();
+                       if (branch.getLength() > 0) {
+                               branches.add(branch);
+                       }
+               } else if (element.equals("warning")) {
+                       warningSet.add(Warning.fromString(content));
+               }
+       }
+
+
+       @Override
+       public void endHandler(String element, HashMap<String, String> attributes,
+                       String content, WarningSet warnings) {
+
+               if (branches.size() > 0) {
+                       data = new FlightData(branches.toArray(new FlightDataBranch[0]));
+               } else {
+                       double maxAltitude = Double.NaN;
+                       double maxVelocity = Double.NaN;
+                       double maxAcceleration = Double.NaN;
+                       double maxMach = Double.NaN;
+                       double timeToApogee = Double.NaN;
+                       double flightTime = Double.NaN;
+                       double groundHitVelocity = Double.NaN;
+                       
+                       try { 
+                               maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               maxVelocity = DocumentConfig.stringToDouble(attributes.get("maxvelocity"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               maxAcceleration = DocumentConfig.stringToDouble(attributes.get("maxacceleration"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               maxMach = DocumentConfig.stringToDouble(attributes.get("maxmach"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               timeToApogee = DocumentConfig.stringToDouble(attributes.get("timetoapogee"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               flightTime = DocumentConfig.stringToDouble(attributes.get("flighttime"));
+                       } catch (NumberFormatException ignore) { }
+                       try { 
+                               groundHitVelocity = 
+                                       DocumentConfig.stringToDouble(attributes.get("groundhitvelocity"));
+                       } catch (NumberFormatException ignore) { }
+                       
+                       data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
+                                       timeToApogee, flightTime, groundHitVelocity);
+               }
+               
+               data.getWarningSet().addAll(warningSet);
+       }
+       
+       
+}
+
+
+class FlightDataBranchHandler extends ElementHandler {
+       private final FlightDataBranch.Type[] types;
+       private final FlightDataBranch branch;
+       
+       public FlightDataBranchHandler(String name, String typeList) {
+               String[] split = typeList.split(",");
+               types = new FlightDataBranch.Type[split.length];
+               for (int i=0; i < split.length; i++) {
+                       types[i] = FlightDataBranch.getType(split[i], UnitGroup.UNITS_NONE);
+               }
+               
+               // TODO: LOW: May throw an IllegalArgumentException
+               branch = new FlightDataBranch(name, types);
+       }
+
+       public FlightDataBranch getBranch() {
+               branch.immute();
+               return branch;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               if (element.equals("datapoint"))
+                       return PlainTextHandler.INSTANCE;
+               if (element.equals("event"))
+                       return PlainTextHandler.INSTANCE;
+               
+               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("event")) {
+                       double time;
+                       FlightEvent.Type type;
+                       
+                       try {
+                               time = DocumentConfig.stringToDouble(attributes.get("time"));
+                       } catch (NumberFormatException e) {
+                               warnings.add("Illegal event specification, ignoring.");
+                               return;
+                       }
+                       
+                       type = (Type) DocumentConfig.findEnum(attributes.get("type"), FlightEvent.Type.class);
+                       if (type == null) {
+                               warnings.add("Illegal event specification, ignoring.");
+                               return;
+                       }
+
+                       branch.addEvent(time, new FlightEvent(type, time));
+                       return;
+               }
+               
+               if (!element.equals("datapoint")) {
+                       warnings.add("Unknown element '"+element+"' encountered, ignoring.");
+                       return;
+               }
+
+               // element == "datapoint"
+               
+               
+               // Check line format
+               String[] split = content.split(",");
+               if (split.length != types.length) {
+                       warnings.add("Data point did not contain correct amount of values, ignoring point.");
+                       return;
+               }
+               
+               // Parse the doubles
+               double[] values = new double[split.length];
+               for (int i=0; i < values.length; i++) {
+                       try {
+                               values[i] = DocumentConfig.stringToDouble(split[i]);
+                       } catch (NumberFormatException e) {
+                               warnings.add("Data point format error, ignoring point.");
+                               return;
+                       }
+               }
+               
+               // Add point to branch
+               branch.addPoint();
+               for (int i=0; i < types.length; i++) {
+                       branch.setValue(types[i], values[i]);
+               }
+       }
+}
+
+
+
+
+
+
+/////////////////    Setters implementation
+
+
+////  Interface
+interface Setter {
+       /**
+        * Set the specified value to the given component.
+        * 
+        * @param component             the component to which to set.
+        * @param value                 the value within the element.
+        * @param attributes    attributes for the element.
+        * @param warnings              the warning set to use.
+        */
+       public void set(RocketComponent component, String value,
+                       HashMap<String, String> attributes, WarningSet warnings);
+}
+
+
+////  StringSetter - sets the value to the contained String
+class StringSetter implements Setter {
+       private final Reflection.Method setMethod;
+
+       public StringSetter(Reflection.Method set) {
+               setMethod = set;
+       }
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               setMethod.invoke(c, s);
+       }
+}
+
+////  IntSetter - set an integer value
+class IntSetter implements Setter {
+       private final Reflection.Method setMethod;
+
+       public IntSetter(Reflection.Method set) {
+               setMethod = set;
+       }
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               try {
+                       int n = Integer.parseInt(s);
+                       setMethod.invoke(c, n);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+       }
+}
+
+
+//// BooleanSetter - set a boolean value
+class BooleanSetter implements Setter {
+       private final Reflection.Method setMethod;
+
+       public BooleanSetter(Reflection.Method set) {
+               setMethod = set;
+       }
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               s = s.trim();
+               if (s.equalsIgnoreCase("true")) {
+                       setMethod.invoke(c, true);
+               } else if (s.equalsIgnoreCase("false")) {
+                       setMethod.invoke(c, false);
+               } else {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+       }
+}
+
+
+
+////  DoubleSetter - sets a double value or (alternatively) if a specific string is encountered
+////  calls a setXXX(boolean) method.
+class DoubleSetter implements Setter {
+       private final Reflection.Method setMethod;
+       private final String specialString;
+       private final Reflection.Method specialMethod;
+       private final double multiplier;
+
+       /**
+        * Set only the double value.
+        * @param set   set method for the double value. 
+        */
+       public DoubleSetter(Reflection.Method set) {
+               this.setMethod = set;
+               this.specialString = null;
+               this.specialMethod = null;
+               this.multiplier = 1.0;
+       }
+
+       /**
+        * Multiply with the given multiplier and set the double value.
+        * @param set   set method for the double value.
+        * @param mul   multiplier.
+        */
+       public DoubleSetter(Reflection.Method set, double mul) {
+               this.setMethod = set;
+               this.specialString = null;
+               this.specialMethod = null;
+               this.multiplier = mul;
+       }
+
+       /**
+        * Set the double value, or if the value equals the special string, use the
+        * special setter and set it to true.
+        * 
+        * @param set                   double setter.
+        * @param special               special string
+        * @param specialMethod boolean setter.
+        */
+       public DoubleSetter(Reflection.Method set, String special,
+                       Reflection.Method specialMethod) {
+               this.setMethod = set;
+               this.specialString = special;
+               this.specialMethod = specialMethod;
+               this.multiplier = 1.0;
+       }
+
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               s = s.trim();
+
+               // Check for special case
+               if (specialMethod != null && s.equalsIgnoreCase(specialString)) {
+                       specialMethod.invoke(c, true);
+                       return;
+               }
+
+               // Normal case
+               try {
+                       double d = Double.parseDouble(s);
+                       setMethod.invoke(c, d * multiplier);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+       }
+}
+
+
+class OverrideSetter implements Setter {
+       private final Reflection.Method setMethod;
+       private final Reflection.Method enabledMethod;
+
+       public OverrideSetter(Reflection.Method set, Reflection.Method enabledMethod) {
+               this.setMethod = set;
+               this.enabledMethod = enabledMethod;
+       }
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               try {
+                       double d = Double.parseDouble(s);
+                       setMethod.invoke(c, d);
+                       enabledMethod.invoke(c, true);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+       }
+}
+
+////  EnumSetter  -  sets a generic enum type
+class EnumSetter<T extends Enum<T>> implements Setter {
+       private final Reflection.Method setter;
+       private final Class<T> enumClass;
+
+       public EnumSetter(Reflection.Method set, Class<T> enumClass) {
+               this.setter = set;
+               this.enumClass = enumClass;
+       }
+
+       @Override
+       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               Enum<?> setEnum = DocumentConfig.findEnum(name, enumClass);
+               if (setEnum == null) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+
+               setter.invoke(c, setEnum);
+       }
+}
+
+
+////  ColorSetter  -  sets a Color value
+class ColorSetter implements Setter {
+       private final Reflection.Method setMethod;
+
+       public ColorSetter(Reflection.Method set) {
+               setMethod = set;
+       }
+
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+
+               String red = attributes.get("red");
+               String green = attributes.get("green");
+               String blue = attributes.get("blue");
+
+               if (red == null || green == null || blue == null) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+               
+               int r, g, b;
+               try {
+                       r = Integer.parseInt(red);
+                       g = Integer.parseInt(green);
+                       b = Integer.parseInt(blue);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+               
+               if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+
+               Color color = new Color(r, g, b);
+               setMethod.invoke(c, color);
+               
+               if (!s.trim().equals("")) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+       }
+}
+
+
+
+class MaterialSetter implements Setter {
+       private final Reflection.Method setMethod;
+       private final Material.Type type;
+
+       public MaterialSetter(Reflection.Method set, Material.Type type) {
+               this.setMethod = set;
+               this.type = type;
+       }
+
+       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               Material mat;
+               
+               // Check name != ""
+               name = name.trim();
+               if (name.equals("")) {
+                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+                       return;
+               }
+               
+               // Parse density
+               double density;
+               String str;
+               str = attributes.remove("density");
+               if (str == null) {
+                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+                       return;
+               }
+               try {
+                       density = Double.parseDouble(str);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+                       return;
+               }
+
+               // Parse thickness
+//             double thickness = 0;
+//             str = attributes.remove("thickness");
+//             try {
+//                     if (str != null)
+//                             thickness = Double.parseDouble(str);
+//             } catch (NumberFormatException e){
+//                     warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+//                     return;
+//             }
+
+               // Check type if specified
+               str = attributes.remove("type");
+               if (str != null  &&  !type.name().toLowerCase().equals(str)) {
+                       warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
+                       return;
+               }
+
+               mat = Databases.findMaterial(type, name, density, false);
+               
+               setMethod.invoke(c, mat);
+       }
+}
+
+
+
+
+class PositionSetter implements Setter {
+
+       public void set(RocketComponent c, String value, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               RocketComponent.Position type = (Position) DocumentConfig.findEnum(attributes.get("type"), 
+                               RocketComponent.Position.class);
+               if (type == null) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+               
+               double pos;
+               try {
+                       pos = Double.parseDouble(value);
+               } catch (NumberFormatException e) {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+                       return;
+               }
+               
+               if (c instanceof FinSet) {
+                       ((FinSet)c).setRelativePosition(type);
+                       c.setPositionValue(pos);
+               } else if (c instanceof LaunchLug) {
+                       ((LaunchLug)c).setRelativePosition(type);
+                       c.setPositionValue(pos);
+               } else if (c instanceof InternalComponent) {
+                       ((InternalComponent)c).setRelativePosition(type);
+                       c.setPositionValue(pos);
+               } else {
+                       warnings.add(Warning.FILE_INVALID_PARAMETER);
+               }
+               
+       }
+}
+
+
+class FinTabPositionSetter extends DoubleSetter {
+
+       public FinTabPositionSetter() {
+               super(Reflection.findMethodStatic(FinSet.class, "setTabShift", double.class));
+       }
+
+       @Override
+       public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               if (!(c instanceof FinSet)) {
+                       throw new IllegalStateException("FinTabPositionSetter called for component " + c);
+               }
+
+               String relative = attributes.get("relativeto");
+               FinSet.TabRelativePosition position = 
+                       (TabRelativePosition) DocumentConfig.findEnum(relative,
+                                       FinSet.TabRelativePosition.class);
+               
+               if (position != null) {
+                       
+                       ((FinSet)c).setTabRelativePosition(position);
+                       
+               } else {
+                       if (relative == null) {
+                               warnings.add("Required attribute 'relativeto' not found for fin tab position.");
+                       } else {
+                               warnings.add("Illegal attribute value '" + relative + "' encountered.");
+                       }
+               }
+               
+               super.set(c, s, attributes, warnings);
+       }
+
+       
+}
+
+
+class ClusterConfigurationSetter implements Setter {
+
+       public void set(RocketComponent component, String value, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               if (!(component instanceof Clusterable)) {
+                       warnings.add("Illegal component defined as cluster.");
+                       return;
+               }
+               
+               ClusterConfiguration config = null;
+               for (ClusterConfiguration c: ClusterConfiguration.CONFIGURATIONS) {
+                       if (c.getXMLName().equals(value)) {
+                               config = c;
+                               break;
+                       }
+               }
+               
+               if (config == null) {
+                       warnings.add("Illegal cluster configuration specified.");
+                       return;
+               }
+               
+               ((Clusterable)component).setClusterConfiguration(config);
+       }
+}
+
+
diff --git a/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
new file mode 100644 (file)
index 0000000..6a13069
--- /dev/null
@@ -0,0 +1,532 @@
+package net.sf.openrocket.file.openrocket;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.GZIPOutputStream;
+
+import net.sf.openrocket.aerodynamics.Warning;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.TubeCoupler;
+import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.simulation.SimulationConditions;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Pair;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.TextUtil;
+
+public class OpenRocketSaver extends RocketSaver {
+       
+       /**
+        * Divisor used in converting an integer version to the point-represented version.
+        * The integer version divided by this value is the major version and the remainder is
+        * the minor version.  For example 101 corresponds to file version "1.1".
+        */
+       public static final int FILE_VERSION_DIVISOR = 100;
+
+       
+       private static final String OPENROCKET_CHARSET = "UTF-8";
+       
+       private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket.savers";
+       private static final String METHOD_SUFFIX = "Saver";
+       
+       
+       // Estimated storage used by different portions
+       // These have been hand-estimated from saved files
+       private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
+       private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
+       private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
+       private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
+       private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
+       private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
+       
+       
+       private int indent;
+       private Writer dest;
+       
+       @Override
+       public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
+       throws IOException {
+               
+               if (options.isCompressionEnabled()) {
+                       output = new GZIPOutputStream(output);
+               }
+               
+               dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); 
+               
+               final int fileVersion = calculateNecessaryFileVersion(document, options);
+               final String fileVersionString = 
+                       (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR); 
+               
+               
+               this.indent = 0;
+               
+               System.out.println("Writing...");
+               
+               writeln("<?xml version='1.0' encoding='utf-8'?>");
+               writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
+                               + Prefs.getVersion() + "\">");
+               indent++;
+               
+               // Recursively save the rocket structure
+               saveComponent(document.getRocket());
+               
+               writeln("");
+               
+               // Save all simulations
+               writeln("<simulations>");
+               indent++;
+               boolean first = true;
+               for (Simulation s: document.getSimulations()) {
+                       if (!first)
+                               writeln("");
+                       first = false;
+                       saveSimulation(s, options.getSimulationTimeSkip());
+               }
+               indent--;
+               writeln("</simulations>");
+               
+               indent--;
+               writeln("</openrocket>");
+               
+               dest.flush();
+               if (options.isCompressionEnabled()) {
+                       ((GZIPOutputStream)output).finish();
+               }
+       }
+       
+       
+       
+       @Override
+       public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
+               
+               long size = 0;
+               
+               // Size per component
+               int componentCount = 0;
+               Rocket rocket = doc.getRocket();
+               Iterator<RocketComponent> iterator = rocket.deepIterator(true);
+               while (iterator.hasNext()) {
+                       iterator.next();
+                       componentCount++;
+               }
+               
+               if (options.isCompressionEnabled())
+                       size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
+               else
+                       size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
+               
+               
+               // Size per simulation
+               if (options.isCompressionEnabled())
+                       size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
+               else
+                       size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
+               
+               
+               // Size per flight data point
+               int pointCount = 0;
+               double timeSkip = options.getSimulationTimeSkip();
+               if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
+                       for (Simulation s: doc.getSimulations()) {
+                               FlightData data = s.getSimulatedData();
+                               if (data != null) {
+                                       for (int i=0; i < data.getBranchCount(); i++) {
+                                               pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
+                                       }
+                               }
+                       }
+               }
+               
+               if (options.isCompressionEnabled())
+                       size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
+               else
+                       size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
+               
+               return size;
+       }
+       
+
+       /**
+        * Determine which file version is required in order to store all the features of the
+        * current design.  By default the oldest version that supports all the necessary features
+        * will be used.
+        * 
+        * @param document      the document to output.
+        * @param opts          the storage options.
+        * @return                      the integer file version to use.
+        */
+       private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
+               /*
+                * File version 1.1 is required for:
+                *  - fin tabs
+                *  - components attached to tube coupler
+                * 
+                * Otherwise use version 1.0.
+                */
+               
+               // Check for fin tabs (version 1.1)
+               Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+               while (iterator.hasNext()) {
+                       RocketComponent c = iterator.next();
+                       
+                       // Check for fin tabs
+                       if (c instanceof FinSet) {
+                               FinSet fin = (FinSet)c;
+                               if (!MathUtil.equals(fin.getTabHeight(),0) &&
+                                               !MathUtil.equals(fin.getTabLength(), 0)) {
+                                       return FILE_VERSION_DIVISOR + 1;
+                               }
+                       }
+                       
+                       // Check for components attached to tube coupler
+                       if (c instanceof TubeCoupler) {
+                               if (c.getChildCount() > 0) {
+                                       return FILE_VERSION_DIVISOR + 1;
+                               }
+                       }
+               }
+               
+               // Default (version 1.0)
+               return FILE_VERSION_DIVISOR + 0;
+       }
+       
+       
+       
+       @SuppressWarnings("unchecked")
+       private void saveComponent(RocketComponent component) throws IOException {
+               
+               Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
+                               "getElements", RocketComponent.class);
+               if (m==null) {
+                       throw new RuntimeException("Unable to find saving class for component "+
+                                       component.getComponentName());
+               }
+
+               // Get the strings to save
+               List<String> list = (List<String>) m.invokeStatic(component);
+               int length = list.size();
+               
+               if (length == 0)  // Nothing to do
+                       return;
+
+               if (length < 2) {
+                       throw new RuntimeException("BUG, component data length less than two lines.");
+               }
+               
+               // Open element
+               writeln(list.get(0));
+               indent++;
+               
+               // Write parameters
+               for (int i=1; i<length-1; i++) {
+                       writeln(list.get(i));
+               }
+               
+               // Recursively write subcomponents
+               if (component.getChildCount() > 0) {
+                       writeln("");
+                       writeln("<subcomponents>");
+                       indent++;
+                       boolean emptyline = false;
+                       for (RocketComponent subcomponent: component) {
+                               if (emptyline)
+                                       writeln("");
+                               emptyline = true;
+                               saveComponent(subcomponent);
+                       }
+                       indent--;
+                       writeln("</subcomponents>");
+               }
+               
+               // Close element
+               indent--;
+               writeln(list.get(length-1));
+       }
+
+       
+       
+       private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
+               SimulationConditions cond = simulation.getConditions();
+               
+               writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
+               indent++;
+               
+               writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
+               // TODO: MEDIUM: Other simulators/calculators
+               writeln("<simulator>RK4Simulator</simulator>");
+               writeln("<calculator>BarrowmanCalculator</calculator>");
+               writeln("<conditions>");
+               indent++;
+               
+               writeElement("configid", cond.getMotorConfigurationID());
+               writeElement("launchrodlength", cond.getLaunchRodLength());
+               writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI); 
+               writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
+               writeElement("windaverage", cond.getWindSpeedAverage());
+               writeElement("windturbulence", cond.getWindTurbulenceIntensity());
+               writeElement("launchaltitude", cond.getLaunchAltitude());
+               writeElement("launchlatitude", cond.getLaunchLatitude());
+               
+               if (cond.isISAAtmosphere()) {
+                       writeln("<atmosphere model=\"isa\"/>");
+               } else {
+                       writeln("<atmosphere model=\"extendedisa\">");
+                       indent++;
+                       writeElement("basetemperature", cond.getLaunchTemperature());
+                       writeElement("basepressure", cond.getLaunchPressure());
+                       indent--;
+                       writeln("</atmosphere>");
+               }
+
+               writeElement("timestep", cond.getTimeStep());
+               
+               indent--;
+               writeln("</conditions>");
+               
+               
+               for (String s: simulation.getSimulationListeners()) {
+                       writeElement("listener", escapeXML(s));
+               }
+               
+               
+               // Write basic simulation data
+               
+               FlightData data = simulation.getSimulatedData();
+               if (data != null) {
+                       String str = "<flightdata";
+                       if (!Double.isNaN(data.getMaxAltitude()))
+                               str += " maxaltitude=\"" + TextUtil.doubleToString(data.getMaxAltitude()) + "\"";
+                       if (!Double.isNaN(data.getMaxVelocity()))
+                               str += " maxvelocity=\"" + TextUtil.doubleToString(data.getMaxVelocity()) + "\"";
+                       if (!Double.isNaN(data.getMaxAcceleration()))
+                               str += " maxacceleration=\"" + TextUtil.doubleToString(data.getMaxAcceleration()) + "\"";
+                       if (!Double.isNaN(data.getMaxMachNumber()))
+                               str += " maxmach=\"" + TextUtil.doubleToString(data.getMaxMachNumber()) + "\"";
+                       if (!Double.isNaN(data.getTimeToApogee()))
+                               str += " timetoapogee=\"" + TextUtil.doubleToString(data.getTimeToApogee()) + "\"";
+                       if (!Double.isNaN(data.getFlightTime()))
+                               str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\"";
+                       if (!Double.isNaN(data.getGroundHitVelocity()))
+                               str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\"";
+                       str += ">";
+                       writeln(str);
+                       indent++;
+                       
+                       for (Warning w: data.getWarningSet()) {
+                               writeElement("warning", escapeXML(w.toString()));
+                       }
+                       
+                       // Check whether to store data
+                       if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
+                               timeSkip = 0;
+                       
+                       if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
+                               for (int i=0; i<data.getBranchCount(); i++) {
+                                       FlightDataBranch branch = data.getBranch(i);
+                                       saveFlightDataBranch(branch, timeSkip);
+                               }
+                       }
+                       
+                       indent--;
+                       writeln("</flightdata>");
+               }
+               
+               indent--;
+               writeln("</simulation>");
+               
+       }
+       
+       
+       
+       private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) 
+       throws IOException {
+               double previousTime = -100000;
+               
+               if (branch == null)
+                       return;
+               
+               // Retrieve the types from the branch
+               FlightDataBranch.Type[] types = branch.getTypes();
+               
+               if (types.length == 0)
+                       return;
+               
+               // Retrieve the data from the branch
+               List<List<Double>> data = new ArrayList<List<Double>>(types.length);
+               for (int i=0; i<types.length; i++) {
+                       data.add(branch.get(types[i]));
+               }
+               List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
+               if (timeData == null) {
+                       // TODO: MEDIUM: External data may not have time data
+                       throw new IllegalArgumentException("Data did not contain time data");
+               }
+               
+               // Build the <databranch> tag
+               StringBuilder sb = new StringBuilder();
+               sb.append("<databranch name=\"");
+               sb.append(escapeXML(branch.getBranchName()));
+               sb.append("\" types=\"");
+               for (int i=0; i<types.length; i++) {
+                       if (i > 0)
+                               sb.append(",");
+                       sb.append(escapeXML(types[i].getName()));
+               }
+               sb.append("\">");
+               writeln(sb.toString());
+               indent++;
+               
+               // Write events
+               for (Pair<Double,FlightEvent> p: branch.getEvents()) {
+                       writeln("<event time=\"" + TextUtil.doubleToString(p.getU())
+                                       + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
+               }
+               
+               // Write the data
+               int length = branch.getLength();
+               if (length > 0) {
+                       writeDataPointString(data, 0, sb);
+                       previousTime = timeData.get(0);
+               }
+               
+               for (int i=1; i < length-1; i++) {
+                       if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
+                                       Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
+                               writeDataPointString(data, i, sb);
+                               previousTime = timeData.get(i);
+                       }
+               }
+               
+               if (length > 1) {
+                       writeDataPointString(data, length-1, sb);
+               }
+               
+               indent--;
+               writeln("</databranch>");
+       }
+       
+       
+       
+       /* TODO: LOW: This is largely duplicated from above! */
+       private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
+               int count = 0;
+
+               double previousTime = -100000;
+               
+               if (branch == null)
+                       return 0;
+               
+               // Retrieve the types from the branch
+               FlightDataBranch.Type[] types = branch.getTypes();
+               
+               if (types.length == 0)
+                       return 0;
+               
+               List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
+               if (timeData == null) {
+                       // TODO: MEDIUM: External data may not have time data
+                       throw new IllegalArgumentException("Data did not contain time data");
+               }
+               
+               // Write the data
+               int length = branch.getLength();
+               if (length > 0) {
+                       count++;
+                       previousTime = timeData.get(0);
+               }
+               
+               for (int i=1; i < length-1; i++) {
+                       if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
+                                       Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
+                               count++;
+                               previousTime = timeData.get(i);
+                       }
+               }
+               
+               if (length > 1) {
+                       count++;
+               }
+
+               return count;
+       }
+       
+       
+       
+       private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
+       throws IOException {
+               sb.setLength(0);
+               sb.append("<datapoint>");
+               for (int j=0; j < data.size(); j++) {
+                       if (j > 0)
+                               sb.append(",");
+                       sb.append(TextUtil.doubleToString(data.get(j).get(index)));
+               }
+               sb.append("</datapoint>");
+               writeln(sb.toString());
+       }
+       
+       
+       
+       private void writeElement(String element, Object content) throws IOException {
+               if (content == null)
+                       content = "";
+               writeln("<"+element+">"+content+"</"+element+">");
+       }
+
+
+       
+       private void writeln(String str) throws IOException {
+               if (str.length() == 0) {
+                       dest.write("\n");
+                       return;
+               }
+               String s="";
+               for (int i=0; i<indent; i++)
+                       s=s+"  ";
+               s = s+str+"\n";
+               dest.write(s);
+       }
+       
+       
+       public static void main(String[] arg) {
+               double d = -0.000000123456789123;
+               
+               
+               for (int i=0; i< 20; i++) {
+                       String str = TextUtil.doubleToString(d);
+                       System.out.println(str + "   ->   " + Double.parseDouble(str));
+                       d *= 10;
+               }
+               
+               
+               System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
+               
+       }
+
+       
+       /**
+        * Return the XML equivalent of an enum name.
+        * 
+        * @param e             the enum to save.
+        * @return              the corresponding XML name.
+        */
+       public static String enumToXMLName(Enum<?> e) {
+               return e.name().toLowerCase().replace("_", "");
+       }
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/ParachuteSaver.java b/src/net/sf/openrocket/file/openrocket/ParachuteSaver.java
deleted file mode 100644 (file)
index 64e44ff..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.Parachute;
-
-
-public class ParachuteSaver extends RecoveryDeviceSaver {
-
-       private static final ParachuteSaver instance = new ParachuteSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<parachute>");
-               instance.addParams(c, list);
-               list.add("</parachute>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               Parachute para = (Parachute) c;
-
-               elements.add("<diameter>" + para.getDiameter() + "</diameter>");
-               elements.add("<linecount>" + para.getLineCount() + "</linecount>");
-               elements.add("<linelength>" + para.getLineLength() + "</linelength>");
-               elements.add(materialParam("linematerial", para.getLineMaterial()));
-       }
-
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RadiusRingComponentSaver.java
deleted file mode 100644 (file)
index 02cf015..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.Bulkhead;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-
-
-public class RadiusRingComponentSaver extends RingComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               RadiusRingComponent comp = (RadiusRingComponent)c;
-               if (comp.isOuterRadiusAutomatic())
-                       elements.add("<outerradius>auto</outerradius>");
-               else
-                       elements.add("<outerradius>" + comp.getOuterRadius() + "</outerradius>");
-               if (!(comp instanceof Bulkhead)) {
-                       if (comp.isInnerRadiusAutomatic())
-                               elements.add("<innerradius>auto</innerradius>");
-                       else
-                               elements.add("<innerradius>" + comp.getInnerRadius() + "</innerradius>");
-               }
-       }
-               
-}
diff --git a/src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java b/src/net/sf/openrocket/file/openrocket/RecoveryDeviceSaver.java
deleted file mode 100644 (file)
index a57747e..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.RecoveryDevice;
-
-
-public class RecoveryDeviceSaver extends MassObjectSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               RecoveryDevice dev = (RecoveryDevice) c;
-
-               if (dev.isCDAutomatic())
-                       elements.add("<cd>auto</cd>");
-               else
-                       elements.add("<cd>" + dev.getCD() + "</cd>");
-
-               elements.add("<deployevent>" + dev.getDeployEvent().name().toLowerCase() + "</deployevent>");
-               elements.add("<deployaltitude>" + dev.getDeployAltitude() + "</deployaltitude>");
-               elements.add("<deploydelay>" + dev.getDeployDelay() + "</deploydelay>");
-               elements.add(materialParam(dev.getMaterial()));
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/RingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RingComponentSaver.java
deleted file mode 100644 (file)
index 53d8177..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.RingComponent;
-
-
-public class RingComponentSaver extends StructuralComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               RingComponent ring = (RingComponent) c;
-
-               elements.add("<length>" + ring.getLength() + "</length>");
-               elements.add("<radialposition>" + ring.getRadialPosition() + "</radialposition>");
-               elements.add("<radialdirection>" + (ring.getRadialDirection() * 180.0 / Math.PI)
-                               + "</radialdirection>");
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java
deleted file mode 100644 (file)
index a9da9b2..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import net.sf.openrocket.file.RocketSaver;
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.ComponentAssembly;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.util.LineStyle;
-
-
-public class RocketComponentSaver {
-
-       protected RocketComponentSaver() {
-               // Prevent instantiation from outside the package
-       }
-
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               elements.add("<name>" + RocketSaver.escapeXML(c.getName()) + "</name>");
-
-
-               // Save color and line style if significant
-               if (!(c instanceof Rocket || c instanceof ComponentAssembly)) {
-                       Color color = c.getColor();
-                       if (color != null) {
-                               elements.add("<color red=\"" + color.getRed() + "\" green=\"" + color.getGreen() 
-                                               + "\" blue=\"" + color.getBlue() + "\"/>");
-                       }
-
-                       LineStyle style = c.getLineStyle();
-                       if (style != null) {
-                               // Type names currently equivalent to the enum names except for case.
-                               elements.add("<linestyle>" + style.name().toLowerCase() + "</linestyle>");
-                       }
-               }
-
-
-               // Save position unless "AFTER"
-               if (c.getRelativePosition() != RocketComponent.Position.AFTER) {
-                       // The type names are currently equivalent to the enum names except for case.
-                       String type = c.getRelativePosition().name().toLowerCase();
-                       elements.add("<position type=\"" + type + "\">" + c.getPositionValue() + "</position>");
-               }
-
-
-               // Overrides
-               boolean overridden = false;
-               if (c.isMassOverridden()) {
-                       elements.add("<overridemass>" + c.getOverrideMass() + "</overridemass>");
-                       overridden = true;
-               }
-               if (c.isCGOverridden()) {
-                       elements.add("<overridecg>" + c.getOverrideCGX() + "</overridecg>");
-                       overridden = true;
-               }
-               if (overridden) {
-                       elements.add("<overridesubcomponents>" + c.getOverrideSubcomponents()
-                                       + "</overridesubcomponents>");
-               }
-
-
-               // Comment
-               if (c.getComment().length() > 0) {
-                       elements.add("<comment>" + RocketSaver.escapeXML(c.getComment()) + "</comment>");
-               }
-
-       }
-
-
-
-
-       protected final String materialParam(Material mat) {
-               return materialParam("material", mat);
-       }
-
-
-       protected final String materialParam(String tag, Material mat) {
-               String str = "<" + tag;
-
-               switch (mat.getType()) {
-               case LINE:
-                       str += " type=\"line\"";
-                       break;
-               case SURFACE:
-                       str += " type=\"surface\"";
-                       break;
-               case BULK:
-                       str += " type=\"bulk\"";
-                       break;
-               default:
-                       throw new RuntimeException("Unknown material type: " + mat.getType());
-               }
-
-               return str + " density=\"" + mat.getDensity() + "\">" + RocketSaver.escapeXML(mat.getName()) + "</"+tag+">";
-       }
-
-
-       protected final List<String> motorMountParams(MotorMount mount) {
-               if (!mount.isMotorMount())
-                       return Collections.emptyList();
-
-               String[] motorConfigIDs = ((RocketComponent) mount).getRocket().getMotorConfigurationIDs();
-               List<String> elements = new ArrayList<String>();
-
-               elements.add("<motormount>");
-
-               for (String id : motorConfigIDs) {
-                       Motor motor = mount.getMotor(id);
-
-                       // Nothing is stored if no motor loaded
-                       if (motor == null)
-                               continue;
-
-                       elements.add("  <motor configid=\"" + id + "\">");
-                       if (motor.getMotorType() != Motor.Type.UNKNOWN) {
-                               elements.add("    <type>" + motor.getMotorType().name().toLowerCase() + "</type>");
-                       }
-                       elements.add("    <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer().getSimpleName()) + "</manufacturer>");
-                       elements.add("    <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>");
-                       elements.add("    <diameter>" + motor.getDiameter() + "</diameter>");
-                       elements.add("    <length>" + motor.getLength() + "</length>");
-                       
-                       // Motor delay
-                       if (mount.getMotorDelay(id) == Motor.PLUGGED) {
-                               elements.add("    <delay>none</delay>");
-                       } else {
-                               elements.add("    <delay>" + mount.getMotorDelay(id) + "</delay>");
-                       }
-
-                       elements.add("  </motor>");
-               }
-
-               elements.add("  <ignitionevent>"
-                               + mount.getIgnitionEvent().name().toLowerCase().replace("_", "")
-                               + "</ignitionevent>");
-
-               elements.add("  <ignitiondelay>" + mount.getIgnitionDelay() + "</ignitiondelay>");
-               elements.add("  <overhang>" + mount.getMotorOverhang() + "</overhang>");
-               
-               elements.add("</motormount>");
-
-               return elements;
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/RocketSaver.java b/src/net/sf/openrocket/file/openrocket/RocketSaver.java
deleted file mode 100644 (file)
index e27fa76..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.ReferenceType;
-import net.sf.openrocket.rocketcomponent.Rocket;
-
-
-public class RocketSaver extends RocketComponentSaver {
-
-       private static final RocketSaver instance = new RocketSaver();
-
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-
-               list.add("<rocket>");
-               instance.addParams(c, list);
-               list.add("</rocket>");
-
-               return list;
-       }
-
-
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               Rocket rocket = (Rocket) c;
-               
-               if (rocket.getDesigner().length() > 0) {
-                       elements.add("<designer>" 
-                                       + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getDesigner())
-                                       + "</designer>");
-               }
-               if (rocket.getRevision().length() > 0) {
-                       elements.add("<revision>" 
-                                       + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getRevision()) 
-                                       + "</revision>");
-               }
-
-
-               // Motor configurations
-               String defId = rocket.getDefaultConfiguration().getMotorConfigurationID();
-               for (String id : rocket.getMotorConfigurationIDs()) {
-                       if (id == null)
-                               continue;
-
-                       String str = "<motorconfiguration configid=\"" + id + "\"";
-                       if (id.equals(defId))
-                               str += " default=\"true\"";
-                       
-                       if (rocket.getMotorConfigurationName(id) == "") {
-                               str += "/>";
-                       } else {
-                               str += "><name>" + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getMotorConfigurationName(id))
-                                       + "</name></motorconfiguration>";
-                       }
-                       elements.add(str);
-               }
-               
-               // Reference diameter
-               elements.add("<referencetype>" + rocket.getReferenceType().name().toLowerCase()
-                               + "</referencetype>");
-               if (rocket.getReferenceType() == ReferenceType.CUSTOM) {
-                       elements.add("<customreference>" + rocket.getCustomReferenceLength()
-                                       + "</customreference>");
-               }
-
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/ShockCordSaver.java b/src/net/sf/openrocket/file/openrocket/ShockCordSaver.java
deleted file mode 100644 (file)
index 0f8746e..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.ShockCord;
-
-
-public class ShockCordSaver extends MassObjectSaver {
-
-       private static final ShockCordSaver instance = new ShockCordSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<shockcord>");
-               instance.addParams(c, list);
-               list.add("</shockcord>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               ShockCord mass = (ShockCord) c;
-
-               elements.add("<cordlength>" + mass.getCordLength() + "</cordlength>");
-               elements.add(materialParam(mass.getMaterial()));
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/StageSaver.java b/src/net/sf/openrocket/file/openrocket/StageSaver.java
deleted file mode 100644 (file)
index 2c83772..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-
-public class StageSaver extends ComponentAssemblySaver {
-
-       private static final StageSaver instance = new StageSaver();
-       
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-               
-               list.add("<stage>");
-               instance.addParams(c,list);
-               list.add("</stage>");
-               
-               return list;
-       }
-       
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/StreamerSaver.java b/src/net/sf/openrocket/file/openrocket/StreamerSaver.java
deleted file mode 100644 (file)
index d3e936b..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.Streamer;
-
-
-public class StreamerSaver extends RecoveryDeviceSaver {
-
-       private static final StreamerSaver instance = new StreamerSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<streamer>");
-               instance.addParams(c, list);
-               list.add("</streamer>");
-
-               return list;
-       }
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               Streamer st = (Streamer) c;
-
-               elements.add("<striplength>" + st.getStripLength() + "</striplength>");
-               elements.add("<stripwidth>" + st.getStripWidth() + "</stripwidth>");
-       }
-
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java b/src/net/sf/openrocket/file/openrocket/StructuralComponentSaver.java
deleted file mode 100644 (file)
index 4d7f605..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.StructuralComponent;
-
-
-public class StructuralComponentSaver extends InternalComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               StructuralComponent comp = (StructuralComponent)c;
-               elements.add(materialParam(comp.getMaterial()));
-       }
-               
-}
diff --git a/src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java b/src/net/sf/openrocket/file/openrocket/SymmetricComponentSaver.java
deleted file mode 100644 (file)
index 741c513..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-public class SymmetricComponentSaver extends BodyComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-
-               net.sf.openrocket.rocketcomponent.SymmetricComponent comp = (net.sf.openrocket.rocketcomponent.SymmetricComponent)c;
-               if (comp.isFilled())
-                       elements.add("<thickness>filled</thickness>");
-               else
-                       elements.add("<thickness>"+comp.getThickness()+"</thickness>");
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/ThicknessRingComponentSaver.java
deleted file mode 100644 (file)
index d52df55..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
-
-
-public class ThicknessRingComponentSaver extends RingComponentSaver {
-
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               ThicknessRingComponent comp = (ThicknessRingComponent)c;
-               if (comp.isOuterRadiusAutomatic())
-                       elements.add("<outerradius>auto</outerradius>");
-               else
-                       elements.add("<outerradius>" + comp.getOuterRadius() + "</outerradius>");
-               elements.add("<thickness>" + comp.getThickness() + "</thickness>");
-       }
-               
-}
diff --git a/src/net/sf/openrocket/file/openrocket/TransitionSaver.java b/src/net/sf/openrocket/file/openrocket/TransitionSaver.java
deleted file mode 100644 (file)
index 461087f..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.Transition;
-
-
-public class TransitionSaver extends SymmetricComponentSaver {
-
-       private static final TransitionSaver instance = new TransitionSaver();
-
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-
-               list.add("<transition>");
-               instance.addParams(c, list);
-               list.add("</transition>");
-
-               return list;
-       }
-
-
-       /*
-        * Note:  This method must be capable of handling nose cones as well.
-        */
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               net.sf.openrocket.rocketcomponent.Transition trans = (net.sf.openrocket.rocketcomponent.Transition) c;
-               boolean nosecone = (trans instanceof NoseCone);
-
-
-               Transition.Shape shape = trans.getType();
-               elements.add("<shape>" + shape.name().toLowerCase() + "</shape>");
-               if (shape.isClippable()) {
-                       elements.add("<shapeclipped>" + trans.isClipped() + "</shapeclipped>");
-               }
-               if (shape.usesParameter()) {
-                       elements.add("<shapeparameter>" + trans.getShapeParameter() + "</shapeparameter>");
-               }
-
-
-               if (!nosecone) {
-                       if (trans.isForeRadiusAutomatic())
-                               elements.add("<foreradius>auto</foreradius>");
-                       else
-                               elements.add("<foreradius>" + trans.getForeRadius() + "</foreradius>");
-               }
-
-               if (trans.isAftRadiusAutomatic())
-                       elements.add("<aftradius>auto</aftradius>");
-               else
-                       elements.add("<aftradius>" + trans.getAftRadius() + "</aftradius>");
-
-
-               if (!nosecone) {
-                       elements.add("<foreshoulderradius>" + trans.getForeShoulderRadius()
-                                       + "</foreshoulderradius>");
-                       elements.add("<foreshoulderlength>" + trans.getForeShoulderLength()
-                                       + "</foreshoulderlength>");
-                       elements.add("<foreshoulderthickness>" + trans.getForeShoulderThickness()
-                                       + "</foreshoulderthickness>");
-                       elements.add("<foreshouldercapped>" + trans.isForeShoulderCapped()
-                                       + "</foreshouldercapped>");
-               }
-
-               elements.add("<aftshoulderradius>" + trans.getAftShoulderRadius()
-                               + "</aftshoulderradius>");
-               elements.add("<aftshoulderlength>" + trans.getAftShoulderLength()
-                               + "</aftshoulderlength>");
-               elements.add("<aftshoulderthickness>" + trans.getAftShoulderThickness()
-                               + "</aftshoulderthickness>");
-               elements.add("<aftshouldercapped>" + trans.isAftShoulderCapped()
-                               + "</aftshouldercapped>");
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/TrapezoidFinSetSaver.java
deleted file mode 100644 (file)
index fc244cc..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TrapezoidFinSetSaver extends FinSetSaver {
-
-       private static final TrapezoidFinSetSaver instance = new TrapezoidFinSetSaver();
-       
-       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               ArrayList<String> list = new ArrayList<String>();
-               
-               list.add("<trapezoidfinset>");
-               instance.addParams(c,list);
-               list.add("</trapezoidfinset>");
-               
-               return list;
-       }
-       
-       @Override
-       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
-               super.addParams(c, elements);
-               
-               net.sf.openrocket.rocketcomponent.TrapezoidFinSet fins = (net.sf.openrocket.rocketcomponent.TrapezoidFinSet)c;
-               elements.add("<rootchord>"+fins.getRootChord()+"</rootchord>");
-               elements.add("<tipchord>"+fins.getTipChord()+"</tipchord>");
-               elements.add("<sweeplength>"+fins.getSweep()+"</sweeplength>");
-               elements.add("<height>"+fins.getHeight()+"</height>");
-       }
-       
-}
diff --git a/src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java b/src/net/sf/openrocket/file/openrocket/TubeCouplerSaver.java
deleted file mode 100644 (file)
index 09a79c7..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TubeCouplerSaver extends ThicknessRingComponentSaver {
-
-       private static final TubeCouplerSaver instance = new TubeCouplerSaver();
-
-       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
-               List<String> list = new ArrayList<String>();
-
-               list.add("<tubecoupler>");
-               instance.addParams(c, list);
-               list.add("</tubecoupler>");
-
-               return list;
-       }
-
-}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/BodyComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/BodyComponentSaver.java
new file mode 100644 (file)
index 0000000..1a65676
--- /dev/null
@@ -0,0 +1,15 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+public class BodyComponentSaver extends ExternalComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               // Body components have a natural length, store it now
+               elements.add("<length>"+((net.sf.openrocket.rocketcomponent.BodyComponent)c).getLength()+"</length>");
+       }
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java b/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java
new file mode 100644 (file)
index 0000000..5caf289
--- /dev/null
@@ -0,0 +1,36 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BodyTubeSaver extends SymmetricComponentSaver {
+
+       private static final BodyTubeSaver instance = new BodyTubeSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<bodytube>");
+               instance.addParams(c, list);
+               list.add("</bodytube>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube) c;
+
+               if (tube.isRadiusAutomatic())
+                       elements.add("<radius>auto</radius>");
+               else
+                       elements.add("<radius>" + tube.getRadius() + "</radius>");
+
+               if (tube.isMotorMount()) {
+                       elements.addAll(motorMountParams(tube));
+               }
+       }
+
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/BulkheadSaver.java b/src/net/sf/openrocket/file/openrocket/savers/BulkheadSaver.java
new file mode 100644 (file)
index 0000000..9c0ef2d
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BulkheadSaver extends RadiusRingComponentSaver {
+
+       private static final BulkheadSaver instance = new BulkheadSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<bulkhead>");
+               instance.addParams(c, list);
+               list.add("</bulkhead>");
+
+               return list;
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/CenteringRingSaver.java b/src/net/sf/openrocket/file/openrocket/savers/CenteringRingSaver.java
new file mode 100644 (file)
index 0000000..bb428a3
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CenteringRingSaver extends RadiusRingComponentSaver {
+
+       private static final CenteringRingSaver instance = new CenteringRingSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<centeringring>");
+               instance.addParams(c, list);
+               list.add("</centeringring>");
+
+               return list;
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/ComponentAssemblySaver.java b/src/net/sf/openrocket/file/openrocket/savers/ComponentAssemblySaver.java
new file mode 100644 (file)
index 0000000..9e9d609
--- /dev/null
@@ -0,0 +1,7 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+public class ComponentAssemblySaver extends RocketComponentSaver {
+
+       // No-op
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/EllipticalFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/savers/EllipticalFinSetSaver.java
new file mode 100644 (file)
index 0000000..31b9dfb
--- /dev/null
@@ -0,0 +1,29 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EllipticalFinSetSaver extends FinSetSaver {
+
+       private static final EllipticalFinSetSaver instance = new EllipticalFinSetSaver();
+       
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+               
+               list.add("<ellipticalfinset>");
+               instance.addParams(c,list);
+               list.add("</ellipticalfinset>");
+               
+               return list;
+       }
+       
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               net.sf.openrocket.rocketcomponent.EllipticalFinSet fins = (net.sf.openrocket.rocketcomponent.EllipticalFinSet)c;
+               elements.add("<rootchord>"+fins.getLength()+"</rootchord>");
+               elements.add("<height>"+fins.getHeight()+"</height>");
+       }
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/EngineBlockSaver.java b/src/net/sf/openrocket/file/openrocket/savers/EngineBlockSaver.java
new file mode 100644 (file)
index 0000000..921ddde
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EngineBlockSaver extends ThicknessRingComponentSaver {
+
+       private static final EngineBlockSaver instance = new EngineBlockSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<engineblock>");
+               instance.addParams(c, list);
+               list.add("</engineblock>");
+
+               return list;
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/ExternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/ExternalComponentSaver.java
new file mode 100644 (file)
index 0000000..2f1b2a4
--- /dev/null
@@ -0,0 +1,23 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.ExternalComponent;
+
+
+public class ExternalComponentSaver extends RocketComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               ExternalComponent ext = (ExternalComponent)c;
+               
+               // Finish enum names are currently the same except for case
+               elements.add("<finish>" + ext.getFinish().name().toLowerCase() + "</finish>");
+               
+               // Material
+               elements.add(materialParam(ext.getMaterial()));
+       }
+               
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/FinSetSaver.java b/src/net/sf/openrocket/file/openrocket/savers/FinSetSaver.java
new file mode 100644 (file)
index 0000000..8756d89
--- /dev/null
@@ -0,0 +1,34 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.util.MathUtil;
+
+public class FinSetSaver extends ExternalComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               net.sf.openrocket.rocketcomponent.FinSet fins = (net.sf.openrocket.rocketcomponent.FinSet) c;
+               elements.add("<fincount>" + fins.getFinCount() + "</fincount>");
+               elements.add("<rotation>" + (fins.getBaseRotation() * 180.0 / Math.PI) + "</rotation>");
+               elements.add("<thickness>" + fins.getThickness() + "</thickness>");
+               elements.add("<crosssection>" + fins.getCrossSection().name().toLowerCase()
+                               + "</crosssection>");
+               elements.add("<cant>" + (fins.getCantAngle() * 180.0 / Math.PI) + "</cant>");
+               
+               // Save fin tabs only if they exist (compatibility with file version < 1.1)
+               if (!MathUtil.equals(fins.getTabHeight(),0) &&
+                               !MathUtil.equals(fins.getTabLength(), 0)) {
+                       
+                       elements.add("<tabheight>" + fins.getTabHeight() + "</tabheight>");
+                       elements.add("<tablength>" + fins.getTabLength() + "</tablength>");
+                       elements.add("<tabposition relativeto=\"" +
+                                       fins.getTabRelativePosition().name().toLowerCase() + "\">" +
+                                       fins.getTabShift() + "</tabposition>");
+               
+               }
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/FreeformFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/savers/FreeformFinSetSaver.java
new file mode 100644 (file)
index 0000000..c310e4d
--- /dev/null
@@ -0,0 +1,36 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.util.Coordinate;
+
+
+public class FreeformFinSetSaver extends FinSetSaver {
+
+       private static final FreeformFinSetSaver instance = new FreeformFinSetSaver();
+       
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+               
+               list.add("<freeformfinset>");
+               instance.addParams(c,list);
+               list.add("</freeformfinset>");
+               
+               return list;
+       }
+       
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               FreeformFinSet fins = (FreeformFinSet)c;
+               elements.add("<finpoints>");
+               for (Coordinate p: fins.getFinPoints()) {
+                       elements.add("  <point x=\"" + p.x + "\" y=\"" + p.y + "\"/>");
+               }
+               elements.add("</finpoints>");
+       }
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/InnerTubeSaver.java b/src/net/sf/openrocket/file/openrocket/savers/InnerTubeSaver.java
new file mode 100644 (file)
index 0000000..7eb0a53
--- /dev/null
@@ -0,0 +1,43 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.InnerTube;
+
+
+public class InnerTubeSaver extends ThicknessRingComponentSaver {
+
+       private static final InnerTubeSaver instance = new InnerTubeSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<innertube>");
+               instance.addParams(c, list);
+               list.add("</innertube>");
+
+               return list;
+       }
+
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               InnerTube tube = (InnerTube) c;
+
+               elements.add("<clusterconfiguration>" + tube.getClusterConfiguration().getXMLName()
+                               + "</clusterconfiguration>");
+               elements.add("<clusterscale>" + tube.getClusterScale() + "</clusterscale>");
+               elements.add("<clusterrotation>" + (tube.getClusterRotation() * 180.0 / Math.PI)
+                               + "</clusterrotation>");
+
+               if (tube.isMotorMount()) {
+                       elements.addAll(motorMountParams(tube));
+               }
+
+
+       }
+
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/InternalComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/InternalComponentSaver.java
new file mode 100644 (file)
index 0000000..0a2dbe7
--- /dev/null
@@ -0,0 +1,14 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+public class InternalComponentSaver extends RocketComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               // Nothing to save
+       }
+               
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java b/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java
new file mode 100644 (file)
index 0000000..3008bcc
--- /dev/null
@@ -0,0 +1,35 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+
+
+public class LaunchLugSaver extends ExternalComponentSaver {
+
+       private static final LaunchLugSaver instance = new LaunchLugSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<launchlug>");
+               instance.addParams(c, list);
+               list.add("</launchlug>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               LaunchLug lug = (LaunchLug) c;
+
+               elements.add("<radius>" + lug.getRadius() + "</radius>");
+               elements.add("<length>" + lug.getLength() + "</length>");
+               elements.add("<thickness>" + lug.getThickness() + "</thickness>");
+               elements.add("<radialdirection>" + (lug.getRadialDirection()*180.0/Math.PI) + "</radialdirection>");
+       }
+
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/MassComponentSaver.java
new file mode 100644 (file)
index 0000000..093303c
--- /dev/null
@@ -0,0 +1,32 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.MassComponent;
+
+
+public class MassComponentSaver extends MassObjectSaver {
+
+       private static final MassComponentSaver instance = new MassComponentSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<masscomponent>");
+               instance.addParams(c, list);
+               list.add("</masscomponent>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               MassComponent mass = (MassComponent) c;
+
+               elements.add("<mass>" + mass.getMass() + "</mass>");
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java b/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java
new file mode 100644 (file)
index 0000000..298cb26
--- /dev/null
@@ -0,0 +1,23 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.MassObject;
+
+
+public class MassObjectSaver extends InternalComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               MassObject mass = (MassObject) c;
+
+               elements.add("<packedlength>" + mass.getLength() + "</packedlength>");
+               elements.add("<packedradius>" + mass.getRadius() + "</packedradius>");
+               elements.add("<radialposition>" + mass.getRadialPosition() + "</radialposition>");
+               elements.add("<radialdirection>" + (mass.getRadialDirection() * 180.0 / Math.PI)
+                               + "</radialdirection>");
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java b/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java
new file mode 100644 (file)
index 0000000..9733254
--- /dev/null
@@ -0,0 +1,27 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NoseConeSaver extends TransitionSaver {
+
+       private static final NoseConeSaver instance = new NoseConeSaver();
+       
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+               
+               list.add("<nosecone>");
+               instance.addParams(c,list);
+               list.add("</nosecone>");
+               
+               return list;
+       }
+       
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               // Transition handles nose cone saving as well
+       }
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/ParachuteSaver.java b/src/net/sf/openrocket/file/openrocket/savers/ParachuteSaver.java
new file mode 100644 (file)
index 0000000..7d906e6
--- /dev/null
@@ -0,0 +1,35 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.Parachute;
+
+
+public class ParachuteSaver extends RecoveryDeviceSaver {
+
+       private static final ParachuteSaver instance = new ParachuteSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<parachute>");
+               instance.addParams(c, list);
+               list.add("</parachute>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               Parachute para = (Parachute) c;
+
+               elements.add("<diameter>" + para.getDiameter() + "</diameter>");
+               elements.add("<linecount>" + para.getLineCount() + "</linecount>");
+               elements.add("<linelength>" + para.getLineLength() + "</linelength>");
+               elements.add(materialParam("linematerial", para.getLineMaterial()));
+       }
+
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/RadiusRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/RadiusRingComponentSaver.java
new file mode 100644 (file)
index 0000000..c1b9020
--- /dev/null
@@ -0,0 +1,28 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.Bulkhead;
+import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
+
+
+public class RadiusRingComponentSaver extends RingComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               RadiusRingComponent comp = (RadiusRingComponent)c;
+               if (comp.isOuterRadiusAutomatic())
+                       elements.add("<outerradius>auto</outerradius>");
+               else
+                       elements.add("<outerradius>" + comp.getOuterRadius() + "</outerradius>");
+               if (!(comp instanceof Bulkhead)) {
+                       if (comp.isInnerRadiusAutomatic())
+                               elements.add("<innerradius>auto</innerradius>");
+                       else
+                               elements.add("<innerradius>" + comp.getInnerRadius() + "</innerradius>");
+               }
+       }
+               
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/RecoveryDeviceSaver.java b/src/net/sf/openrocket/file/openrocket/savers/RecoveryDeviceSaver.java
new file mode 100644 (file)
index 0000000..22dcaa9
--- /dev/null
@@ -0,0 +1,27 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+
+
+public class RecoveryDeviceSaver extends MassObjectSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               RecoveryDevice dev = (RecoveryDevice) c;
+
+               if (dev.isCDAutomatic())
+                       elements.add("<cd>auto</cd>");
+               else
+                       elements.add("<cd>" + dev.getCD() + "</cd>");
+
+               elements.add("<deployevent>" + dev.getDeployEvent().name().toLowerCase() + "</deployevent>");
+               elements.add("<deployaltitude>" + dev.getDeployAltitude() + "</deployaltitude>");
+               elements.add("<deploydelay>" + dev.getDeployDelay() + "</deploydelay>");
+               elements.add(materialParam(dev.getMaterial()));
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/RingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/RingComponentSaver.java
new file mode 100644 (file)
index 0000000..c1999fb
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.RingComponent;
+
+
+public class RingComponentSaver extends StructuralComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               RingComponent ring = (RingComponent) c;
+
+               elements.add("<length>" + ring.getLength() + "</length>");
+               elements.add("<radialposition>" + ring.getRadialPosition() + "</radialposition>");
+               elements.add("<radialdirection>" + (ring.getRadialDirection() * 180.0 / Math.PI)
+                               + "</radialdirection>");
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java
new file mode 100644 (file)
index 0000000..64e5589
--- /dev/null
@@ -0,0 +1,151 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.ComponentAssembly;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.LineStyle;
+
+
+public class RocketComponentSaver {
+
+       protected RocketComponentSaver() {
+               // Prevent instantiation from outside the package
+       }
+
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               elements.add("<name>" + RocketSaver.escapeXML(c.getName()) + "</name>");
+
+
+               // Save color and line style if significant
+               if (!(c instanceof Rocket || c instanceof ComponentAssembly)) {
+                       Color color = c.getColor();
+                       if (color != null) {
+                               elements.add("<color red=\"" + color.getRed() + "\" green=\"" + color.getGreen() 
+                                               + "\" blue=\"" + color.getBlue() + "\"/>");
+                       }
+
+                       LineStyle style = c.getLineStyle();
+                       if (style != null) {
+                               // Type names currently equivalent to the enum names except for case.
+                               elements.add("<linestyle>" + style.name().toLowerCase() + "</linestyle>");
+                       }
+               }
+
+
+               // Save position unless "AFTER"
+               if (c.getRelativePosition() != RocketComponent.Position.AFTER) {
+                       // The type names are currently equivalent to the enum names except for case.
+                       String type = c.getRelativePosition().name().toLowerCase();
+                       elements.add("<position type=\"" + type + "\">" + c.getPositionValue() + "</position>");
+               }
+
+
+               // Overrides
+               boolean overridden = false;
+               if (c.isMassOverridden()) {
+                       elements.add("<overridemass>" + c.getOverrideMass() + "</overridemass>");
+                       overridden = true;
+               }
+               if (c.isCGOverridden()) {
+                       elements.add("<overridecg>" + c.getOverrideCGX() + "</overridecg>");
+                       overridden = true;
+               }
+               if (overridden) {
+                       elements.add("<overridesubcomponents>" + c.getOverrideSubcomponents()
+                                       + "</overridesubcomponents>");
+               }
+
+
+               // Comment
+               if (c.getComment().length() > 0) {
+                       elements.add("<comment>" + RocketSaver.escapeXML(c.getComment()) + "</comment>");
+               }
+
+       }
+
+
+
+
+       protected final String materialParam(Material mat) {
+               return materialParam("material", mat);
+       }
+
+
+       protected final String materialParam(String tag, Material mat) {
+               String str = "<" + tag;
+
+               switch (mat.getType()) {
+               case LINE:
+                       str += " type=\"line\"";
+                       break;
+               case SURFACE:
+                       str += " type=\"surface\"";
+                       break;
+               case BULK:
+                       str += " type=\"bulk\"";
+                       break;
+               default:
+                       throw new RuntimeException("Unknown material type: " + mat.getType());
+               }
+
+               return str + " density=\"" + mat.getDensity() + "\">" + RocketSaver.escapeXML(mat.getName()) + "</"+tag+">";
+       }
+
+
+       protected final List<String> motorMountParams(MotorMount mount) {
+               if (!mount.isMotorMount())
+                       return Collections.emptyList();
+
+               String[] motorConfigIDs = ((RocketComponent) mount).getRocket().getMotorConfigurationIDs();
+               List<String> elements = new ArrayList<String>();
+
+               elements.add("<motormount>");
+
+               for (String id : motorConfigIDs) {
+                       Motor motor = mount.getMotor(id);
+
+                       // Nothing is stored if no motor loaded
+                       if (motor == null)
+                               continue;
+
+                       elements.add("  <motor configid=\"" + id + "\">");
+                       if (motor.getMotorType() != Motor.Type.UNKNOWN) {
+                               elements.add("    <type>" + motor.getMotorType().name().toLowerCase() + "</type>");
+                       }
+                       elements.add("    <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer().getSimpleName()) + "</manufacturer>");
+                       elements.add("    <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>");
+                       elements.add("    <diameter>" + motor.getDiameter() + "</diameter>");
+                       elements.add("    <length>" + motor.getLength() + "</length>");
+                       
+                       // Motor delay
+                       if (mount.getMotorDelay(id) == Motor.PLUGGED) {
+                               elements.add("    <delay>none</delay>");
+                       } else {
+                               elements.add("    <delay>" + mount.getMotorDelay(id) + "</delay>");
+                       }
+
+                       elements.add("  </motor>");
+               }
+
+               elements.add("  <ignitionevent>"
+                               + mount.getIgnitionEvent().name().toLowerCase().replace("_", "")
+                               + "</ignitionevent>");
+
+               elements.add("  <ignitiondelay>" + mount.getIgnitionDelay() + "</ignitiondelay>");
+               elements.add("  <overhang>" + mount.getMotorOverhang() + "</overhang>");
+               
+               elements.add("</motormount>");
+
+               return elements;
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java b/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java
new file mode 100644 (file)
index 0000000..e8b7a34
--- /dev/null
@@ -0,0 +1,73 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.ReferenceType;
+import net.sf.openrocket.rocketcomponent.Rocket;
+
+
+public class RocketSaver extends RocketComponentSaver {
+
+       private static final RocketSaver instance = new RocketSaver();
+
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+
+               list.add("<rocket>");
+               instance.addParams(c, list);
+               list.add("</rocket>");
+
+               return list;
+       }
+
+
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               Rocket rocket = (Rocket) c;
+               
+               if (rocket.getDesigner().length() > 0) {
+                       elements.add("<designer>" 
+                                       + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getDesigner())
+                                       + "</designer>");
+               }
+               if (rocket.getRevision().length() > 0) {
+                       elements.add("<revision>" 
+                                       + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getRevision()) 
+                                       + "</revision>");
+               }
+
+
+               // Motor configurations
+               String defId = rocket.getDefaultConfiguration().getMotorConfigurationID();
+               for (String id : rocket.getMotorConfigurationIDs()) {
+                       if (id == null)
+                               continue;
+
+                       String str = "<motorconfiguration configid=\"" + id + "\"";
+                       if (id.equals(defId))
+                               str += " default=\"true\"";
+                       
+                       if (rocket.getMotorConfigurationName(id) == "") {
+                               str += "/>";
+                       } else {
+                               str += "><name>" + net.sf.openrocket.file.RocketSaver.escapeXML(rocket.getMotorConfigurationName(id))
+                                       + "</name></motorconfiguration>";
+                       }
+                       elements.add(str);
+               }
+               
+               // Reference diameter
+               elements.add("<referencetype>" + rocket.getReferenceType().name().toLowerCase()
+                               + "</referencetype>");
+               if (rocket.getReferenceType() == ReferenceType.CUSTOM) {
+                       elements.add("<customreference>" + rocket.getCustomReferenceLength()
+                                       + "</customreference>");
+               }
+
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/ShockCordSaver.java b/src/net/sf/openrocket/file/openrocket/savers/ShockCordSaver.java
new file mode 100644 (file)
index 0000000..8b8ae01
--- /dev/null
@@ -0,0 +1,33 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.ShockCord;
+
+
+public class ShockCordSaver extends MassObjectSaver {
+
+       private static final ShockCordSaver instance = new ShockCordSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<shockcord>");
+               instance.addParams(c, list);
+               list.add("</shockcord>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               ShockCord mass = (ShockCord) c;
+
+               elements.add("<cordlength>" + mass.getCordLength() + "</cordlength>");
+               elements.add(materialParam(mass.getMaterial()));
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/StageSaver.java b/src/net/sf/openrocket/file/openrocket/savers/StageSaver.java
new file mode 100644 (file)
index 0000000..0fd0f6f
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+
+public class StageSaver extends ComponentAssemblySaver {
+
+       private static final StageSaver instance = new StageSaver();
+       
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+               
+               list.add("<stage>");
+               instance.addParams(c,list);
+               list.add("</stage>");
+               
+               return list;
+       }
+       
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/StreamerSaver.java b/src/net/sf/openrocket/file/openrocket/savers/StreamerSaver.java
new file mode 100644 (file)
index 0000000..5b92852
--- /dev/null
@@ -0,0 +1,33 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.Streamer;
+
+
+public class StreamerSaver extends RecoveryDeviceSaver {
+
+       private static final StreamerSaver instance = new StreamerSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<streamer>");
+               instance.addParams(c, list);
+               list.add("</streamer>");
+
+               return list;
+       }
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               Streamer st = (Streamer) c;
+
+               elements.add("<striplength>" + st.getStripLength() + "</striplength>");
+               elements.add("<stripwidth>" + st.getStripWidth() + "</stripwidth>");
+       }
+
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/StructuralComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/StructuralComponentSaver.java
new file mode 100644 (file)
index 0000000..5cf5ce7
--- /dev/null
@@ -0,0 +1,18 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.StructuralComponent;
+
+
+public class StructuralComponentSaver extends InternalComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               StructuralComponent comp = (StructuralComponent)c;
+               elements.add(materialParam(comp.getMaterial()));
+       }
+               
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/SymmetricComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/SymmetricComponentSaver.java
new file mode 100644 (file)
index 0000000..bab5ecc
--- /dev/null
@@ -0,0 +1,18 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+public class SymmetricComponentSaver extends BodyComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+
+               net.sf.openrocket.rocketcomponent.SymmetricComponent comp = (net.sf.openrocket.rocketcomponent.SymmetricComponent)c;
+               if (comp.isFilled())
+                       elements.add("<thickness>filled</thickness>");
+               else
+                       elements.add("<thickness>"+comp.getThickness()+"</thickness>");
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/ThicknessRingComponentSaver.java b/src/net/sf/openrocket/file/openrocket/savers/ThicknessRingComponentSaver.java
new file mode 100644 (file)
index 0000000..d5a3c1b
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
+
+
+public class ThicknessRingComponentSaver extends RingComponentSaver {
+
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               ThicknessRingComponent comp = (ThicknessRingComponent)c;
+               if (comp.isOuterRadiusAutomatic())
+                       elements.add("<outerradius>auto</outerradius>");
+               else
+                       elements.add("<outerradius>" + comp.getOuterRadius() + "</outerradius>");
+               elements.add("<thickness>" + comp.getThickness() + "</thickness>");
+       }
+               
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java b/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java
new file mode 100644 (file)
index 0000000..d7bb1ed
--- /dev/null
@@ -0,0 +1,79 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.Transition;
+
+
+public class TransitionSaver extends SymmetricComponentSaver {
+
+       private static final TransitionSaver instance = new TransitionSaver();
+
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+
+               list.add("<transition>");
+               instance.addParams(c, list);
+               list.add("</transition>");
+
+               return list;
+       }
+
+
+       /*
+        * Note:  This method must be capable of handling nose cones as well.
+        */
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               net.sf.openrocket.rocketcomponent.Transition trans = (net.sf.openrocket.rocketcomponent.Transition) c;
+               boolean nosecone = (trans instanceof NoseCone);
+
+
+               Transition.Shape shape = trans.getType();
+               elements.add("<shape>" + shape.name().toLowerCase() + "</shape>");
+               if (shape.isClippable()) {
+                       elements.add("<shapeclipped>" + trans.isClipped() + "</shapeclipped>");
+               }
+               if (shape.usesParameter()) {
+                       elements.add("<shapeparameter>" + trans.getShapeParameter() + "</shapeparameter>");
+               }
+
+
+               if (!nosecone) {
+                       if (trans.isForeRadiusAutomatic())
+                               elements.add("<foreradius>auto</foreradius>");
+                       else
+                               elements.add("<foreradius>" + trans.getForeRadius() + "</foreradius>");
+               }
+
+               if (trans.isAftRadiusAutomatic())
+                       elements.add("<aftradius>auto</aftradius>");
+               else
+                       elements.add("<aftradius>" + trans.getAftRadius() + "</aftradius>");
+
+
+               if (!nosecone) {
+                       elements.add("<foreshoulderradius>" + trans.getForeShoulderRadius()
+                                       + "</foreshoulderradius>");
+                       elements.add("<foreshoulderlength>" + trans.getForeShoulderLength()
+                                       + "</foreshoulderlength>");
+                       elements.add("<foreshoulderthickness>" + trans.getForeShoulderThickness()
+                                       + "</foreshoulderthickness>");
+                       elements.add("<foreshouldercapped>" + trans.isForeShoulderCapped()
+                                       + "</foreshouldercapped>");
+               }
+
+               elements.add("<aftshoulderradius>" + trans.getAftShoulderRadius()
+                               + "</aftshoulderradius>");
+               elements.add("<aftshoulderlength>" + trans.getAftShoulderLength()
+                               + "</aftshoulderlength>");
+               elements.add("<aftshoulderthickness>" + trans.getAftShoulderThickness()
+                               + "</aftshoulderthickness>");
+               elements.add("<aftshouldercapped>" + trans.isAftShoulderCapped()
+                               + "</aftshouldercapped>");
+       }
+
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/TrapezoidFinSetSaver.java b/src/net/sf/openrocket/file/openrocket/savers/TrapezoidFinSetSaver.java
new file mode 100644 (file)
index 0000000..21ac2aa
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TrapezoidFinSetSaver extends FinSetSaver {
+
+       private static final TrapezoidFinSetSaver instance = new TrapezoidFinSetSaver();
+       
+       public static ArrayList<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               ArrayList<String> list = new ArrayList<String>();
+               
+               list.add("<trapezoidfinset>");
+               instance.addParams(c,list);
+               list.add("</trapezoidfinset>");
+               
+               return list;
+       }
+       
+       @Override
+       protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
+               super.addParams(c, elements);
+               
+               net.sf.openrocket.rocketcomponent.TrapezoidFinSet fins = (net.sf.openrocket.rocketcomponent.TrapezoidFinSet)c;
+               elements.add("<rootchord>"+fins.getRootChord()+"</rootchord>");
+               elements.add("<tipchord>"+fins.getTipChord()+"</tipchord>");
+               elements.add("<sweeplength>"+fins.getSweep()+"</sweeplength>");
+               elements.add("<height>"+fins.getHeight()+"</height>");
+       }
+       
+}
diff --git a/src/net/sf/openrocket/file/openrocket/savers/TubeCouplerSaver.java b/src/net/sf/openrocket/file/openrocket/savers/TubeCouplerSaver.java
new file mode 100644 (file)
index 0000000..0e19fd8
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.file.openrocket.savers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TubeCouplerSaver extends ThicknessRingComponentSaver {
+
+       private static final TubeCouplerSaver instance = new TubeCouplerSaver();
+
+       public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
+               List<String> list = new ArrayList<String>();
+
+               list.add("<tubecoupler>");
+               instance.addParams(c, list);
+               list.add("</tubecoupler>");
+
+               return list;
+       }
+
+}
index 7e4b956c863ed85348673c1f79a8f4719f3d3398..dc9901b09e400dce26030242920ea0dcee996bd3 100644 (file)
@@ -20,8 +20,8 @@ import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.document.StorageOptions;
-import net.sf.openrocket.file.OpenRocketSaver;
 import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.file.openrocket.OpenRocketSaver;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 
index c2d0d3367a62b866572745a181a118bf921a82e5..bd6e1552bb7279646d652a0e18ce7ae260de7ce5 100644 (file)
@@ -66,10 +66,10 @@ import net.sf.openrocket.communication.UpdateInfoRetriever;
 import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.file.GeneralRocketLoader;
-import net.sf.openrocket.file.OpenRocketSaver;
 import net.sf.openrocket.file.RocketLoadException;
 import net.sf.openrocket.file.RocketLoader;
 import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.file.openrocket.OpenRocketSaver;
 import net.sf.openrocket.gui.StorageOptionChooser;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
 import net.sf.openrocket.gui.dialogs.AboutDialog;
index 560cc260150a064ec02c281ca15bc539c461ee7a..3d70493dedee977072218c54872efe52d1ddbf42 100644 (file)
@@ -28,6 +28,7 @@ public class CenteringRing extends RadiusRingComponent {
                                        if (pos2 < 0 || pos1 > sibling.getLength())
                                                continue;
                                        
+                                       // TODO: CRITICAL: ClassCastException below:
                                        innerRadius = Math.max(innerRadius, ((InnerTube)sibling).getOuterRadius());
                                }
                                innerRadius = Math.min(innerRadius, getOuterRadius());