document loading refactoring
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Wed, 25 Jan 2012 21:42:20 +0000 (21:42 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Wed, 25 Jan 2012 21:42:20 +0000 (21:42 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@372 180e2498-e6e9-4542-8430-84ac67f01cd8

15 files changed:
core/fileformat.txt
core/resources/l10n/messages.properties
core/src/net/sf/openrocket/database/Databases.java
core/src/net/sf/openrocket/database/MotorDatabase.java
core/src/net/sf/openrocket/file/AbstractRocketLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/file/GeneralRocketLoader.java
core/src/net/sf/openrocket/file/RocketLoader.java
core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java [deleted file]
core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
core/src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java [deleted file]
core/src/net/sf/openrocket/file/openrocket/importt/DocumentLoadingContext.java [new file with mode: 0644]
core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/file/openrocket/importt/PreferredMotorDigests.java [new file with mode: 0644]
core/src/net/sf/openrocket/file/rocksim/importt/RocksimLoader.java
core/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java

index 17247f26dae59b5e92cc00867e0acf9bd0d8e4f8..3ba93461cdad6a43e852275fa6b9e9ff96ecc64d 100644 (file)
@@ -5,7 +5,7 @@ reference implementation.  This will hopefully change in the future.
 
 The "version" attribute of the <openrocket> tag describes the file
 format version used, while the "creator" attribute MAY describe the
-software version used to write the document.
+software and version used to write the document.
 
 The file format version is increased every time the format is
 changed.  The minor number is increased when changes are made that are
@@ -20,11 +20,6 @@ version that supports all the necessary design features.
 The following file format versions exist:
 
 
-0.9:  Used before the first public release of Openrocket; effectively
-      equivalent to 1.0.  Should not be used when writing documents.
-      OpenRocket accepts this version when reading files, but other
-      software need not.
-
 1.0:  File format version of the first public release (OpenRocket 0.9.0)
 
 1.1:  Introduced with OpenRocket 0.9.4.  Adds support for saving fin
@@ -40,4 +35,5 @@ The following file format versions exist:
       <geodeticmethod> parameters to the simulation conditions element.
 
 1.4:  Introduced with OpenRocket 1.1.10.  Adds the launchrodvelocity and
-      deploymentvelocity attributes to <flightdata> element.
\ No newline at end of file
+      deploymentvelocity attributes to <flightdata> element.  The motor
+      digesting algorithm was changed.
index e385eb94e8c4febbb76b30fb86ee5d8a510d2a5a..58fd496abe014eea0a139c08b8f659321889b372 100644 (file)
@@ -1023,9 +1023,11 @@ main.menu.debug.createtestrocket = Create test rocket
 ! Material database
 ! BULK_MATERIAL
 Databases.materials.Acrylic = Acrylic
+Databases.materials.Aluminum = Aluminum
 Databases.materials.Balsa = Balsa
 Databases.materials.Basswood = Basswood
 Databases.materials.Birch = Birch
+Databases.materials.Brass = Brass
 Databases.materials.Cardboard = Cardboard
 Databases.materials.Carbonfiber = Carbon fiber
 Databases.materials.Cork = Cork
@@ -1040,8 +1042,10 @@ Databases.materials.PolycarbonateLexan = Polycarbonate (Lexan)
 Databases.materials.Polystyrene = Polystyrene
 Databases.materials.PVC = PVC
 Databases.materials.Spruce = Spruce
+Databases.materials.Steel = Steel
 Databases.materials.StyrofoamgenericEPS = Styrofoam (generic EPS)
 Databases.materials.StyrofoamBluefoamXPS = Styrofoam \"Blue foam\" (XPS)
+Databases.materials.Titanium = Titanium
 Databases.materials.Quantumtubing = Quantum tubing
 Databases.materials.BlueTube = Blue tube
 !SURFACE_MATERIAL
index d8a132b02345037f2ca840d09f638dd78b1302ad..30797e31a7561dd48de7e5623e7e8c0db1dbe349 100644 (file)
@@ -18,7 +18,7 @@ public class Databases {
        private static final Translator trans = Application.getTranslator();
        
        /* Static implementations of specific databases: */
-
+       
        /**
         * A database of bulk materials (with bulk densities).
         */
@@ -32,15 +32,17 @@ public class Databases {
         */
        public static final Database<Material> LINE_MATERIAL = new Database<Material>();
        
-
-
+       
+       
        static {
                
                // Add default materials
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Acrylic"), 1190, false));
+               BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Aluminum"), 2700, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Balsa"), 170, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Basswood"), 500, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Birch"), 670, false));
+               BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Brass"), 8600, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Cardboard"), 680, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Carbonfiber"), 1780, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Cork"), 240, false));
@@ -55,9 +57,11 @@ public class Databases {
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Polystyrene"), 1050, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.PVC"), 1390, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Spruce"), 450, false));
+               BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Steel"), 7850, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.StyrofoamgenericEPS"), 20, false));
                //              BULK_MATERIAL.add(new Material.Bulk("Styrofoam (Blue foam, XPS)", 32, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.StyrofoamBluefoamXPS"), 32, false));
+               BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Titanium"), 4500, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.Quantumtubing"), 1050, false));
                BULK_MATERIAL.add(new Material.Bulk(trans.get("Databases.materials.BlueTube"), 1300, false));
                
@@ -93,7 +97,7 @@ public class Databases {
                //// Tubular nylon (25 mm, 1 in)
                LINE_MATERIAL.add(new Material.Line(trans.get("Databases.materials.Tubularnylon25mm"), 0.029, false));
                
-
+               
                // Add user-defined materials
                for (Material m : Application.getPreferences().getUserMaterials()) {
                        switch (m.getType()) {
@@ -198,5 +202,5 @@ public class Databases {
                return Material.newMaterial(type, name, density, userDefined);
        }
        
-
+       
 }
index fdde5facedd5fd21cabdb589516e0350a4e3e7da..0a1e9095a9868583a86d9f9d71637d6b0065bf60 100644 (file)
@@ -5,7 +5,7 @@ import java.util.List;
 import net.sf.openrocket.motor.Motor;\r
 \r
 public interface MotorDatabase {\r
-\r
+       \r
        /**\r
         * Return all motors in the database matching a search criteria.  Any search criteria that\r
         * is null or NaN is ignored.\r
@@ -20,5 +20,5 @@ public interface MotorDatabase {
        public List<? extends Motor> findMotors(Motor.Type type,\r
                        String manufacturer, String designation, double diameter,\r
                        double length);\r
-\r
-}
\ No newline at end of file
+       \r
+}\r
diff --git a/core/src/net/sf/openrocket/file/AbstractRocketLoader.java b/core/src/net/sf/openrocket/file/AbstractRocketLoader.java
new file mode 100644 (file)
index 0000000..0cb1911
--- /dev/null
@@ -0,0 +1,77 @@
+package net.sf.openrocket.file;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.document.OpenRocketDocument;
+
+
+public abstract class AbstractRocketLoader implements RocketLoader {
+       protected final WarningSet warnings = new WarningSet();
+       
+       
+       /**
+        * Loads a rocket from the specified File object.
+        */
+       @Override
+       public final OpenRocketDocument load(File source) throws RocketLoadException {
+               warnings.clear();
+               InputStream stream = null;
+               
+               try {
+                       
+                       stream = new BufferedInputStream(new FileInputStream(source));
+                       return load(stream);
+                       
+               } catch (FileNotFoundException e) {
+                       throw new RocketLoadException("File not found: " + source);
+               } finally {
+                       if (stream != null) {
+                               try {
+                                       stream.close();
+                               } catch (IOException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Loads a rocket from the specified InputStream.
+        */
+       @Override
+       public final OpenRocketDocument load(InputStream source) throws RocketLoadException {
+               warnings.clear();
+               
+               try {
+                       return loadFromStream(source);
+               } catch (RocketLoadException e) {
+                       throw e;
+               } catch (IOException e) {
+                       throw new RocketLoadException("I/O error: " + e.getMessage(), e);
+               }
+       }
+       
+       
+       
+       /**
+        * This method is called by the default implementations of {@link #load(File)} 
+        * and {@link #load(InputStream)} to load the rocket.
+        * 
+        * @throws RocketLoadException  if an error occurs during loading.
+        */
+       protected abstract OpenRocketDocument loadFromStream(InputStream source) throws IOException,
+                       RocketLoadException;
+       
+       
+       
+       @Override
+       public final WarningSet getWarnings() {
+               return warnings;
+       }
+}
index b6117f787e56409914747ef79730f8b242747063..68555d1052b26b6d2ebc5c30cc361767ac955518 100644 (file)
@@ -1,14 +1,17 @@
 package net.sf.openrocket.file;
 
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.file.openrocket.OpenRocketLoader;
-
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.openrocket.importt.OpenRocketLoader;
+import net.sf.openrocket.file.rocksim.importt.RocksimLoader;
 
 
 /**
@@ -18,24 +21,23 @@ import java.util.zip.GZIPInputStream;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public class GeneralRocketLoader extends RocketLoader {
-
+public class GeneralRocketLoader extends AbstractRocketLoader {
+       
        private static final int READ_BYTES = 300;
        
-       private static final byte[] GZIP_SIGNATURE = { 31, -117 };  // 0x1f, 0x8b
-       private static final byte[] OPENROCKET_SIGNATURE = 
-               "<openrocket".getBytes(Charset.forName("US-ASCII"));
-    private static final byte[] ROCKSIM_SIGNATURE = 
-        "<RockSimDoc".getBytes(Charset.forName("US-ASCII"));
+       private static final byte[] GZIP_SIGNATURE = { 31, -117 }; // 0x1f, 0x8b
+       private static final byte[] ZIP_SIGNATURE = "PK".getBytes(Charset.forName("US-ASCII"));
+       private static final byte[] OPENROCKET_SIGNATURE = "<openrocket".getBytes(Charset.forName("US-ASCII"));
+       private static final byte[] ROCKSIM_SIGNATURE = "<RockSimDoc".getBytes(Charset.forName("US-ASCII"));
        
        private final OpenRocketLoader openRocketLoader = new OpenRocketLoader();
-    
-    private final net.sf.openrocket.file.rocksim.importt.RocksimLoader rocksimLoader = new net.sf.openrocket.file.rocksim.importt.RocksimLoader();
+       
+       private final RocksimLoader rocksimLoader = new RocksimLoader();
        
        @Override
        protected OpenRocketDocument loadFromStream(InputStream source) throws IOException,
                        RocketLoadException {
-
+               
                // Check for mark() support
                if (!source.markSupported()) {
                        source = new BufferedInputStream(source);
@@ -53,17 +55,34 @@ public class GeneralRocketLoader extends RocketLoader {
                }
                
                // Detect the appropriate loader
-
+               
                // Check for GZIP
-               if (buffer[0] == GZIP_SIGNATURE[0]  &&  buffer[1] == GZIP_SIGNATURE[1]) {
+               if (buffer[0] == GZIP_SIGNATURE[0] && buffer[1] == GZIP_SIGNATURE[1]) {
                        OpenRocketDocument doc = loadFromStream(new GZIPInputStream(source));
                        doc.getDefaultStorageOptions().setCompressionEnabled(true);
                        return doc;
                }
                
+               // Check for ZIP (for future compatibility)
+               if (buffer[0] == ZIP_SIGNATURE[0] && buffer[1] == ZIP_SIGNATURE[1]) {
+                       // Search for entry with name *.ork
+                       ZipInputStream in = new ZipInputStream(source);
+                       while (true) {
+                               ZipEntry entry = in.getNextEntry();
+                               if (entry == null) {
+                                       throw new RocketLoadException("Unsupported or corrupt file.");
+                               }
+                               if (entry.getName().matches(".*\\.[oO][rR][kK]$")) {
+                                       OpenRocketDocument doc = loadFromStream(in);
+                                       doc.getDefaultStorageOptions().setCompressionEnabled(true);
+                                       return doc;
+                               }
+                       }
+               }
+               
                // Check for OpenRocket
                int match = 0;
-               for (int i=0; i < count; i++) {
+               for (int i = 0; i < count; i++) {
                        if (buffer[i] == OPENROCKET_SIGNATURE[match]) {
                                match++;
                                if (match == OPENROCKET_SIGNATURE.length) {
@@ -73,16 +92,16 @@ public class GeneralRocketLoader extends RocketLoader {
                                match = 0;
                        }
                }
-
-        byte[] typeIdentifier = Arrays.copyOf(buffer, ROCKSIM_SIGNATURE.length);
-        if (Arrays.equals(ROCKSIM_SIGNATURE, typeIdentifier)) {
-            return loadUsing(source, rocksimLoader);            
-        }
+               
+               byte[] typeIdentifier = Arrays.copyOf(buffer, ROCKSIM_SIGNATURE.length);
+               if (Arrays.equals(ROCKSIM_SIGNATURE, typeIdentifier)) {
+                       return loadUsing(source, rocksimLoader);
+               }
                throw new RocketLoadException("Unsupported or corrupt file.");
        }
        
-       private OpenRocketDocument loadUsing(InputStream source, RocketLoader loader) 
-       throws RocketLoadException {
+       private OpenRocketDocument loadUsing(InputStream source, RocketLoader loader)
+                       throws RocketLoadException {
                warnings.clear();
                OpenRocketDocument doc = loader.load(source);
                warnings.addAll(loader.getWarnings());
index 4fc8dada7cbd12e37c1ce6c3946e37336778e4a3..6918acd1f2e3be0dd3425bdd7dbfd0aedcfb8de4 100644 (file)
@@ -1,74 +1,17 @@
 package net.sf.openrocket.file;
 
-import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
 import java.io.InputStream;
 
 import net.sf.openrocket.aerodynamics.WarningSet;
 import net.sf.openrocket.document.OpenRocketDocument;
 
-
-public abstract class RocketLoader {
-       protected final WarningSet warnings = new WarningSet();
-       
-       
-       /**
-        * Loads a rocket from the specified File object.
-        */
-       public final OpenRocketDocument load(File source) throws RocketLoadException {
-               warnings.clear();
-               InputStream stream = null;
-               
-               try {
-                       
-                       stream = new BufferedInputStream(new FileInputStream(source));
-                       return load(stream);
-                       
-               } catch (FileNotFoundException e) {
-                       throw new RocketLoadException("File not found: " + source);
-               } finally {
-                       if (stream != null) {
-                               try {
-                                       stream.close();
-                               } catch (IOException e) {
-                                       e.printStackTrace();
-                               }
-                       }
-               }
-       }
+public interface RocketLoader {
        
-       /**
-        * Loads a rocket from the specified InputStream.
-        */
-       public final OpenRocketDocument load(InputStream source) throws RocketLoadException {
-               warnings.clear();
-               
-               try {
-                       return loadFromStream(source);
-               } catch (RocketLoadException e) {
-                       throw e;
-               } catch (IOException e) {
-                       throw new RocketLoadException("I/O error: " + e.getMessage(), e);
-               }
-       }
+       public OpenRocketDocument load(File source) throws RocketLoadException;
        
+       public OpenRocketDocument load(InputStream source) throws RocketLoadException;
        
-
-       /**
-        * This method is called by the default implementations of {@link #load(File)} 
-        * and {@link #load(InputStream)} to load the rocket.
-        * 
-        * @throws RocketLoadException  if an error occurs during loading.
-        */
-       protected abstract OpenRocketDocument loadFromStream(InputStream source) throws IOException,
-                       RocketLoadException;
-       
+       public WarningSet getWarnings();
        
-
-       public final WarningSet getWarnings() {
-               return warnings;
-       }
 }
diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java
deleted file mode 100644 (file)
index f8d6bed..0000000
+++ /dev/null
@@ -1,2084 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-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.Simulation.Status;
-import net.sf.openrocket.document.StorageOptions;
-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.logging.LogHelper;
-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.ExternalComponent.Finish;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
-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.RocketComponent.Position;
-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.simulation.FlightData;
-import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightDataType;
-import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.FlightEvent.Type;
-import net.sf.openrocket.simulation.SimulationOptions;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.Color;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.GeodeticComputationStrategy;
-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 {
-       private static final LogHelper log = Application.getLogger();
-       
-       
-       @Override
-       public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
-                       IOException {
-               log.info("Loading .ork file");
-               
-               InputSource xmlSource = new InputSource(source);
-               OpenRocketHandler handler = new OpenRocketHandler();
-               
-
-               try {
-                       SimpleSAX.readXML(xmlSource, handler, warnings);
-               } catch (SAXException e) {
-                       log.warn("Malformed XML in input");
-                       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(FlightDataType.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();
-               log.info("Loading done");
-               return doc;
-       }
-       
-}
-
-
-
-class DocumentConfig {
-       
-       /* Remember to update OpenRocketSaver as well! */
-       public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1", "1.2", "1.3" };
-       
-
-       ////////  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 BugException(
-                                       "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.findMethod(RocketComponent.class, "setName", String.class)));
-               setters.put("RocketComponent:color", new ColorSetter(
-                               Reflection.findMethod(RocketComponent.class, "setColor", Color.class)));
-               setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
-                               Reflection.findMethod(RocketComponent.class, "setLineStyle", LineStyle.class),
-                               LineStyle.class));
-               setters.put("RocketComponent:position", new PositionSetter());
-               setters.put("RocketComponent:overridemass", new OverrideSetter(
-                               Reflection.findMethod(RocketComponent.class, "setOverrideMass", double.class),
-                               Reflection.findMethod(RocketComponent.class, "setMassOverridden", boolean.class)));
-               setters.put("RocketComponent:overridecg", new OverrideSetter(
-                               Reflection.findMethod(RocketComponent.class, "setOverrideCGX", double.class),
-                               Reflection.findMethod(RocketComponent.class, "setCGOverridden", boolean.class)));
-               setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
-                               Reflection.findMethod(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
-               setters.put("RocketComponent:comment", new StringSetter(
-                               Reflection.findMethod(RocketComponent.class, "setComment", String.class)));
-               
-               // ExternalComponent
-               setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
-                               Reflection.findMethod(ExternalComponent.class, "setFinish", Finish.class),
-                               Finish.class));
-               setters.put("ExternalComponent:material", new MaterialSetter(
-                               Reflection.findMethod(ExternalComponent.class, "setMaterial", Material.class),
-                               Material.Type.BULK));
-               
-               // BodyComponent
-               setters.put("BodyComponent:length", new DoubleSetter(
-                               Reflection.findMethod(BodyComponent.class, "setLength", double.class)));
-               
-               // SymmetricComponent
-               setters.put("SymmetricComponent:thickness", new DoubleSetter(
-                               Reflection.findMethod(SymmetricComponent.class, "setThickness", double.class),
-                               "filled",
-                               Reflection.findMethod(SymmetricComponent.class, "setFilled", boolean.class)));
-               
-               // BodyTube
-               setters.put("BodyTube:radius", new DoubleSetter(
-                               Reflection.findMethod(BodyTube.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(BodyTube.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // Transition
-               setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
-                               Reflection.findMethod(Transition.class, "setType", Transition.Shape.class),
-                               Transition.Shape.class));
-               setters.put("Transition:shapeclipped", new BooleanSetter(
-                               Reflection.findMethod(Transition.class, "setClipped", boolean.class)));
-               setters.put("Transition:shapeparameter", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setShapeParameter", double.class)));
-               
-               setters.put("Transition:foreradius", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setForeRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(Transition.class, "setForeRadiusAutomatic", boolean.class)));
-               setters.put("Transition:aftradius", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setAftRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(Transition.class, "setAftRadiusAutomatic", boolean.class)));
-               
-               setters.put("Transition:foreshoulderradius", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setForeShoulderRadius", double.class)));
-               setters.put("Transition:foreshoulderlength", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setForeShoulderLength", double.class)));
-               setters.put("Transition:foreshoulderthickness", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setForeShoulderThickness", double.class)));
-               setters.put("Transition:foreshouldercapped", new BooleanSetter(
-                               Reflection.findMethod(Transition.class, "setForeShoulderCapped", boolean.class)));
-               
-               setters.put("Transition:aftshoulderradius", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setAftShoulderRadius", double.class)));
-               setters.put("Transition:aftshoulderlength", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setAftShoulderLength", double.class)));
-               setters.put("Transition:aftshoulderthickness", new DoubleSetter(
-                               Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class)));
-               setters.put("Transition:aftshouldercapped", new BooleanSetter(
-                               Reflection.findMethod(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.findMethod(FinSet.class, "setFinCount", int.class)));
-               setters.put("FinSet:rotation", new DoubleSetter(
-                               Reflection.findMethod(FinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
-               setters.put("FinSet:thickness", new DoubleSetter(
-                               Reflection.findMethod(FinSet.class, "setThickness", double.class)));
-               setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
-                               Reflection.findMethod(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
-                               FinSet.CrossSection.class));
-               setters.put("FinSet:cant", new DoubleSetter(
-                               Reflection.findMethod(FinSet.class, "setCantAngle", double.class), Math.PI / 180.0));
-               setters.put("FinSet:tabheight", new DoubleSetter(
-                               Reflection.findMethod(FinSet.class, "setTabHeight", double.class)));
-               setters.put("FinSet:tablength", new DoubleSetter(
-                               Reflection.findMethod(FinSet.class, "setTabLength", double.class)));
-               setters.put("FinSet:tabposition", new FinTabPositionSetter());
-               
-               // TrapezoidFinSet
-               setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
-                               Reflection.findMethod(TrapezoidFinSet.class, "setRootChord", double.class)));
-               setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
-                               Reflection.findMethod(TrapezoidFinSet.class, "setTipChord", double.class)));
-               setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
-                               Reflection.findMethod(TrapezoidFinSet.class, "setSweep", double.class)));
-               setters.put("TrapezoidFinSet:height", new DoubleSetter(
-                               Reflection.findMethod(TrapezoidFinSet.class, "setHeight", double.class)));
-               
-               // EllipticalFinSet
-               setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
-                               Reflection.findMethod(EllipticalFinSet.class, "setLength", double.class)));
-               setters.put("EllipticalFinSet:height", new DoubleSetter(
-                               Reflection.findMethod(EllipticalFinSet.class, "setHeight", double.class)));
-               
-               // FreeformFinSet points handled as a special handler
-               
-               // LaunchLug
-               setters.put("LaunchLug:radius", new DoubleSetter(
-                               Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
-               setters.put("LaunchLug:length", new DoubleSetter(
-                               Reflection.findMethod(LaunchLug.class, "setLength", double.class)));
-               setters.put("LaunchLug:thickness", new DoubleSetter(
-                               Reflection.findMethod(LaunchLug.class, "setThickness", double.class)));
-               setters.put("LaunchLug:radialdirection", new DoubleSetter(
-                               Reflection.findMethod(LaunchLug.class, "setRadialDirection", double.class),
-                               Math.PI / 180.0));
-               
-               // InternalComponent - nothing
-               
-               // StructuralComponent
-               setters.put("StructuralComponent:material", new MaterialSetter(
-                               Reflection.findMethod(StructuralComponent.class, "setMaterial", Material.class),
-                               Material.Type.BULK));
-               
-               // RingComponent
-               setters.put("RingComponent:length", new DoubleSetter(
-                               Reflection.findMethod(RingComponent.class, "setLength", double.class)));
-               setters.put("RingComponent:radialposition", new DoubleSetter(
-                               Reflection.findMethod(RingComponent.class, "setRadialPosition", double.class)));
-               setters.put("RingComponent:radialdirection", new DoubleSetter(
-                               Reflection.findMethod(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.findMethod(ThicknessRingComponent.class, "setThickness", double.class)));
-               
-               // EngineBlock
-               setters.put("EngineBlock:outerradius", new DoubleSetter(
-                               Reflection.findMethod(EngineBlock.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // TubeCoupler
-               setters.put("TubeCoupler:outerradius", new DoubleSetter(
-                               Reflection.findMethod(TubeCoupler.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // InnerTube
-               setters.put("InnerTube:outerradius", new DoubleSetter(
-                               Reflection.findMethod(InnerTube.class, "setOuterRadius", double.class)));
-               setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
-               setters.put("InnerTube:clusterscale", new DoubleSetter(
-                               Reflection.findMethod(InnerTube.class, "setClusterScale", double.class)));
-               setters.put("InnerTube:clusterrotation", new DoubleSetter(
-                               Reflection.findMethod(InnerTube.class, "setClusterRotation", double.class),
-                               Math.PI / 180.0));
-               
-               // RadiusRingComponent
-               
-               // Bulkhead
-               setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
-                               Reflection.findMethod(RadiusRingComponent.class, "setInnerRadius", double.class)));
-               setters.put("Bulkhead:outerradius", new DoubleSetter(
-                               Reflection.findMethod(Bulkhead.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-               // CenteringRing
-               setters.put("CenteringRing:innerradius", new DoubleSetter(
-                               Reflection.findMethod(CenteringRing.class, "setInnerRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
-               setters.put("CenteringRing:outerradius", new DoubleSetter(
-                               Reflection.findMethod(CenteringRing.class, "setOuterRadius", double.class),
-                               "auto",
-                               Reflection.findMethod(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
-               
-
-               // MassObject
-               setters.put("MassObject:packedlength", new DoubleSetter(
-                               Reflection.findMethod(MassObject.class, "setLength", double.class)));
-               setters.put("MassObject:packedradius", new DoubleSetter(
-                               Reflection.findMethod(MassObject.class, "setRadius", double.class)));
-               setters.put("MassObject:radialposition", new DoubleSetter(
-                               Reflection.findMethod(MassObject.class, "setRadialPosition", double.class)));
-               setters.put("MassObject:radialdirection", new DoubleSetter(
-                               Reflection.findMethod(MassObject.class, "setRadialDirection", double.class),
-                               Math.PI / 180.0));
-               
-               // MassComponent
-               setters.put("MassComponent:mass", new DoubleSetter(
-                               Reflection.findMethod(MassComponent.class, "setComponentMass", double.class)));
-               
-               // ShockCord
-               setters.put("ShockCord:cordlength", new DoubleSetter(
-                               Reflection.findMethod(ShockCord.class, "setCordLength", double.class)));
-               setters.put("ShockCord:material", new MaterialSetter(
-                               Reflection.findMethod(ShockCord.class, "setMaterial", Material.class),
-                               Material.Type.LINE));
-               
-               // RecoveryDevice
-               setters.put("RecoveryDevice:cd", new DoubleSetter(
-                               Reflection.findMethod(RecoveryDevice.class, "setCD", double.class),
-                               "auto",
-                               Reflection.findMethod(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
-               setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
-                               Reflection.findMethod(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
-                               RecoveryDevice.DeployEvent.class));
-               setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
-                               Reflection.findMethod(RecoveryDevice.class, "setDeployAltitude", double.class)));
-               setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
-                               Reflection.findMethod(RecoveryDevice.class, "setDeployDelay", double.class)));
-               setters.put("RecoveryDevice:material", new MaterialSetter(
-                               Reflection.findMethod(RecoveryDevice.class, "setMaterial", Material.class),
-                               Material.Type.SURFACE));
-               
-               // Parachute
-               setters.put("Parachute:diameter", new DoubleSetter(
-                               Reflection.findMethod(Parachute.class, "setDiameter", double.class)));
-               setters.put("Parachute:linecount", new IntSetter(
-                               Reflection.findMethod(Parachute.class, "setLineCount", int.class)));
-               setters.put("Parachute:linelength", new DoubleSetter(
-                               Reflection.findMethod(Parachute.class, "setLineLength", double.class)));
-               setters.put("Parachute:linematerial", new MaterialSetter(
-                               Reflection.findMethod(Parachute.class, "setLineMaterial", Material.class),
-                               Material.Type.LINE));
-               
-               // Streamer
-               setters.put("Streamer:striplength", new DoubleSetter(
-                               Reflection.findMethod(Streamer.class, "setStripLength", double.class)));
-               setters.put("Streamer:stripwidth", new DoubleSetter(
-                               Reflection.findMethod(Streamer.class, "setStripWidth", double.class)));
-               
-               // Rocket
-               // <motorconfiguration> handled by separate handler
-               setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
-                               Reflection.findMethod(Rocket.class, "setReferenceType", ReferenceType.class),
-                               ReferenceType.class));
-               setters.put("Rocket:customreference", new DoubleSetter(
-                               Reflection.findMethod(Rocket.class, "setCustomReferenceLength", double.class)));
-               setters.put("Rocket:designer", new StringSetter(
-                               Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
-               setters.put("Rocket:revision", new StringSetter(
-                               Reflection.findMethod(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 BugException("Error constructing component.", e);
-               } catch (IllegalAccessException e) {
-                       throw new BugException("Error constructing component.", e);
-               } catch (InvocationTargetException e) {
-                       throw Reflection.handleWrappedException(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
-                               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 String digest = 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;
-               }
-               
-               List<? extends Motor> motors = Application.getMotorSetDatabase().findMotors(type, manufacturer,
-                               designation, diameter, length);
-               
-               // No motors
-               if (motors.size() == 0) {
-                       Warning.MissingMotor mmw = new Warning.MissingMotor();
-                       mmw.setDesignation(designation);
-                       mmw.setDigest(digest);
-                       mmw.setDiameter(diameter);
-                       mmw.setLength(length);
-                       mmw.setManufacturer(manufacturer);
-                       mmw.setType(type);
-                       warnings.add(mmw);
-                       return null;
-               }
-               
-               // One motor
-               if (motors.size() == 1) {
-                       Motor m = motors.get(0);
-                       if (digest != null && !digest.equals(m.getDigest())) {
-                               String str = "Motor with designation '" + designation + "'";
-                               if (manufacturer != null)
-                                       str += " for manufacturer '" + manufacturer + "'";
-                               str += " has differing thrust curve than the original.";
-                               warnings.add(str);
-                       }
-                       return m;
-               }
-               
-               // Multiple motors, check digest for which one to use
-               if (digest != null) {
-                       
-                       // Check for motor with correct digest
-                       for (Motor m : motors) {
-                               if (digest.equals(m.getDigest())) {
-                                       return m;
-                               }
-                       }
-                       String str = "Motor with designation '" + designation + "'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       str += " has differing thrust curve than the original.";
-                       warnings.add(str);
-                       
-               } else {
-                       
-                       // No digest, check for preferred digest (OpenRocket <= 1.1.0)
-                       // TODO: MEDIUM: This should only be done for document versions 1.1 and below
-                       for (Motor m : motors) {
-                               if (PreferredMotorDigests.DIGESTS.contains(m.getDigest())) {
-                                       return m;
-                               }
-                       }
-                       
-                       String str = "Multiple motors with designation '" + designation + "'";
-                       if (manufacturer != null)
-                               str += " for manufacturer '" + manufacturer + "'";
-                       str += " found, one chosen arbitrarily.";
-                       warnings.add(str);
-                       
-               }
-               return motors.get(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.trim())) {
-                                       type = t;
-                                       break;
-                               }
-                       }
-                       if (type == null) {
-                               warnings.add(Warning.fromString("Unknown motor type '" + content + "', ignoring."));
-                       }
-                       
-               } else if (element.equals("manufacturer")) {
-                       
-                       // Manufacturer
-                       manufacturer = content.trim();
-                       
-               } else if (element.equals("designation")) {
-                       
-                       // Designation
-                       designation = content.trim();
-                       
-               } else if (element.equals("digest")) {
-                       
-                       // Digest
-                       digest = content.trim();
-                       
-               } else if (element.equals("diameter")) {
-                       
-                       // Diameter
-                       diameter = Double.NaN;
-                       try {
-                               diameter = Double.parseDouble(content.trim());
-                       } 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.trim());
-                       } 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.trim());
-                               } 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;
-               }
-               
-               SimulationOptions conditions;
-               if (conditionHandler != null) {
-                       conditions = conditionHandler.getConditions();
-               } else {
-                       warnings.add("Simulation conditions not defined, using defaults.");
-                       conditions = new SimulationOptions(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 SimulationOptions conditions;
-       private AtmosphereHandler atmosphereHandler;
-       
-       public SimulationConditionsHandler(Rocket rocket) {
-               conditions = new SimulationOptions(rocket);
-               // Set up default loading settings (which may differ from the new defaults)
-               conditions.setGeodeticComputation(GeodeticComputationStrategy.FLAT);
-       }
-       
-       public SimulationOptions 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("launchlongitude")) {
-                       if (Double.isNaN(d)) {
-                               warnings.add("Illegal launch longitude.");
-                       } else {
-                               conditions.setLaunchLongitude(d);
-                       }
-               } else if (element.equals("geodeticmethod")) {
-                       GeodeticComputationStrategy gcs =
-                                       (GeodeticComputationStrategy) DocumentConfig.findEnum(content, GeodeticComputationStrategy.class);
-                       if (gcs != null) {
-                               conditions.setGeodeticComputation(gcs);
-                       } else {
-                               warnings.add("Unknown geodetic computation method '" + content + "'");
-                       }
-               } 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(SimulationOptions 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;
-                       double launchRodVelocity = Double.NaN;
-                       double deploymentVelocity = 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) {
-                       }
-                       try {
-                               launchRodVelocity = DocumentConfig.stringToDouble(attributes.get("launchrodvelocity"));
-                       } catch (NumberFormatException ignore) {
-                       }
-                       try {
-                               deploymentVelocity = DocumentConfig.stringToDouble(attributes.get("deploymentvelocity"));
-                       } catch (NumberFormatException ignore) {
-                       }
-                       
-                       data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
-                                       timeToApogee, flightTime, groundHitVelocity, launchRodVelocity, deploymentVelocity);
-               }
-               
-               data.getWarningSet().addAll(warningSet);
-               data.immute();
-       }
-       
-
-}
-
-
-class FlightDataBranchHandler extends ElementHandler {
-       private final FlightDataType[] types;
-       private final FlightDataBranch branch;
-       
-       public FlightDataBranchHandler(String name, String typeList) {
-               String[] split = typeList.split(",");
-               types = new FlightDataType[split.length];
-               for (int i = 0; i < split.length; i++) {
-                       types[i] = FlightDataType.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(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;
-       }
-       
-       @Override
-       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;
-       }
-       
-       @Override
-       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;
-       }
-       
-       @Override
-       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;
-       }
-       
-       
-       @Override
-       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;
-       }
-       
-       @Override
-       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;
-       }
-       
-       @Override
-       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;
-       }
-       
-       @Override
-       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 {
-       
-       @Override
-       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.findMethod(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 {
-       
-       @Override
-       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);
-       }
-}
index c04cce9b2d0bdb0fc94ff76ce882441ac94891e7..5b863158ad54cdda9e8f79777c49c9b58a72e4b1 100644 (file)
@@ -36,7 +36,7 @@ import net.sf.openrocket.util.TextUtil;
 public class OpenRocketSaver extends RocketSaver {
        private static final LogHelper log = Application.getLogger();
        
-
+       
        /**
         * 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
@@ -44,13 +44,13 @@ public class OpenRocketSaver extends RocketSaver {
         */
        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;
@@ -60,7 +60,7 @@ public class OpenRocketSaver extends RocketSaver {
        private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
        private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
        
-
+       
        private int indent;
        private Writer dest;
        
@@ -83,10 +83,10 @@ public class OpenRocketSaver extends RocketSaver {
                                (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
                log.debug("Storing file version " + fileVersionString);
                
-
+               
                this.indent = 0;
                
-
+               
                writeln("<?xml version='1.0' encoding='utf-8'?>");
                writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
                                + BuildProperties.getVersion() + "\">");
@@ -121,7 +121,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        @Override
        public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
                
@@ -141,14 +141,14 @@ public class OpenRocketSaver extends RocketSaver {
                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();
@@ -183,7 +183,8 @@ public class OpenRocketSaver extends RocketSaver {
         */
        private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
                /*
-                * File version 1.2 is required for:
+                * File version 1.4 is required for:
+                *  - saving simulation data
                 *  - saving motor data
                 * 
                 * File version 1.1 is required for:
@@ -192,13 +193,13 @@ public class OpenRocketSaver extends RocketSaver {
                 * 
                 * Otherwise use version 1.0.
                 */
-
-               // Check if design has simulations defined (version 1.3)
+               
+               // Check if design has simulations defined (version 1.4)
                if (document.getSimulationCount() > 0) {
-                       return FILE_VERSION_DIVISOR + 3;
+                       return FILE_VERSION_DIVISOR + 4;
                }
                
-               // Check for motor definitions (version 1.2)
+               // Check for motor definitions (version 1.4)
                Iterator<RocketComponent> iterator = document.getRocket().iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
@@ -208,7 +209,7 @@ public class OpenRocketSaver extends RocketSaver {
                        MotorMount mount = (MotorMount) c;
                        for (String id : document.getRocket().getMotorConfigurationIDs()) {
                                if (mount.getMotor(id) != null) {
-                                       return FILE_VERSION_DIVISOR + 2;
+                                       return FILE_VERSION_DIVISOR + 4;
                                }
                        }
                }
@@ -240,7 +241,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        @SuppressWarnings("unchecked")
        private void saveComponent(RocketComponent component) throws IOException {
                
@@ -335,12 +336,12 @@ public class OpenRocketSaver extends RocketSaver {
                indent--;
                writeln("</conditions>");
                
-
+               
                for (String s : simulation.getSimulationListeners()) {
                        writeElement("listener", escapeXML(s));
                }
                
-
+               
                // Write basic simulation data
                
                FlightData data = simulation.getSimulatedData();
@@ -393,7 +394,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
                        throws IOException {
                double previousTime = -100000;
@@ -462,7 +463,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        /* TODO: LOW: This is largely duplicated from above! */
        private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
                int count = 0;
@@ -506,7 +507,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
                        throws IOException {
                sb.setLength(0);
@@ -521,7 +522,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        private void writeElement(String element, Object content) throws IOException {
                if (content == null)
                        content = "";
@@ -529,7 +530,7 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
+       
        private void writeln(String str) throws IOException {
                if (str.length() == 0) {
                        dest.write("\n");
@@ -543,8 +544,8 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
-
+       
+       
        /**
         * Return the XML equivalent of an enum name.
         * 
diff --git a/core/src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java b/core/src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java
deleted file mode 100644 (file)
index d05cccb..0000000
+++ /dev/null
@@ -1,885 +0,0 @@
-package net.sf.openrocket.file.openrocket;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * This class contains the motor digests of motors included in OpenRocket versions prior to 1.1.1.
- * Before this the motor digest was not included in the design files, and therefore if the motor
- * digest is missing when loading a file, the loader should prefer the motors with a digest defined
- * in this class.
- * <p>
- * This is not a requirement for supporting the OpenRocket format, but allows opening older OpenRocket
- * design files accurately without warnings of multiple motors. 
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-final class PreferredMotorDigests {
-       
-       /**
-        * A set containing the preferred motor digests.
-        */
-       public static final Set<String> DIGESTS;
-       static {
-               /*
-                * The list contains 845 digests, set initial parameters suitably to
-                * prevent any rehashing operations and to minimize size (power of two).
-                *    845/1024 = 0.825
-                */
-               Set<String> set = new HashSet<String>(1024, 0.85f);
-               
-               set.add("000ffb4c8e49ae47b2ab9a659da9e59b");
-               set.add("0039ed088e61360d934d9bd8503fad92");
-               set.add("003eeba358de7ebf9293b0e4c4ca9e66");
-               set.add("00e1a0576a93101d458c1c3d68d3eee0");
-               set.add("0111b89926277a6ea3f6075052343105");
-               set.add("0142c270a670ffff41c43268b0f129b9");
-               set.add("01be1f9100e05fb15df4c13395f7181c");
-               set.add("026f5924c48693077f2b11cdcdeb7452");
-               set.add("029082f7acda395568ca7f7df40764e1");
-               set.add("02dd1b3e2df7daf48b763f5ace35345e");
-               set.add("036e124dce42859ff08efa79e1f202e8");
-               set.add("03b88e64af521b03803247922801c996");
-               set.add("0468d7dc3dca25ac073dac1bd674e271");
-               set.add("048cfb7c2477c6e957d501c5ed3bc252");
-               set.add("049dda2ad1a709321734f393dc8a115b");
-               set.add("056d61b6a268283411e9dc9731dbb5e6");
-               set.add("05b85612f288726b02cdc47af7026aac");
-               set.add("05e205dc5dbd95db25305aa5c77b1192");
-               set.add("0601c6944d02e8736c09c2a8bb7cba49");
-               set.add("0622884d0a0954b1df6694ead24868bf");
-               set.add("063e7748d9a96508a70b1a2a1887aa3d");
-               set.add("06634321a8c5d533eb5efcbb40143257");
-               set.add("069a54372ed2776286160384ca0cac4f");
-               set.add("075539867b13c2afcc5198e00d7f4b5c");
-               set.add("076d9374af5fb0f2469083f9b57b7b96");
-               set.add("07c44b615a67060bca83c6faed56c0c6");
-               set.add("0825628215a980eed5fb4bed4eaec3b8");
-               set.add("082bad018f6d1e5622c371c1fe3148d6");
-               set.add("0837c3014078c8c8e79961b939be83cb");
-               set.add("08abceec22c5f6be5e9864be38df8ad5");
-               set.add("08c3b40a4bcf7a33256e5543e484f995");
-               set.add("08ca5be1a598772a8683016db619de36");
-               set.add("0a80cecafb53ae0ac73e6aec1a4567dd");
-               set.add("0add7ca688bcd32df8c3367a094e7847");
-               set.add("0b175b4beb0057db1b169d61061208a7");
-               set.add("0b955870dc2007c2b5f07eea57609420");
-               set.add("0c60a51d36ee5118fe29173aff2f6e49");
-               set.add("0c96cd95432d8e2ce6a6463ebf50beb1");
-               set.add("0d06d195c29a7f6fde6d002171922f2e");
-               set.add("0d642d7cb1544d19ec471124db97b92e");
-               set.add("0dd49968e2b1c4b1077e3c7ade056a79");
-               set.add("0e0d93ee28216440a5fa9452c9082351");
-               set.add("0e6774068b61579e20b89771b8a8f273");
-               set.add("0eac15679d3ae2fbd41083492b356b03");
-               set.add("0eca4c015dd1de561c2bbc47eaa4daf6");
-               set.add("0f0e1d09c7dec3a05b870b399ddbf6ee");
-               set.add("0f3c31b26b5768b3202f02f9d6bcc71c");
-               set.add("0f47293601d59fbad2076012090665dc");
-               set.add("0f5a1b31c333b722e4a72acbeba3a189");
-               set.add("0f6a55aca8a317f4d3d3236e4944343d");
-               set.add("0ffaa291ee52495d7dfec03b3a845636");
-               set.add("1092f5c5b48c2dcd4ac0bb16fa5383a8");
-               set.add("10a1689703dc533d435bef7265dd9ac0");
-               set.add("11bcc433b82e714f59809d76eed0ba05");
-               set.add("11ce2ec12b216f3e8d71fd9c53782b23");
-               set.add("11d11cdff93f18d10a1286cc3485c8c7");
-               set.add("11eac86852e12b9c3a2d0a4b183c3b79");
-               set.add("120eab6dd03b9bee7f5eb717e4e9d491");
-               set.add("1272d1a6979ea20a2efee3eb04657915");
-               set.add("12f6c5360c83656356c902ece3c0ff1b");
-               set.add("138a603a483dcf4127f1dcf208843e67");
-               set.add("140276d009fde1357ecdcb5d9ddc8a80");
-               set.add("1491fae1c7ce940915dd8296c74320f3");
-               set.add("14955ccec83043f3b1ef92f8524b3e67");
-               set.add("150b1279bc8b7f509a030274ee8e3f35");
-               set.add("153374d45687af1e96d5b8b1b03a2515");
-               set.add("1536a1389a9cd4ecf5bfaac9f4333852");
-               set.add("1539231d9952fdbe0533df405c46356c");
-               set.add("15d6a88bb656579740291df01297aa5c");
-               set.add("15fbf68a7c02161beb6cad00325752c3");
-               set.add("161cd37f60e13b9850e881bac61c840f");
-               set.add("161ed36663b694184f7f4131d1d5f3df");
-               set.add("167df7bf13809a19da8ff90a27f4b522");
-               set.add("170e81af0371550ea20c827669fbf0fd");
-               set.add("177c0df08cb85a4e13bf7412dacf2699");
-               set.add("179b9694bca64255ce9c0b06a08f46e6");
-               set.add("17d55e2cd3df50ef07aff9be6b160915");
-               set.add("1835337dfceafa20029fe6e472e7c7f0");
-               set.add("185820cacfb62e34c1f6c2e1feb42d27");
-               set.add("18981fde366efeca850bdf490253f0ec");
-               set.add("18b7f1dce04bc7838f3d2b234923de27");
-               set.add("18c2d213b8de15fc11ef66f7a7ad04a4");
-               set.add("1914ab609416b8559eeefda814867b9b");
-               set.add("19ae231357c49b6bf9427fa178dc58ab");
-               set.add("19b0b447800ba36f2d4ce76264009e2d");
-               set.add("19c9120e2fb7bbf6d21d71659f77439c");
-               set.add("19c9753bd99d7f0328792a434625f8a5");
-               set.add("1a508ce5b461be1998750bdad26764a3");
-               set.add("1a77681a4646cd21461df84c49074fe3");
-               set.add("1aa169a73004fc66a932576ac2732b15");
-               set.add("1aa1f3cc21a0f6a6eadb6166d468284c");
-               set.add("1ac8dac1b547a064a306bf42e568b5bc");
-               set.add("1af11d2e99f06b69ab5103731592ae8e");
-               set.add("1af30f73640ac1f9f3c8ef32fd04bfb8");
-               set.add("1b337a115a491abfc3abcd62399704d2");
-               set.add("1bb9c002f22ccd24bfcec36957ac0367");
-               set.add("1cbb12c9b58adc33642e1165b77c2e58");
-               set.add("1d30457aa2af0f212a26b9d2c203a216");
-               set.add("1d390d2ede88fb2f77ad7e7432155466");
-               set.add("1d920d4ee2bef0c7ffb28a91b9e325f6");
-               set.add("1e09cd01462e6d4728efafc4a550a5a6");
-               set.add("1e26c7969adb5bfc507da22802f17053");
-               set.add("1e5378337317821ffa4f53e9ecf47fbd");
-               set.add("1e68b1ce7eb224dc65b39546d0892299");
-               set.add("1e757827e2e03a781905e9f983c89933");
-               set.add("1f2564b3f0d78751b4e1d5836d54f7b1");
-               set.add("210bd4536d1c1872d213995420cf9513");
-               set.add("21bdc48d9411ffc8e811e32c45640f58");
-               set.add("21d4e53c3308cf3a1e916ef6cafff873");
-               set.add("21db7fea27e33cbab6fa2984017c241c");
-               set.add("221ab691a72a6f8b65792233b7bdf884");
-               set.add("222b7613b7a9a85d45051fb263b511bf");
-               set.add("224c062a8213f22058c0479c91ce470a");
-               set.add("22777fde378d9610258e4223fb5563f5");
-               set.add("22929b4849129644531a722397786513");
-               set.add("22c31705c3948c39721ced4ca04b2e65");
-               set.add("22e355a9e573b7f6f86c7e0791647ba7");
-               set.add("2320f4b15fb78448ce16a5a625f6f8f2");
-               set.add("234467bcf00a15e7377ceca46b7302f8");
-               set.add("23e140b2126af53487781f63082615e5");
-               set.add("245d147c568faf00dfb47d9c9080871c");
-               set.add("24a5102957c91107a092704f4f166e77");
-               set.add("24b7b0f55cea9329f981f00d922cfe61");
-               set.add("24d9308fa5d88f89365760a6e54f557f");
-               set.add("24fe3f1922a6d16b21f57b9925558296");
-               set.add("2571d40a353d275cdd8a4ba6e80a32fd");
-               set.add("259a0325a52acf54184fd439d1b2521d");
-               set.add("259d90773b3387c58aecb648c2c3812e");
-               set.add("25fd0f44fbbadfb70cee0467f9b53d3e");
-               set.add("26331fa38f2af84b18df5dd1db0244f0");
-               set.add("26a5e7834018943090396d419ca64662");
-               set.add("271f29d0b199d0d3f036e8f99ce76975");
-               set.add("2762f40ffacbc78b4c949cd38101a02a");
-               set.add("2769033a0acfff04e1f427027643c03a");
-               set.add("27b1601bb3a33c7cd2745caa651f0705");
-               set.add("27e522bd25f54343584ae89e90e64ee3");
-               set.add("2815e68ed1683663820682c8e00fd795");
-               set.add("285e598a676de414661a022f72967d29");
-               set.add("2886ee93f5dd4f02b331089928520e4f");
-               set.add("28f53f74ab45da2ab83072143f0d01d0");
-               set.add("2967cd7a160b396ef96f09695429d8e9");
-               set.add("29e99fbfab8c9771f4b5a86195db0c46");
-               set.add("2a1f5f5a829badfd64e2c20cd17bd38b");
-               set.add("2a941643d418880e0e7337aaaa00c555");
-               set.add("2a9d2a64b4601046774c9d27202de593");
-               set.add("2ad8de03de84415f1397cb2d4c77fb84");
-               set.add("2af7bcae566ada617d8888f34a5f70a3");
-               set.add("2bb2cea5465ab43f9b7e83cb44851223");
-               set.add("2bc22736450a8d0efb8d898bdcf52d43");
-               set.add("2c19c0cd4c005877798821dd65a2ff2e");
-               set.add("2c39985a5a49fa07759dc880e3722203");
-               set.add("2c58d5382b8d5fdbe1800e013f200f38");
-               set.add("2c8f6954ba9842ad9fc9bb367d14cf72");
-               set.add("2d13c151bbf6e1d7d7378c86d191d2d8");
-               set.add("2df4ee3f8a2c3611b267936e47bd3d3f");
-               set.add("2e6c8ecf50ee9ea82f407a8b0acd4f85");
-               set.add("2e97a2f015b1247b01b5e022bf6109cc");
-               set.add("2eae476e3eb97e2a1ad54c5b8fa48208");
-               set.add("2f44b9347e986c91ab886dc2e508885f");
-               set.add("2f478d2efa82571d5c3e49fde16c936e");
-               set.add("2f7460de6e7581a6775f739f894d86c6");
-               set.add("2fa429a16950f6c3f19a051b3417aac7");
-               set.add("2fa4545430dae966dce186984c98d0b7");
-               set.add("3027d63763f7aca58b41d52689f38dbd");
-               set.add("302b34ea5ec261fd74a4901d041d3f82");
-               set.add("30b5952157345beb00d753425a728757");
-               set.add("3136fef31b6d0e1d9a0dbbbdac05b0a3");
-               set.add("321377ccf346be4efa1fb8658032298a");
-               set.add("325e3898dc252f6c936301412be06505");
-               set.add("32fe6eecb5e97a6ff9c4f1c005857435");
-               set.add("33197b8e7194af401da6150c68004d7b");
-               set.add("3393a92e46a045c4eaf6b9e18f7044e3");
-               set.add("33a89133876e91fccc4058627b34d617");
-               set.add("3466c5940034ddb1371c4f86dabce964");
-               set.add("348abf304c63a702e4a229db28feee16");
-               set.add("349260e7bc0291ba2e4c26d4db00bee9");
-               set.add("3507c7d2b11a693620235ea3872dce66");
-               set.add("353236b8cb07eef73d80f25e240acddb");
-               set.add("35aeed248d254fbc3542b5cd3aa9842d");
-               set.add("36218bbb51e99aed53ea822ebaa2c873");
-               set.add("3666b06f839465adc5d36a6e75066a47");
-               set.add("36fb9fb79c253ee61e498e459f0cf395");
-               set.add("3703dd15904a118a05d771e7ee6e3f11");
-               set.add("370b98cc77577db1a07021e46c21cd3b");
-               set.add("3719475cc57cf3b5312f21b1efd228ef");
-               set.add("3738564e4327367bc2f359cdbb442304");
-               set.add("37bf1e76b05f333eefc0495e4f725838");
-               set.add("38715f11bf91b5ce06494e1ddd94c444");
-               set.add("387eea945f83c9567fa42c6e150b7ba9");
-               set.add("389687548b9f05e6c99d93a2ecf76307");
-               set.add("38b1e93cc1910ecc5301502fbb9bd8a3");
-               set.add("3a0b2ffd2d4593581c52bdc1094d92d8");
-               set.add("3a99a5318497e7108995a08675fa70d5");
-               set.add("3b4573f1f11db1ffedd14e10d539aad3");
-               set.add("3bc526028cf0be42fcbb75936810d41c");
-               set.add("3bc5834ec0726b10465b67f17b77044e");
-               set.add("3bf858e6c91e0292259a886b8bf793c3");
-               set.add("3c4eea57e65806dc59dd4c206bef79e1");
-               set.add("3c7b9e1836fe07f7a4ffaea90e7f33fc");
-               set.add("3c8aee818229c48b9a882caa6de58c18");
-               set.add("3cf831629486be08e747671a14d113f5");
-               set.add("3d6b990aaee7dff0be939343711dfa74");
-               set.add("3e2d355d7fd39e54ceead835d14df7e9");
-               set.add("3e8697fe46496d41528387e2d37d734a");
-               set.add("3ea538f54677ecaffbed1ae5f2e12d28");
-               set.add("3f654d824783b4392396b34ad2b44974");
-               set.add("3fc4889ea40aea23fedc994704ba4708");
-               set.add("41145e8204991f0b644b831cd859c4e2");
-               set.add("415fecbed32f8de16ffbab8e97edb4cb");
-               set.add("41633604778611785d7453c23823b0b3");
-               set.add("41d37971a99bb08d0f5f4fdcfcd87e8d");
-               set.add("428c0aeb520fe9e77d3b464543620716");
-               set.add("42cc2865a6fc399e689d2d569c58de2a");
-               set.add("43a6db581840e3645459ce51953ca9a5");
-               set.add("43a72eab1f3f874da7d68092e83357ec");
-               set.add("44255564acd68eca32ffab8e6130c5cc");
-               set.add("4448ff245bfd8d2606b418f33797571f");
-               set.add("44a4e02e520657221706cd6d69bcfb13");
-               set.add("44b12361fee8a2385a9b90e44fd079f3");
-               set.add("44b7c1c17e8e4728fadeecb6ba797af0");
-               set.add("44d734a18b45937c3035a047f9063dfd");
-               set.add("44edf41dd7624a6e2259d8e451622527");
-               set.add("4528bda7194c6dfafada95d68c2faa3a");
-               set.add("45a8a995a3614f823c04f3c157effe97");
-               set.add("45d2f014e70681483d6bc5864cf94b20");
-               set.add("46232174087bfb178ad7cc35bfb387a8");
-               set.add("46401106d96b729d330375a63e655374");
-               set.add("46ac2356b12ed7519ae2dd5f199b7c10");
-               set.add("4790684e33d48e3dfe99b6ff7712be8a");
-               set.add("479a2848353fef692063ec37e7d556dc");
-               set.add("47a649cae70a90e7d1ae2b2ab10465f0");
-               set.add("47bc150e2585e61cf9380ed540de4465");
-               set.add("4863872b48ecad3005e7b60d114c0fde");
-               set.add("487c3163ebf25cd3b4479e13e30cba5b");
-               set.add("48c5d84e56a982689f4268ed9b50cded");
-               set.add("493a84bde424f5946290238998d64873");
-               set.add("499e8c7c38dd4d8068eefc4eb58d4cf5");
-               set.add("4a03d963b887b4ade3d574e87d111e9d");
-               set.add("4a5509929d6991507c6e136178942a2d");
-               set.add("4a933f8824eba082555515e69d3bfe43");
-               set.add("4abc93cb926e33fbb97aa0d2ffe7885a");
-               set.add("4ad536d6aee9fffe1e84c9e75698f5cf");
-               set.add("4af14f94870a2e3d47dbd78cc05e58a8");
-               set.add("4b0a7961ee650f518533f03c38ca8320");
-               set.add("4b166cec69dc9ace3a9f598674c35b3c");
-               set.add("4b5a632e55f4dbea5435e151145337a7");
-               set.add("4b797a7d23faae4daf8b2946d6cb24dd");
-               set.add("4b9e8ea91d6bd67b22be67dd40b871a7");
-               set.add("4bd7e46dd429e45ddee5736f86d494cc");
-               set.add("4beec7064114b6e49cc76cc2f71594ec");
-               set.add("4c3f47c263ea5b933ac5184466573f6d");
-               set.add("4c9b11094fa43b8a9aaf1a1568bf60c2");
-               set.add("4ca44906c21909f396774827017e007e");
-               set.add("4ca7dd633f56f76f794faf958416f4c1");
-               set.add("4d6956c8d3db98528dfbdafa4a3316b6");
-               set.add("4d84b18416836b7297b990a408a6eda3");
-               set.add("4e13b8d5d4a77393b2fbfbaebe9ea1ca");
-               set.add("4e3b029d124b898f1d11a8d15d2a6688");
-               set.add("4e9723863a06235d9339cd00912871ed");
-               set.add("4efdf67cd98424e7cb008dd09b169942");
-               set.add("4f25dd1fcb4aedb512f24758961d87f9");
-               set.add("4f86907e557c00d13b42a2393b834d8d");
-               set.add("4fdb3ba6ebc9b3e9ab133c15312f995a");
-               set.add("504bbb5991ad94728e6b73c6ddc6c476");
-               set.add("515f449c1e9fd279dbdadf3cc38fd008");
-               set.add("51d9a0c78486de462af6a490acea1fcb");
-               set.add("52032bb8c1acb8bf7320aa73babd2e50");
-               set.add("5203feb9b0d422a38052d9df4103e3ab");
-               set.add("5222c37a7e8618d4cb43ce8c4a188129");
-               set.add("52731882ea73ad5b1d39c25e2969d9aa");
-               set.add("536af35745c20e4ee25486a31c2fb57c");
-               set.add("5379086fb93464cbdad4459101ed4d07");
-               set.add("542f3505366c2d6575e73064aacf536a");
-               set.add("54350b63fafc31af32bdf49cf0bbfda2");
-               set.add("5498ead583ab6cd6900a533b1cb69df8");
-               set.add("553eb9e396b2f304f963b717bb3a9059");
-               set.add("55c5181d0e1b170cfd05c3a9271b3bc6");
-               set.add("566ff170b185c5cfd893030c97080451");
-               set.add("568c906117202c4d4451dfb3be697921");
-               set.add("56a9926b91222c8943640da0b642d617");
-               set.add("56fcddb2fc61ab068e1ce98a226fd34d");
-               set.add("573f9b1aa16e771da95df44fe3a62167");
-               set.add("5805ae3e1c5fa9f7a604152c40c9d06d");
-               set.add("5844ffd995e179e21476fe41a72c7e85");
-               set.add("5866a0ca3348c1b406e4a4a869b183ae");
-               set.add("5922a04c19e52d4a3844b61b559a89d4");
-               set.add("5957b399b3380e097b70cfc09bae1bd3");
-               set.add("59785d3feccf91b7a9fcd96fe5e686de");
-               set.add("59cc15fde8f2bab7beac6a9542662df3");
-               set.add("59ef8fd572ad56b7c00092f185512c0a");
-               set.add("5a26e5d6effb9634941bbdaecf1cc4ce");
-               set.add("5a94fedb054c29331d22c4442ad619a6");
-               set.add("5b1a41ab325cdfb925f500868f77e058");
-               set.add("5b20fd5088ed40d65a52c38fbe314373");
-               set.add("5b3510c0aa53405e1fbd7a67b3af34fd");
-               set.add("5b96ce711afb37fb511e70ac12cb717f");
-               set.add("5bb8c694f0d7e39aceaa7fe7a885a6e1");
-               set.add("5bc7dae98ed248bc85f4782783c7a383");
-               set.add("5c1e091a898470db28aaddc968071a00");
-               set.add("5c603c37c8ae3b7441a49bfdd93a2426");
-               set.add("5ca4eac1f0b43f03288c19c42e1ccb2b");
-               set.add("5ced682df2330890f2239e8da8d33061");
-               set.add("5d437ac21a6da33b77c980abef5af0ac");
-               set.add("5d4f136bcd4f5f71e0402819f2f666ea");
-               set.add("5d9d43530d78a07475063de4f6b82578");
-               set.add("5e8973f53dfe0e36537e7d88ac84cfaa");
-               set.add("5e8b973df438112549cbd73b38240094");
-               set.add("5ec17176ac8ca3ffe5c7707d4b33aba0");
-               set.add("5ecdf016b2374b2029c58dce498548cf");
-               set.add("5f4d8576e9299aecd4ece33f8b4ffb3d");
-               set.add("5f5bc13ecb72cde7c4c6e89279e836f0");
-               set.add("5fc7b23ca79086fde585ac92b8ddfa61");
-               set.add("5fc8dfad0c6503b16fcbdaf2140f5bd6");
-               set.add("610b21fa92e08d26b0ebbd27ac406558");
-               set.add("618c82b1f690b74209a68062f0b7f50e");
-               set.add("6214548d7b510154960ca3d23da4f38d");
-               set.add("6244a9533207c9f3d89bd48d2c946d72");
-               set.add("628475d3f98ce21920983b2335d29771");
-               set.add("62b5f08d8f9087672b45485f5177b688");
-               set.add("62c0ca2c1be43447418c673a27f69a53");
-               set.add("62dd2d23b56d1991111028754f7d5718");
-               set.add("62fe634a6ec154d4b675a8944ab98a7b");
-               set.add("638e84fef470490263300ed27293aca9");
-               set.add("643181e6ca3418a86b5dac6858805839");
-               set.add("6431d2ee351952b0ca1c8e24aee89d9a");
-               set.add("64cf9d529b625818f06f910fd5a51ebc");
-               set.add("64f5901476b159bd9c4f5ed9aa2b4cc7");
-               set.add("651c5d94aa8b742ea6bf89eb4760d88b");
-               set.add("6535664e59493ee6581d3ec49d666a05");
-               set.add("659bd0331a1348d14e9cd72006008d5b");
-               set.add("659d8f3f58c60862ec21306475d5b86c");
-               set.add("65ed980ed9e129e301e3c219be11999c");
-               set.add("661c50f934f8b021101df91c063c2662");
-               set.add("66289df1c8d41414638696d9847188a7");
-               set.add("667f3707995e365e210a1bb9e1926455");
-               set.add("66ff6174d6a5b1c8a40637c8a3a8a7b9");
-               set.add("673d52c40a3153d07e7a81ad3bf2027c");
-               set.add("67c803799e8e1d877eb3f713dc775444");
-               set.add("680708ce5f383f0c7511eb3d7b7209d9");
-               set.add("68ee06fe147e143b6e1486d292fbc9b4");
-               set.add("690da28e475ebd2dec079e7de4c60719");
-               set.add("693db94b6ffb0c1afa7b82499d17b25f");
-               set.add("6961f9a08f066d0318669a9c9c94017d");
-               set.add("69a38fb26f994ccc04e84e66441e0287");
-               set.add("69f5b82d6acf05cee8615ff5d05f7921");
-               set.add("6a1e040ce59176bcbe4c47654dcf83a7");
-               set.add("6a26a773a6223c79697e12838582f122");
-               set.add("6a6e0e4350ef8d4a3489aa4398bd674b");
-               set.add("6a8abe4a6fe236bf93d9b85681db2c0e");
-               set.add("6aaddb50ae45f1006852479932dfbd54");
-               set.add("6adb0778b8930a8e6a2f1e99de2ef074");
-               set.add("6b54ec7203070bb29e514eb7d684e380");
-               set.add("6b598530a066271059bc94c1fa0cd7a1");
-               set.add("6be4f1c5af0ff30131481d009e87133b");
-               set.add("6be8cb8a938f1ecef2b36c823e8e6ade");
-               set.add("6bfe9b78813cfa4014e630454f7a06a5");
-               set.add("6cb4d52135f005a2c7ba8ccc3b8781e3");
-               set.add("6cd5f8dd36648fcafcfecc8d5b990e9b");
-               set.add("6cfdb07efc0d3c1f36b2d992263253f9");
-               set.add("6d95c9c12fe5498af055b013bf7ceb7d");
-               set.add("6e8f160f1b2b54c3c89b81c4f9a98283");
-               set.add("6eadec5ff4cb05c8ef1a64d2c94d627b");
-               set.add("6eceba3c0a19666f5a9adbc13ceb1ae7");
-               set.add("6f47bff8d62f5bd51cee156f78e8cfcb");
-               set.add("6f484725ba3abcadfe8fbfb4e6e83db6");
-               set.add("7031b89c62590b6a41e7ad524bb0a308");
-               set.add("7058fc792efe7aaddf8a0323bf998f72");
-               set.add("706e502b5a6db25407c2565457944633");
-               set.add("70cfd491765d3e4e6a4e4b1ccaf9c343");
-               set.add("711e7a11c4be36563cae1b71234dc414");
-               set.add("71794c9ad0e60b6d0dcd44b51c3704f0");
-               set.add("7193a4c6f286f7b86b18cc7908498d06");
-               set.add("71b17eeb05fd473e42aa5c4e51e47a15");
-               set.add("71e0014aeaebda1113a12cecb51fd20c");
-               set.add("71e2d06eaa0ab3ae59d0f7b7ef20fc31");
-               set.add("71fe7c7f2a54587c2278b3e630faee56");
-               set.add("729ba9dde60740e6d5e8140fae54f4c6");
-               set.add("72b5be92417a4a6a09f5e01c106cf96a");
-               set.add("72c0c6f5a653bb57a1aba44e7edb202b");
-               set.add("72f6580c0aa3b101acffce59adf7809b");
-               set.add("730ac94082f25931179a313af186b335");
-               set.add("73243d82b8157406558562b5eb70818b");
-               set.add("73371ae751751a5998c3bc8de577b83e");
-               set.add("733c6d4df3b6781473ba0a3a169ca74a");
-               set.add("7376d2568492e6f6c0fadab86e22416b");
-               set.add("737b791d27930ccba061fa36c4046208");
-               set.add("73a47e531e9c0ddf5a334c40508f6361");
-               set.add("73b2859aedfe1bf317841bbc150e0491");
-               set.add("7413c1de6d5f286055e8244101da306c");
-               set.add("741bfaabd99301c5503fd36d17912627");
-               set.add("7423f1b74c8367863a1071bcd0864d42");
-               set.add("747ddec4bc30cbde2ebefac7b8df466c");
-               set.add("7494df6c5102bbfb6e48c9b30281325b");
-               set.add("74c5cb4f540e2c9b27ae60dcc8247eae");
-               set.add("74f6a218f8877fb76f74eacc9df32fc6");
-               set.add("751ff3c7f3e77c8d3ba24c568fd11845");
-               set.add("757c05f3194d6d4b848b83c0e3e5f1a3");
-               set.add("75f724e20c3f2e26d2de13927fbf78f1");
-               set.add("761c6d190a411231fccfeef67f08eacf");
-               set.add("763eddbcb1f6787b3350f65c64b44ba4");
-               set.add("765a1a5d8f09ddffec749d3a6920c4a7");
-               set.add("76acd0d7e4d38152771480bedacba209");
-               set.add("76dc5d4b4f65aacb1dfc1a5a8f61b023");
-               set.add("771cc4503662f2fc2c5f15f46e7e58b6");
-               set.add("772a3193a3cf3e18fd2058c3f45c35f8");
-               set.add("7823311e8d60374d4b37c50d927507c8");
-               set.add("78282abebd03a40f2dd21f1505a5f789");
-               set.add("782e12a60bbef943f13f2fa1af1e39f1");
-               set.add("782e6df5b10a60b26117e0648e76c6c4");
-               set.add("7875a865fbaf33ba617cdb7c7f0f7140");
-               set.add("78ee37b2f7acb3d892e54f0e9d2e0c14");
-               set.add("791f7d21ea119ccd49df31b2f614a0d6");
-               set.add("795036eafd006f62ee5a68ba1c309865");
-               set.add("795fcaf2d6d9e1c37a4c283876f26cec");
-               set.add("79688fa15c424b73454d9cd0a070472f");
-               set.add("796c759040e72b8efd4630754bd3f30b");
-               set.add("798ad9ae186a9d89e6f69e065bc22a86");
-               set.add("7a2a604d923e5bd87846902e47acc438");
-               set.add("7a39ea82b6b2bb75f9f6a7b817dab9cb");
-               set.add("7a62872422cf100af636b434f4a0b307");
-               set.add("7acda66f5d077fa708de7d153882b97c");
-               set.add("7b1aca3caab3a61147d4ebf5f7971d42");
-               set.add("7b5bc0bfd0de5126b4d278aa5775abd7");
-               set.add("7bd0735d3b5d579f0c97e11309a12451");
-               set.add("7be2fb055d29d5c8e42c559295ee8113");
-               set.add("7c14e11e0126b310217db2488f898127");
-               set.add("7c4ab23d9b1db15ea1f49fe017edf346");
-               set.add("7c6080928783078289d9a473efecc134");
-               set.add("7ccde35451846731eff4ae16e40f661f");
-               set.add("7cce66eec1313c11f5b9005db8f2823d");
-               set.add("7d40723bc0200f13675626309559ce6d");
-               set.add("7da7fa494528cd0836f9988f3e7ada96");
-               set.add("7e3bc2bc33f34ad791573e94021381d5");
-               set.add("7fb1e485fa41841779a0a1f95a2b7cd8");
-               set.add("809b63d7a153ee60272ffc224766fd72");
-               set.add("80fc9ff72737286ad64fe7de1524c799");
-               set.add("82b602bacfe681cee58d5530ac9e8b99");
-               set.add("82f69b66499f2bc375467ee933fe4577");
-               set.add("83243e87941f4ec7f8841571dd90b3b2");
-               set.add("836481fe9bfd7612506e0545bdcf279d");
-               set.add("83a498353a59dea68538962eb2642ba8");
-               set.add("83eafb190276935630f43cddf3d78143");
-               set.add("845c54809778f5b838e32443a7d44072");
-               set.add("849b5885cbf965447675610ee1d0dca2");
-               set.add("84a895acdcd487244b6803082036fad7");
-               set.add("84bdf63a67691194e37082e3f7f6d712");
-               set.add("84c99be383e4ada00f4e6bd335774655");
-               set.add("84ed2fb163b5b2525a9a731056ffd144");
-               set.add("8517e14d6f657f1244c0344d4f1a828b");
-               set.add("8541aca6dd06f2dc6984d5e1b664900c");
-               set.add("85cc38b178bd721bf7225144dd301b0f");
-               set.add("85d00ae1ce88ace2bc6918750a97502f");
-               set.add("868af0eab556081846fdbff18df40b28");
-               set.add("871f7fe309f61ec7e45e4b29833349d9");
-               set.add("878e7848ab58bf9271fc04766e969c8f");
-               set.add("87b872efe9433147c61d5d2c3dcca14f");
-               set.add("87cd3518578a2ef76341b33f9c95198f");
-               set.add("87cd3a0a86f398ba1026cdb969e55090");
-               set.add("87cdeb3fcaa050209091a1600ce1df11");
-               set.add("88008ed2e9b600fa2e453390688aaa7e");
-               set.add("8833c25743e0f9725ca22dbc6e54d1bf");
-               set.add("88693556ff80aacd354c1948675c0674");
-               set.add("888664c26a3296f9571d561723b44255");
-               set.add("88ed07b96422ec99767fb35bf6a51966");
-               set.add("88ed43ef6f483b9a7e34c34b46335dea");
-               set.add("8a2e4445364c3c9151dcf4622c6add51");
-               set.add("8a73ce2e18dacf250a961dac49338407");
-               set.add("8ba75b207cc0bee8ec624e9f33019161");
-               set.add("8bc592cc7aaa336637b9c82c43cbb081");
-               set.add("8c1bdef25d6a6df69c303be204748da9");
-               set.add("8c8b182ec0845de4a5fed3245e5601ea");
-               set.add("8c8d724fba729940b1234e58e64125b8");
-               set.add("8ce47ac01efd8c0ab75ae5371ff0f7ba");
-               set.add("8e1600a04363c86199d660ccb7085735");
-               set.add("8eb548ee8bf98a6426f0b5a11e32c88a");
-               set.add("8ec54a8bd1ab30f324eb0f03ef79d097");
-               set.add("8ede1653debc5617deae6a7632c18502");
-               set.add("903594c774fd5be01132f559c00778b4");
-               set.add("9079d8f7488bca4504d58be0bc76deea");
-               set.add("909a1f7458c8f1f1138dff9ce019fb6c");
-               set.add("90b8dd2817509c2374b20a1975ca1a54");
-               set.add("90d0f3d40769a6219608964628d40e55");
-               set.add("9104737f888d85480d0cc9aef8587e77");
-               set.add("9118a19b2abc5d1d624b10f2bceb18bb");
-               set.add("912e499f9a4a55f11133e01b33542ad1");
-               set.add("915fcc373ba5d8a13814f236c1a9e4e5");
-               set.add("918ca652867678984ae1149a3b5467bd");
-               set.add("91fbebd600bbd88370994b739ae8e1f8");
-               set.add("92fc949a982c897ca4a17750e2ee4afd");
-               set.add("93c0446ee508efe75a176035986627cc");
-               set.add("93d4329e22ed50d3502b2d0bc117baa6");
-               set.add("93f33bcfa6201057376a3fe53eb29959");
-               set.add("944b74b5ff9c617312ca2b0868e8cbc2");
-               set.add("94bacf4caccc653a74a68698be0da4bc");
-               set.add("9572f2ed73f01766b0ede9ec3d52715a");
-               set.add("965e3d6087eec8e991175aada15a550a");
-               set.add("967119411833b80c9ea55f0e64dacad6");
-               set.add("968c5025a59e782d96682b09c4e94746");
-               set.add("97824aa7746b63a32ea6d0dedb3e3f84");
-               set.add("97aa914f28281f67ae3ac5222566c2a0");
-               set.add("97f5a198489144a2f306456c1a346f9b");
-               set.add("98a7e979d454d7f46ceb4a4283794d3c");
-               set.add("98ff8ee9107e864d7c52d631070fff3b");
-               set.add("993739fad4a47f34eb65e3ee48d15c09");
-               set.add("99bb411f89eb34ebfa59900de36581fc");
-               set.add("9a13940746bcf4cbe210150833b2a58b");
-               set.add("9a3d7af6ccb7d277e3ed582d8932b5db");
-               set.add("9a76e86b4275099983c5ede78626e0dd");
-               set.add("9a9caad4a9c674daf41b5cb616092501");
-               set.add("9ae6b0ad5010301ea610f49e008adf8c");
-               set.add("9b6033bd4470408ecf2949d88531d6a1");
-               set.add("9bfc7853ff00c7ea0e2f8972dc2595d4");
-               set.add("9c8bdd485912f9d9eaaba3d5872be726");
-               set.add("9cba07b76b4e79c0265deda5df2e2381");
-               set.add("9e082b9bb6c1361965c0f0e06f051acb");
-               set.add("9e24dbadcadc67447af65d571ffaee55");
-               set.add("9e6a5f03a8b524ffa3264a3f32818e1c");
-               set.add("9ead837b9e4f8c922f74ddbff0d2b88a");
-               set.add("9fb7aa659c0475d5dc72bb35567247c9");
-               set.add("a0006978c9a542518b425c0caa67042b");
-               set.add("a01bdd6575c3cad9f9a4cb8aac9c716a");
-               set.add("a02500e28eeb7e56e343607a822e2a7e");
-               set.add("a05c1799e061712459e6c46f533263a6");
-               set.add("a0799831bfb3f9b77b63c03fad39cce0");
-               set.add("a0d4911294ccb20c0920a3cc6705f326");
-               set.add("a11dfa1b02b1671d42b1abc858de2f2e");
-               set.add("a11e237bd6d3c4a4ee8a7ee791444ad3");
-               set.add("a148d83d50cf0852f6c08ceacbea0296");
-               set.add("a1d8b81c03860585fb40613e828c1b2e");
-               set.add("a20c867fdbb93bbe1d1231d9a8ea76c5");
-               set.add("a21e0795fe0977d50a4496ba60e685e1");
-               set.add("a260bb11468a2252a8dedff81f5672fd");
-               set.add("a2b01bf43bc1d403e6ab3a9b71f32484");
-               set.add("a2c15ded3e16d9aa12b9c07f3383c098");
-               set.add("a360659a62e2e34f4edc89ce5e8b8a0c");
-               set.add("a3a985e0ae5a9c96c74b8ee647101439");
-               set.add("a3bf05e31288667a43b4c39cc0604c97");
-               set.add("a427397e35b28706c5b8aa50e8f92a1c");
-               set.add("a432e1b27b7d9814368d8f4071cf2dd0");
-               set.add("a4b4800082feb6fcaf1cd56dda3a28c6");
-               set.add("a4b83742cb45f1dd9130250cd72c460e");
-               set.add("a5a8b20a222bd51b707e50303fdae33a");
-               set.add("a5cf16d12d611ddc5ae1b010083690ad");
-               set.add("a67b1720a7f123bb943c3f1ee74b8f00");
-               set.add("a6b31c2e971254f85e231653abdc3a06");
-               set.add("a6f9fe8c867cbef07730db4d1d461960");
-               set.add("a706de20cf1a31e379d98807d1cb2060");
-               set.add("a7b5467023706646a6b8ce2602bba226");
-               set.add("a7bb7f7f68b538fb778856d4fbda50b7");
-               set.add("a7fee39f2151056370c57d383e486348");
-               set.add("a84a5f90f1083075081f135c74314bff");
-               set.add("a8a6b73342c6a92638e41b86e6838958");
-               set.add("a8f1c8b28c017186778e3074637e52ef");
-               set.add("a90e513d9b2d0f55b875c3229e2d9816");
-               set.add("a9e697026e08d1a8765497a9569b04e6");
-               set.add("aa3218984177ce38bfdf06e38fbaa64b");
-               set.add("aaa0291aa11c304b3a2300d4025db74d");
-               set.add("aad63a3685d9f906a7c6c8716d626c0b");
-               set.add("aafee591c7a3ae5f3c4f44f2d0f8a70f");
-               set.add("ab85503c9acb730fcb9ed2a4dd74e2d7");
-               set.add("ab8ad454409604808d1b444b068e602d");
-               set.add("ac4c8af4d29676c8c79ac9ef180fc5df");
-               set.add("ac4cd34387b04786cc5315b464006ec8");
-               set.add("ac9c443698ac77bcb3a73a959f6ca0f0");
-               set.add("acde934989eba2c7fef7cce095ce85c7");
-               set.add("ad053830e5d0bb7e736ab98a8f3f1612");
-               set.add("ad08d0d2d84298deb08b4c4a1cf62f39");
-               set.add("ad0a1f2424a1b831f9777e638e8f829a");
-               set.add("add039636134cb123908df5053304f3e");
-               set.add("adf89cbcb01a2ec6d4afb24914790a67");
-               set.add("ae1ae7c31f46325ce6a28104fa7070e6");
-               set.add("af8c83664fd6eec8089ef1992aec463f");
-               set.add("afcd59e32572ecb7ebe2d9c993d5fa9d");
-               set.add("b012115b4276791c5049dace174933f7");
-               set.add("b218489d2d4d7ddbfee2f073e238ff30");
-               set.add("b251290e1d8690953d9cc0c1ea3bac6f");
-               set.add("b2843a551894de318b80f790565dcfe3");
-               set.add("b2a414aeb8800edfa8945863ffa5fbc9");
-               set.add("b2d68ad2619bbb15a827b7baca6677b0");
-               set.add("b2fe203ee319ae28b9ccdad26a8f21de");
-               set.add("b33afd95fbd9aae903bbe7cb853cbbf3");
-               set.add("b385f0f86168fea4f5f456b7700a8ffe");
-               set.add("b3bd462a51847d58ed348f17c8718dca");
-               set.add("b3d1befe2272f354b85f0ca5a3162dc8");
-               set.add("b3f50d0da11772487101b44ae8aeb4ac");
-               set.add("b42625f51295803ae1f99daf241c0bd0");
-               set.add("b49cdae29a3394a25058e94b4eb5573c");
-               set.add("b4ce8f683ec172aecf22cf8e516cce05");
-               set.add("b4ffd04e41c1b8757149777a894f33f2");
-               set.add("b5a1510fcf6dd596e87f497bfd5317bb");
-               set.add("b5a75d8c18db0a96a3423e06554122c8");
-               set.add("b5d312d32267bd15ee43f36077feefe9");
-               set.add("b6645bb07f58754b8838d54e24562c06");
-               set.add("b69831350ae6a3dfc18c0c05db0c25a8");
-               set.add("b6b70e569be8de2fdecf285730319735");
-               set.add("b6ee0ea7d82d3d7e0ab8bc62057c0385");
-               set.add("b707a076a44ca9b3e6b8dc1dcde7d877");
-               set.add("b77df6081bbeb4da02c075befb4beb9b");
-               set.add("b7bdcedd416cccc742643e8e244f6282");
-               set.add("b7ea4565381c6dc534cf0af8305f27ac");
-               set.add("b7f3fb01d8c41525b103fc5faba23362");
-               set.add("b80bf674f28284a3614596800ec02b3a");
-               set.add("b81ab08e53854aba9462ebbaee1ff248");
-               set.add("b87e12381d354360f7945299ad92a4d2");
-               set.add("b8bd5737f81fddbaf120ebe43a0010e4");
-               set.add("b92f1e45fdb62c1fd6d7f3e774b4b610");
-               set.add("b9769bfc0d93a570d94fa130de750b1f");
-               set.add("b980c7a501ce63ebb6e20b04348298b7");
-               set.add("b9e4b006db3e1597b21fb7aba13f79c2");
-               set.add("ba031cf2febc03ddbff278200dca45a0");
-               set.add("bb0f54037f5ab70e96c7d8ba7f57ca4b");
-               set.add("bb2eb6b3f7383d7ef521409fa7710385");
-               set.add("bb3eb6a5dbe0582b31f35e5dc2b457a7");
-               set.add("bbc22cc7f6851e06fadfac40a6225419");
-               set.add("bc4d886813fe2eba9ccd7bef0b4d05ca");
-               set.add("bc8162e261658ece13f8bc61aa43ab74");
-               set.add("bc89ec14f4629c3afe1e286b07e588f6");
-               set.add("bccdb576cb50b44ae34806c2a2781c52");
-               set.add("bd10772f1554ccd6e245d6869d84efe8");
-               set.add("bd969e90ff4b1362e2f24553124a66cc");
-               set.add("bde145f138ed13399b8a747da20c1826");
-               set.add("be11a726b56813c4b1aea0574c8302b2");
-               set.add("be1cfa6a82eb4fbf7186efd6ddbb0161");
-               set.add("be5f3dcf0badef84475741cc47e2ddc0");
-               set.add("bf316e6ad7e72f7dc4033207dd033c76");
-               set.add("bfadbf9c4cde6071e54e43a5da64aca9");
-               set.add("c029503ea85e7a7742d826bc184d65ce");
-               set.add("c049499ca03fd69115352e5d4be72de7");
-               set.add("c0524ddd036991b4718f6ab0ab4e4f42");
-               set.add("c056cf25df6751f7bb8a94bc4f64750f");
-               set.add("c0a9c2fd4f0ed2b8f2bdc7f2c0a7a7ce");
-               set.add("c165e480073bcdccb3fad1c5e507159f");
-               set.add("c24ac1ab205eb3fbd1c64841f0f288d6");
-               set.add("c26987c1c7e95810bbb6f2e284861494");
-               set.add("c295b3b2493aff880daac1532a063b72");
-               set.add("c2b18390691697037d5698b683eee361");
-               set.add("c2cd680e3032ce3a70d3bffdb7d0582f");
-               set.add("c2defcfb93d217c4be28aa27ec62978b");
-               set.add("c332adf9d689dcbbb38fead7098781b3");
-               set.add("c4d8f1baafe99501b0d80e8a9c8c3086");
-               set.add("c4e5ca3e96b219539e3e61c3c4fbe5a9");
-               set.add("c5e1448d1fb24ebcef161ee65f21a725");
-               set.add("c60db0ccfc2a22a152f7470505eef8d3");
-               set.add("c65e2561352e75a66b5738268b1d126a");
-               set.add("c69c01ed9b781941561c3a9dcfacf7ca");
-               set.add("c76bb0011d2519fc9e3af85de593e8a9");
-               set.add("c7a946bb164a3f642e4c5f1b7af337f1");
-               set.add("c833820441cbbf28a25d1ea7934ad6f8");
-               set.add("c8762972b9325b7ec040c782aa9414d0");
-               set.add("c8b1563c45f4fd4dc8ba5fafd5c566d2");
-               set.add("c8f2a5a0533de5eae8d1d01da8fcfc1c");
-               set.add("c94045226f625ab9a507552f64892fbe");
-               set.add("ca365baface31f6167328e65a0aec03b");
-               set.add("ca3dc74a6eb57042ea911afa05b1021b");
-               set.add("ca5b57fca35c5bfa4281802b13381d0c");
-               set.add("cab05efb1584bddbc5e4f064c1628a13");
-               set.add("cad2db4a8a73a867a6cdacceec4044ac");
-               set.add("cb6b65a06bbb9ba5536936188a08d836");
-               set.add("cba49b7c8d1982635866a32956861df3");
-               set.add("cbd94e882bdb84ec79ea2bebc1eb4aed");
-               set.add("cc49ecf163d383488b44dbb17dd5b4d9");
-               set.add("cc4bcc37de4d7bf87acea95ac914997e");
-               set.add("cc9300ecd7f2799c750ca3efcde7ce20");
-               set.add("ccccf43f691ed8bb94ac27d3eab98659");
-               set.add("cd0d8952d3e5742f4bf62195e4b385ec");
-               set.add("cd57964f0a86f3c9afbcca44733081d2");
-               set.add("cd80d4f5366cd31ae31e377c636a773a");
-               set.add("cd987a30667f7ff04a5852fd3f15fe3b");
-               set.add("ce16d46a26c771b1adbff14cc7272bf2");
-               set.add("ce43a9cf2d81a1e09237ed2279ca04be");
-               set.add("ce7a500ffd8f5925bea7a48401fae95e");
-               set.add("cf3894401e317e2545d0ae284802291f");
-               set.add("cf779cafecefb6bae5137bb77582e7e2");
-               set.add("cfdbc0be3829a6d55a5403ad92c77bcf");
-               set.add("cff24b5ef480cd58324e927e4ba0ed37");
-               set.add("d05a83622e817871306a3878a9d867e9");
-               set.add("d060e6662cda17c2c523c74963d2d556");
-               set.add("d0b058971d8a20e840de043c68c140b1");
-               set.add("d0c122a8a62cb5728f1423816b26c49f");
-               set.add("d141ed3ad5b33d6f92c976ad8d518b3b");
-               set.add("d15c7bdb5fc7b9316d1cc60a85acdc64");
-               set.add("d1c8ba5392f01f13dfef28e4ecd11cc2");
-               set.add("d1e661c0bfe1c23ca4e91cfa0a40a9d3");
-               set.add("d25a8aef0d42d7a31783e3da52dd4ee8");
-               set.add("d26ef38d4ea39ba85171f444a8947443");
-               set.add("d30b7d8663c7852f2be21d886b79a6eb");
-               set.add("d315c862f290b99466e3d406d3104413");
-               set.add("d4007ee1fd6ede4a148bdca7ab778dd3");
-               set.add("d4805374b5f1ece75c0dd51d6283a0f6");
-               set.add("d55b77d76f6ece8bbd33cb2afdbd040f");
-               set.add("d570cb3cbfe88dfdf086bb1ab9ef76f8");
-               set.add("d587ebc9902ba2620d52a610441470cc");
-               set.add("d6196bfd55d758dd5a4899ce84cea85b");
-               set.add("d6815ec0b3e12fea0f470c01c0da3888");
-               set.add("d68971ffd271de2ddde6ac10c2817451");
-               set.add("d68ff175d25d083eee4f31bf0701a0d8");
-               set.add("d7fca16ff4e4ed99b72046f99533eef3");
-               set.add("d815d7a8dd0446ba21ddbc37d3733f36");
-               set.add("d8d40312d0751951b4c45f2100379950");
-               set.add("d97e0d0fbea2a015d5e7ea229b99a6c3");
-               set.add("d98c6a23fafafb0f9981f2123b1a1938");
-               set.add("d99d6bfaede491ceae109a644cf54098");
-               set.add("da0f523e815ce990a3a5f5b5574cec4a");
-               set.add("da686b7f2a26a36051b273bbabdd7ecc");
-               set.add("dadb54d4a8ba762a8d2a2fad5fcd7582");
-               set.add("db03b5399e1a324660934ad81036d987");
-               set.add("db29f48ac45b41ad389b1b4e8e30f553");
-               set.add("db98a87e4be1e5b59f589335e1509996");
-               set.add("db9be69a1dd929be69406f9e75533fd3");
-               set.add("dba45dfda4d06aebf3adc194ca4ec35d");
-               set.add("dc339714def04ec3e5e280faec8445e5");
-               set.add("dc3fb757715c96f7c629b7712d87ca61");
-               set.add("dc5dabf20b3fbee0d3a0b1d800c74d4f");
-               set.add("dcc7b2c56f358641ea9a73c6a81479f5");
-               set.add("dd922d98ecf5096482946d8671b647e3");
-               set.add("dda8214d7b53392f8ed9fbe4178e90b9");
-               set.add("ddd37af5af0a69bed06bc50cc0a6f4c2");
-               set.add("de8d217fad9883d9bfdee5af01472971");
-               set.add("def34fc0fd41527b300d3181a70cdecf");
-               set.add("df00d9361332c48cba23bfcd41e799d4");
-               set.add("df35772f10769bc28701c488d33e89b2");
-               set.add("df769f9dc2477135b0c4320e7e7b4c2f");
-               set.add("df95377a3f69b8fbe5dcdfa041491717");
-               set.add("df98c3766238aa84f9a9dd92cd73fe72");
-               set.add("dfd019d69302047a67434458d0fa8952");
-               set.add("e037a9e26a8b319437ab7c962714dc56");
-               set.add("e0d2f02d29a965fafd85a4ae6ad37246");
-               set.add("e10e651cd85e41be3402f51885bbf107");
-               set.add("e162d7b2e436ae6d369f4fbaf53af4b4");
-               set.add("e176177a2b64669a6bcd1cf8beb35df2");
-               set.add("e194252a63a3433b5a5278f68678b7dc");
-               set.add("e19d16546c555d073454ea56ece1cbd6");
-               set.add("e1cb375938189d4090b000ab41c77a06");
-               set.add("e1ce2428389f0c2356881e626f161ccc");
-               set.add("e27c22419443eb612af1620f4c8be007");
-               set.add("e2a5adcdd7b01611736b6b74f8c506ee");
-               set.add("e3084721ba7ae53996337e82c5f469ab");
-               set.add("e35f4cccfe57bdd7338dadeba55609f1");
-               set.add("e39a2ef2eaaaf7ba74623f14c917ee1d");
-               set.add("e3e11cf57dc3f1c6ca59acb06370698f");
-               set.add("e4557f7733332200116b753408cdb966");
-               set.add("e48c96d6025b38addad2278f24c963ef");
-               set.add("e48db7db130af48cccb2d830d3cbaa14");
-               set.add("e4d169990e34bfeab0c6a780d6a49d58");
-               set.add("e4ea1f6b01c9cdcf9e550792ed336384");
-               set.add("e4ee8ada1fbbe886fb25a7f484609690");
-               set.add("e54846d325334547923d8b64da70f529");
-               set.add("e56ccf6eca77d62dde88c895abfc1c1a");
-               set.add("e591790779db1c866b179d6f85b09dda");
-               set.add("e5bec4799ceef43816054f92de9652b5");
-               set.add("e5db5832d59e14d6999144fa8cd10e3f");
-               set.add("e611fb9e857f9bee391056e1f971a0aa");
-               set.add("e6321fdd099d70352883b45f6c2a20a9");
-               set.add("e682fc42ee7ecdbf595116293cbe8a6b");
-               set.add("e6ae48418d10883fe9657075b476274d");
-               set.add("e6fa2a139e5c56f8f483aaeeee0b7fbb");
-               set.add("e77d3c78240ec60f7f4dd67a2e71085a");
-               set.add("e78ad15fb1fd450f9221147e458b1abd");
-               set.add("e7a1dc89a6cab821776ea61fe6ba10f4");
-               set.add("e7df074666f6caa44b798342bdab6230");
-               set.add("e7f25163d78c2c658300cd0f9a8a3b04");
-               set.add("e80f22347419025053de7da1f07912ec");
-               set.add("e828123fa3cdf86dc0fe1b5c86d7c87d");
-               set.add("e8764c00097a0a1254f43a16c98a1d7f");
-               set.add("e89df60deddf270cbc2232bbe26420d1");
-               set.add("e8d289f3c1aa961cf4ac8d164e286dde");
-               set.add("e9051eac7829dc1a95987230fb21d2d9");
-               set.add("e90846d2c3e16de5ed5dff4c21356edd");
-               set.add("e954907cdfba1cf07f19f64af5cf45b1");
-               set.add("e96e004e988b8e36b2ab9ed1b0f65649");
-               set.add("e984d3924451d3498a3efccd845a77fe");
-               set.add("e98b4097ddb057755e561c33a0f3428d");
-               set.add("e9a2ba17cc4b93063d61813359fd6798");
-               set.add("ea90b42f6ada6e0ac5d179af4129022d");
-               set.add("eab4231af5b3ffab13f9a04b8aef0fad");
-               set.add("eac17a7d30d24e60e6b2ce10059b37a0");
-               set.add("eaf3af4d0b61df60ee8fe3a808de6ffd");
-               set.add("eb3178d36a578fd8b430ea82429ee145");
-               set.add("eb4fbf9835a3584e6015260f0dff2174");
-               set.add("ec3a9437b1481db4d8cbc3b4fc0888a1");
-               set.add("ec47d133c23dba3d015ae730a1b9659f");
-               set.add("ec6d321581a133fee9766eedff4db9d6");
-               set.add("eca16f6d986bd893af3c4a97b99df623");
-               set.add("ecf09182c51e5110d636828364ba3ee6");
-               set.add("ecfbf6f7017f0981ba6d30331c9dc870");
-               set.add("ed1eaef061f0d30503a64f27d8ea2825");
-               set.add("ed2e4441ad7dcbe0a5d3e62bc31aa9bc");
-               set.add("ed6304572c0f1283fd06f9c09ef32b61");
-               set.add("ed7d650fc0f5de8c9da107e53025f220");
-               set.add("ef405c5b0de638c01cf57c379aaff45b");
-               set.add("ef5ec03860cd32e910a3ddb2d8740300");
-               set.add("efdc8c21ee843f98a0dc08ef694b6db7");
-               set.add("f0111af67e822944e1dc1ab24e5b8458");
-               set.add("f0e8026289bc1f9109b25f4599556aaf");
-               set.add("f0ff5a7aa6f4e8785fa406636618c01d");
-               set.add("f19a809facb46a7a094c17039126eb3e");
-               set.add("f1c7524d454c25cdd837662a80709030");
-               set.add("f202d26f911074235ac8e4a5c1ed4dad");
-               set.add("f2250dd8736aa007a7e2530dca1c6081");
-               set.add("f2e9d36561ed03eb23d38005919625d2");
-               set.add("f303e8a2a96635d2f44c414229c349bb");
-               set.add("f35b8eac398bae58ba622ef643f64aa2");
-               set.add("f3a37dbd51e59af2801272fffe457d64");
-               set.add("f3c4afc965427977685da607f8a6edca");
-               set.add("f468c204661ab47379613a1d03f56571");
-               set.add("f4ff8d1667c95377ac96e870170bfe64");
-               set.add("f585dccae7fae67affbf500ecf9a3839");
-               set.add("f59a4193ec432bd26f780a6b74b8be38");
-               set.add("f5d44e9d1523c3e6834108d9c76b2da9");
-               set.add("f69f58acf2b80f5dc29682c64de8da7f");
-               set.add("f6adafaf98b92438e1ad916e51a65366");
-               set.add("f6f175a7910c5039d0fa51393c920df8");
-               set.add("f71a00225b1abf1dddfcace79d0345a2");
-               set.add("f7446eb342242f011c118bb3439335a0");
-               set.add("f76e6e86c9b0d84638c1c26c55b16cc4");
-               set.add("f775463704e3d13817abd9674d62f468");
-               set.add("f80df9b85c1219fd2030ada584fbfc35");
-               set.add("f843fa1d0cd00122fcbcfd7caf1cb8ca");
-               set.add("f88e05d8303a6e5dfbd60ceed3988d78");
-               set.add("f92dbcd91aac65e0770f5fe947fc5a80");
-               set.add("f9512c5cc198adeff74fed3d4b0f4509");
-               set.add("f9e1ffe33f3676d0e39bc24e63cf3a99");
-               set.add("fa492225fbf03ad58ee88b6d84914583");
-               set.add("fa6279cc58de3fe6c617559684afec4f");
-               set.add("fb2a4db1a1a68dae79653dd2e32ade50");
-               set.add("fb2bc93f011d62ac03aed40d92b26ba2");
-               set.add("fb9b6d2d2d5e3c219e0163092181e014");
-               set.add("fbdc7fc274e5c71226372606beedb706");
-               set.add("fbe25dc54e2761c2c5e9f1f3a98d7f0f");
-               set.add("fbec5f910872b333be655c5143b1cb37");
-               set.add("fc372707722b210b257ef9e2f731edc3");
-               set.add("fcedc7e1d4fc17c7c4f2c6f6c7a820e0");
-               set.add("fcff88f351f2535dcbab726dec9060ee");
-               set.add("fd15d45e5f876ac3ff10cef478077e8b");
-               set.add("fd21ff84af0fe74de102f1985d068dee");
-               set.add("fd30f89057cd8ad151d612def38afb41");
-               set.add("fdab6eed0ecadf924ae128f25e8b1a10");
-               set.add("fdced723077daed39d0e4da044f75d64");
-               set.add("fddbc361461ae318e147e420a805a428");
-               set.add("fdee78ddeb6f567a636b9850f942256f");
-               set.add("fe858217631f3aaf02d8aaf849c7b2c9");
-               set.add("fec4bbfe3563397790d48ce6f5b48260");
-               set.add("ff73d7804897f4e1624a3b5571e48fbb");
-               set.add("ff78c3b27889d5365a03b3a3fd3a4c1e");
-               set.add("ffac65c383eb053e54b267fe4dfd2141");
-               
-               DIGESTS = Collections.unmodifiableSet(set);
-       }
-       
-       private PreferredMotorDigests() {
-               // Prevent instantiation
-       }
-       
-}
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentLoadingContext.java b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentLoadingContext.java
new file mode 100644 (file)
index 0000000..ec80db4
--- /dev/null
@@ -0,0 +1,15 @@
+package net.sf.openrocket.file.openrocket.importt;
+
+public class DocumentLoadingContext {
+       
+       private int fileVersion;
+       
+       public int getFileVersion() {
+               return fileVersion;
+       }
+       
+       public void setFileVersion(int fileVersion) {
+               this.fileVersion = fileVersion;
+       }
+       
+}
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java
new file mode 100644 (file)
index 0000000..071d571
--- /dev/null
@@ -0,0 +1,2160 @@
+package net.sf.openrocket.file.openrocket.importt;
+
+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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.Simulation.Status;
+import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.file.AbstractRocketLoader;
+import net.sf.openrocket.file.RocketLoadException;
+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.logging.LogHelper;
+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.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
+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.RocketComponent.Position;
+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.simulation.FlightData;
+import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.simulation.FlightEvent.Type;
+import net.sf.openrocket.simulation.SimulationOptions;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Color;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.GeodeticComputationStrategy;
+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 AbstractRocketLoader {
+       private static final LogHelper log = Application.getLogger();
+       
+       
+       @Override
+       public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
+                       IOException {
+               log.info("Loading .ork file");
+               DocumentLoadingContext context = new DocumentLoadingContext();
+               
+               InputSource xmlSource = new InputSource(source);
+               OpenRocketHandler handler = new OpenRocketHandler(context);
+               
+               
+               try {
+                       SimpleSAX.readXML(xmlSource, handler, warnings);
+               } catch (SAXException e) {
+                       log.warn("Malformed XML in input");
+                       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(FlightDataType.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();
+               log.info("Loading done");
+               return doc;
+       }
+       
+}
+
+
+
+class DocumentConfig {
+       
+       /* Remember to update OpenRocketSaver as well! */
+       public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4" };
+       
+       /**
+        * 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;
+       
+       
+       ////////  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 BugException(
+                                       "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.findMethod(RocketComponent.class, "setName", String.class)));
+               setters.put("RocketComponent:color", new ColorSetter(
+                               Reflection.findMethod(RocketComponent.class, "setColor", Color.class)));
+               setters.put("RocketComponent:linestyle", new EnumSetter<LineStyle>(
+                               Reflection.findMethod(RocketComponent.class, "setLineStyle", LineStyle.class),
+                               LineStyle.class));
+               setters.put("RocketComponent:position", new PositionSetter());
+               setters.put("RocketComponent:overridemass", new OverrideSetter(
+                               Reflection.findMethod(RocketComponent.class, "setOverrideMass", double.class),
+                               Reflection.findMethod(RocketComponent.class, "setMassOverridden", boolean.class)));
+               setters.put("RocketComponent:overridecg", new OverrideSetter(
+                               Reflection.findMethod(RocketComponent.class, "setOverrideCGX", double.class),
+                               Reflection.findMethod(RocketComponent.class, "setCGOverridden", boolean.class)));
+               setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
+                               Reflection.findMethod(RocketComponent.class, "setOverrideSubcomponents", boolean.class)));
+               setters.put("RocketComponent:comment", new StringSetter(
+                               Reflection.findMethod(RocketComponent.class, "setComment", String.class)));
+               
+               // ExternalComponent
+               setters.put("ExternalComponent:finish", new EnumSetter<Finish>(
+                               Reflection.findMethod(ExternalComponent.class, "setFinish", Finish.class),
+                               Finish.class));
+               setters.put("ExternalComponent:material", new MaterialSetter(
+                               Reflection.findMethod(ExternalComponent.class, "setMaterial", Material.class),
+                               Material.Type.BULK));
+               
+               // BodyComponent
+               setters.put("BodyComponent:length", new DoubleSetter(
+                               Reflection.findMethod(BodyComponent.class, "setLength", double.class)));
+               
+               // SymmetricComponent
+               setters.put("SymmetricComponent:thickness", new DoubleSetter(
+                               Reflection.findMethod(SymmetricComponent.class, "setThickness", double.class),
+                               "filled",
+                               Reflection.findMethod(SymmetricComponent.class, "setFilled", boolean.class)));
+               
+               // BodyTube
+               setters.put("BodyTube:radius", new DoubleSetter(
+                               Reflection.findMethod(BodyTube.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(BodyTube.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // Transition
+               setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
+                               Reflection.findMethod(Transition.class, "setType", Transition.Shape.class),
+                               Transition.Shape.class));
+               setters.put("Transition:shapeclipped", new BooleanSetter(
+                               Reflection.findMethod(Transition.class, "setClipped", boolean.class)));
+               setters.put("Transition:shapeparameter", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setShapeParameter", double.class)));
+               
+               setters.put("Transition:foreradius", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setForeRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(Transition.class, "setForeRadiusAutomatic", boolean.class)));
+               setters.put("Transition:aftradius", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setAftRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(Transition.class, "setAftRadiusAutomatic", boolean.class)));
+               
+               setters.put("Transition:foreshoulderradius", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setForeShoulderRadius", double.class)));
+               setters.put("Transition:foreshoulderlength", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setForeShoulderLength", double.class)));
+               setters.put("Transition:foreshoulderthickness", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setForeShoulderThickness", double.class)));
+               setters.put("Transition:foreshouldercapped", new BooleanSetter(
+                               Reflection.findMethod(Transition.class, "setForeShoulderCapped", boolean.class)));
+               
+               setters.put("Transition:aftshoulderradius", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setAftShoulderRadius", double.class)));
+               setters.put("Transition:aftshoulderlength", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setAftShoulderLength", double.class)));
+               setters.put("Transition:aftshoulderthickness", new DoubleSetter(
+                               Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class)));
+               setters.put("Transition:aftshouldercapped", new BooleanSetter(
+                               Reflection.findMethod(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.findMethod(FinSet.class, "setFinCount", int.class)));
+               setters.put("FinSet:rotation", new DoubleSetter(
+                               Reflection.findMethod(FinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
+               setters.put("FinSet:thickness", new DoubleSetter(
+                               Reflection.findMethod(FinSet.class, "setThickness", double.class)));
+               setters.put("FinSet:crosssection", new EnumSetter<FinSet.CrossSection>(
+                               Reflection.findMethod(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
+                               FinSet.CrossSection.class));
+               setters.put("FinSet:cant", new DoubleSetter(
+                               Reflection.findMethod(FinSet.class, "setCantAngle", double.class), Math.PI / 180.0));
+               setters.put("FinSet:tabheight", new DoubleSetter(
+                               Reflection.findMethod(FinSet.class, "setTabHeight", double.class)));
+               setters.put("FinSet:tablength", new DoubleSetter(
+                               Reflection.findMethod(FinSet.class, "setTabLength", double.class)));
+               setters.put("FinSet:tabposition", new FinTabPositionSetter());
+               
+               // TrapezoidFinSet
+               setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
+                               Reflection.findMethod(TrapezoidFinSet.class, "setRootChord", double.class)));
+               setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
+                               Reflection.findMethod(TrapezoidFinSet.class, "setTipChord", double.class)));
+               setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
+                               Reflection.findMethod(TrapezoidFinSet.class, "setSweep", double.class)));
+               setters.put("TrapezoidFinSet:height", new DoubleSetter(
+                               Reflection.findMethod(TrapezoidFinSet.class, "setHeight", double.class)));
+               
+               // EllipticalFinSet
+               setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
+                               Reflection.findMethod(EllipticalFinSet.class, "setLength", double.class)));
+               setters.put("EllipticalFinSet:height", new DoubleSetter(
+                               Reflection.findMethod(EllipticalFinSet.class, "setHeight", double.class)));
+               
+               // FreeformFinSet points handled as a special handler
+               
+               // LaunchLug
+               setters.put("LaunchLug:radius", new DoubleSetter(
+                               Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
+               setters.put("LaunchLug:length", new DoubleSetter(
+                               Reflection.findMethod(LaunchLug.class, "setLength", double.class)));
+               setters.put("LaunchLug:thickness", new DoubleSetter(
+                               Reflection.findMethod(LaunchLug.class, "setThickness", double.class)));
+               setters.put("LaunchLug:radialdirection", new DoubleSetter(
+                               Reflection.findMethod(LaunchLug.class, "setRadialDirection", double.class),
+                               Math.PI / 180.0));
+               
+               // InternalComponent - nothing
+               
+               // StructuralComponent
+               setters.put("StructuralComponent:material", new MaterialSetter(
+                               Reflection.findMethod(StructuralComponent.class, "setMaterial", Material.class),
+                               Material.Type.BULK));
+               
+               // RingComponent
+               setters.put("RingComponent:length", new DoubleSetter(
+                               Reflection.findMethod(RingComponent.class, "setLength", double.class)));
+               setters.put("RingComponent:radialposition", new DoubleSetter(
+                               Reflection.findMethod(RingComponent.class, "setRadialPosition", double.class)));
+               setters.put("RingComponent:radialdirection", new DoubleSetter(
+                               Reflection.findMethod(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.findMethod(ThicknessRingComponent.class, "setThickness", double.class)));
+               
+               // EngineBlock
+               setters.put("EngineBlock:outerradius", new DoubleSetter(
+                               Reflection.findMethod(EngineBlock.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // TubeCoupler
+               setters.put("TubeCoupler:outerradius", new DoubleSetter(
+                               Reflection.findMethod(TubeCoupler.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // InnerTube
+               setters.put("InnerTube:outerradius", new DoubleSetter(
+                               Reflection.findMethod(InnerTube.class, "setOuterRadius", double.class)));
+               setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
+               setters.put("InnerTube:clusterscale", new DoubleSetter(
+                               Reflection.findMethod(InnerTube.class, "setClusterScale", double.class)));
+               setters.put("InnerTube:clusterrotation", new DoubleSetter(
+                               Reflection.findMethod(InnerTube.class, "setClusterRotation", double.class),
+                               Math.PI / 180.0));
+               
+               // RadiusRingComponent
+               
+               // Bulkhead
+               setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
+                               Reflection.findMethod(RadiusRingComponent.class, "setInnerRadius", double.class)));
+               setters.put("Bulkhead:outerradius", new DoubleSetter(
+                               Reflection.findMethod(Bulkhead.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               // CenteringRing
+               setters.put("CenteringRing:innerradius", new DoubleSetter(
+                               Reflection.findMethod(CenteringRing.class, "setInnerRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
+               setters.put("CenteringRing:outerradius", new DoubleSetter(
+                               Reflection.findMethod(CenteringRing.class, "setOuterRadius", double.class),
+                               "auto",
+                               Reflection.findMethod(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
+               
+               
+               // MassObject
+               setters.put("MassObject:packedlength", new DoubleSetter(
+                               Reflection.findMethod(MassObject.class, "setLength", double.class)));
+               setters.put("MassObject:packedradius", new DoubleSetter(
+                               Reflection.findMethod(MassObject.class, "setRadius", double.class)));
+               setters.put("MassObject:radialposition", new DoubleSetter(
+                               Reflection.findMethod(MassObject.class, "setRadialPosition", double.class)));
+               setters.put("MassObject:radialdirection", new DoubleSetter(
+                               Reflection.findMethod(MassObject.class, "setRadialDirection", double.class),
+                               Math.PI / 180.0));
+               
+               // MassComponent
+               setters.put("MassComponent:mass", new DoubleSetter(
+                               Reflection.findMethod(MassComponent.class, "setComponentMass", double.class)));
+               
+               // ShockCord
+               setters.put("ShockCord:cordlength", new DoubleSetter(
+                               Reflection.findMethod(ShockCord.class, "setCordLength", double.class)));
+               setters.put("ShockCord:material", new MaterialSetter(
+                               Reflection.findMethod(ShockCord.class, "setMaterial", Material.class),
+                               Material.Type.LINE));
+               
+               // RecoveryDevice
+               setters.put("RecoveryDevice:cd", new DoubleSetter(
+                               Reflection.findMethod(RecoveryDevice.class, "setCD", double.class),
+                               "auto",
+                               Reflection.findMethod(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
+               setters.put("RecoveryDevice:deployevent", new EnumSetter<RecoveryDevice.DeployEvent>(
+                               Reflection.findMethod(RecoveryDevice.class, "setDeployEvent", RecoveryDevice.DeployEvent.class),
+                               RecoveryDevice.DeployEvent.class));
+               setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
+                               Reflection.findMethod(RecoveryDevice.class, "setDeployAltitude", double.class)));
+               setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
+                               Reflection.findMethod(RecoveryDevice.class, "setDeployDelay", double.class)));
+               setters.put("RecoveryDevice:material", new MaterialSetter(
+                               Reflection.findMethod(RecoveryDevice.class, "setMaterial", Material.class),
+                               Material.Type.SURFACE));
+               
+               // Parachute
+               setters.put("Parachute:diameter", new DoubleSetter(
+                               Reflection.findMethod(Parachute.class, "setDiameter", double.class)));
+               setters.put("Parachute:linecount", new IntSetter(
+                               Reflection.findMethod(Parachute.class, "setLineCount", int.class)));
+               setters.put("Parachute:linelength", new DoubleSetter(
+                               Reflection.findMethod(Parachute.class, "setLineLength", double.class)));
+               setters.put("Parachute:linematerial", new MaterialSetter(
+                               Reflection.findMethod(Parachute.class, "setLineMaterial", Material.class),
+                               Material.Type.LINE));
+               
+               // Streamer
+               setters.put("Streamer:striplength", new DoubleSetter(
+                               Reflection.findMethod(Streamer.class, "setStripLength", double.class)));
+               setters.put("Streamer:stripwidth", new DoubleSetter(
+                               Reflection.findMethod(Streamer.class, "setStripWidth", double.class)));
+               
+               // Rocket
+               // <motorconfiguration> handled by separate handler
+               setters.put("Rocket:referencetype", new EnumSetter<ReferenceType>(
+                               Reflection.findMethod(Rocket.class, "setReferenceType", ReferenceType.class),
+                               ReferenceType.class));
+               setters.put("Rocket:customreference", new DoubleSetter(
+                               Reflection.findMethod(Rocket.class, "setCustomReferenceLength", double.class)));
+               setters.put("Rocket:designer", new StringSetter(
+                               Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
+               setters.put("Rocket:revision", new StringSetter(
+                               Reflection.findMethod(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 final DocumentLoadingContext context;
+       private OpenRocketContentHandler handler = null;
+       
+       public OpenRocketHandler(DocumentLoadingContext context) {
+               this.context = context;
+       }
+       
+       /**
+        * 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);
+               }
+               
+               context.setFileVersion(parseVersion(docVersion));
+               
+               handler = new OpenRocketContentHandler(context);
+               return handler;
+       }
+       
+       
+       private int parseVersion(String docVersion) {
+               if (docVersion == null)
+                       return 0;
+               
+               Matcher m = Pattern.compile("^([0-9]+)\\.([0-9]+)$").matcher(docVersion);
+               if (m.matches()) {
+                       int major = Integer.parseInt(m.group(1));
+                       int minor = Integer.parseInt(m.group(2));
+                       return major * DocumentConfig.FILE_VERSION_DIVISOR + minor;
+               } else {
+                       return 0;
+               }
+       }
+       
+       @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 DocumentLoadingContext context;
+       private final OpenRocketDocument doc;
+       private final Rocket rocket;
+       
+       private boolean rocketDefined = false;
+       private boolean simulationsDefined = false;
+       
+       public OpenRocketContentHandler(DocumentLoadingContext context) {
+               this.context = context;
+               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, context);
+               }
+               
+               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, context);
+               }
+               
+               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 DocumentLoadingContext context;
+       private final RocketComponent parent;
+       
+       public ComponentHandler(RocketComponent parent, DocumentLoadingContext context) {
+               this.parent = parent;
+               this.context = context;
+       }
+       
+       @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 BugException("Error constructing component.", e);
+               } catch (IllegalAccessException e) {
+                       throw new BugException("Error constructing component.", e);
+               } catch (InvocationTargetException e) {
+                       throw Reflection.handleWrappedException(e);
+               }
+               
+               parent.addChild(c);
+               
+               return new ComponentParameterHandler(c, context);
+       }
+}
+
+
+/**
+ * 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 DocumentLoadingContext context;
+       private final RocketComponent component;
+       
+       public ComponentParameterHandler(RocketComponent c, DocumentLoadingContext context) {
+               this.component = c;
+               this.context = context;
+       }
+       
+       @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, context);
+               }
+               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, context);
+               }
+               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, context);
+               }
+               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, context);
+               }
+               
+               
+               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
+                               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 {
+       @SuppressWarnings("unused")
+       private final DocumentLoadingContext context;
+       private final FreeformFinSet finset;
+       private final ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
+       
+       public FinSetPointHandler(FreeformFinSet finset, DocumentLoadingContext context) {
+               this.finset = finset;
+               this.context = context;
+       }
+       
+       @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 DocumentLoadingContext context;
+       private final MotorMount mount;
+       private MotorHandler motorHandler;
+       
+       public MotorMountHandler(MotorMount mount, DocumentLoadingContext context) {
+               this.mount = mount;
+               this.context = context;
+               mount.setMotorMount(true);
+       }
+       
+       @Override
+       public ElementHandler openElement(String element, HashMap<String, String> attributes,
+                       WarningSet warnings) {
+               
+               if (element.equals("motor")) {
+                       motorHandler = new MotorHandler(context);
+                       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 {
+       @SuppressWarnings("unused")
+       private final DocumentLoadingContext context;
+       private final Rocket rocket;
+       private String name = null;
+       private boolean inNameElement = false;
+       
+       public MotorConfigurationHandler(Rocket rocket, DocumentLoadingContext context) {
+               this.rocket = rocket;
+               this.context = context;
+       }
+       
+       @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 {
+       /** File version where latest digest format was introduced */
+       private static final int MOTOR_DIGEST_VERSION = 104;
+       
+       @SuppressWarnings("unused")
+       private final DocumentLoadingContext context;
+       private Motor.Type type = null;
+       private String manufacturer = null;
+       private String designation = null;
+       private String digest = null;
+       private double diameter = Double.NaN;
+       private double length = Double.NaN;
+       private double delay = Double.NaN;
+       
+       public MotorHandler(DocumentLoadingContext context) {
+               this.context = context;
+       }
+       
+       
+       @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;
+               }
+               
+               List<? extends Motor> motors = Application.getMotorSetDatabase().findMotors(type, manufacturer,
+                               designation, diameter, length);
+               
+               // No motors
+               if (motors.size() == 0) {
+                       Warning.MissingMotor mmw = new Warning.MissingMotor();
+                       mmw.setDesignation(designation);
+                       mmw.setDigest(digest);
+                       mmw.setDiameter(diameter);
+                       mmw.setLength(length);
+                       mmw.setManufacturer(manufacturer);
+                       mmw.setType(type);
+                       warnings.add(mmw);
+                       return null;
+               }
+               
+               // One motor
+               if (motors.size() == 1) {
+                       Motor m = motors.get(0);
+                       if (digest != null && !digest.equals(m.getDigest())) {
+                               String str = "Motor with designation '" + designation + "'";
+                               if (manufacturer != null)
+                                       str += " for manufacturer '" + manufacturer + "'";
+                               str += " has differing thrust curve than the original.";
+                               warnings.add(str);
+                       }
+                       return m;
+               }
+               
+               // Multiple motors, check digest for which one to use
+               if (digest != null) {
+                       
+                       // Check for motor with correct digest
+                       for (Motor m : motors) {
+                               if (digest.equals(m.getDigest())) {
+                                       return m;
+                               }
+                       }
+                       String str = "Motor with designation '" + designation + "'";
+                       if (manufacturer != null)
+                               str += " for manufacturer '" + manufacturer + "'";
+                       str += " has differing thrust curve than the original.";
+                       warnings.add(str);
+                       
+               } else {
+                       
+                       // No digest, check for preferred digest (OpenRocket <= 1.1.0)
+                       // TODO: MEDIUM: This should only be done for document versions 1.1 and below
+                       for (Motor m : motors) {
+                               if (PreferredMotorDigests.DIGESTS.contains(m.getDigest())) {
+                                       return m;
+                               }
+                       }
+                       
+                       String str = "Multiple motors with designation '" + designation + "'";
+                       if (manufacturer != null)
+                               str += " for manufacturer '" + manufacturer + "'";
+                       str += " found, one chosen arbitrarily.";
+                       warnings.add(str);
+                       
+               }
+               return motors.get(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.trim())) {
+                                       type = t;
+                                       break;
+                               }
+                       }
+                       if (type == null) {
+                               warnings.add(Warning.fromString("Unknown motor type '" + content + "', ignoring."));
+                       }
+                       
+               } else if (element.equals("manufacturer")) {
+                       
+                       // Manufacturer
+                       manufacturer = content.trim();
+                       
+               } else if (element.equals("designation")) {
+                       
+                       // Designation
+                       designation = content.trim();
+                       
+               } else if (element.equals("digest")) {
+                       
+                       // Digest is used only for file versions saved using the same digest algorithm
+                       if (context.getFileVersion() >= MOTOR_DIGEST_VERSION) {
+                               digest = content.trim();
+                       }
+                       
+               } else if (element.equals("diameter")) {
+                       
+                       // Diameter
+                       diameter = Double.NaN;
+                       try {
+                               diameter = Double.parseDouble(content.trim());
+                       } 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.trim());
+                       } 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.trim());
+                               } 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 DocumentLoadingContext context;
+       private final OpenRocketDocument doc;
+       private SingleSimulationHandler handler;
+       
+       public SimulationsHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
+               this.doc = doc;
+               this.context = context;
+       }
+       
+       @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, context);
+               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 DocumentLoadingContext context;
+       
+       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, DocumentLoadingContext context) {
+               this.doc = doc;
+               this.context = context;
+       }
+       
+       
+       
+       @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(), context);
+                       return conditionHandler;
+               } else if (element.equals("flightdata")) {
+                       dataHandler = new FlightDataHandler(context);
+                       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;
+               }
+               
+               SimulationOptions conditions;
+               if (conditionHandler != null) {
+                       conditions = conditionHandler.getConditions();
+               } else {
+                       warnings.add("Simulation conditions not defined, using defaults.");
+                       conditions = new SimulationOptions(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 final DocumentLoadingContext context;
+       private SimulationOptions conditions;
+       private AtmosphereHandler atmosphereHandler;
+       
+       public SimulationConditionsHandler(Rocket rocket, DocumentLoadingContext context) {
+               this.context = context;
+               conditions = new SimulationOptions(rocket);
+               // Set up default loading settings (which may differ from the new defaults)
+               conditions.setGeodeticComputation(GeodeticComputationStrategy.FLAT);
+       }
+       
+       public SimulationOptions 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"), context);
+                       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("launchlongitude")) {
+                       if (Double.isNaN(d)) {
+                               warnings.add("Illegal launch longitude.");
+                       } else {
+                               conditions.setLaunchLongitude(d);
+                       }
+               } else if (element.equals("geodeticmethod")) {
+                       GeodeticComputationStrategy gcs =
+                                       (GeodeticComputationStrategy) DocumentConfig.findEnum(content, GeodeticComputationStrategy.class);
+                       if (gcs != null) {
+                               conditions.setGeodeticComputation(gcs);
+                       } else {
+                               warnings.add("Unknown geodetic computation method '" + content + "'");
+                       }
+               } 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 {
+       @SuppressWarnings("unused")
+       private final DocumentLoadingContext context;
+       private final String model;
+       private double temperature = Double.NaN;
+       private double pressure = Double.NaN;
+       
+       public AtmosphereHandler(String model, DocumentLoadingContext context) {
+               this.model = model;
+               this.context = context;
+       }
+       
+       @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(SimulationOptions 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 final DocumentLoadingContext context;
+       
+       private FlightDataBranchHandler dataHandler;
+       private WarningSet warningSet = new WarningSet();
+       private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
+       
+       private FlightData data;
+       
+       
+       public FlightDataHandler(DocumentLoadingContext context) {
+               this.context = context;
+       }
+       
+       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"), context);
+                       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;
+                       double launchRodVelocity = Double.NaN;
+                       double deploymentVelocity = 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) {
+                       }
+                       try {
+                               launchRodVelocity = DocumentConfig.stringToDouble(attributes.get("launchrodvelocity"));
+                       } catch (NumberFormatException ignore) {
+                       }
+                       try {
+                               deploymentVelocity = DocumentConfig.stringToDouble(attributes.get("deploymentvelocity"));
+                       } catch (NumberFormatException ignore) {
+                       }
+                       
+                       data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach,
+                                       timeToApogee, flightTime, groundHitVelocity, launchRodVelocity, deploymentVelocity);
+               }
+               
+               data.getWarningSet().addAll(warningSet);
+               data.immute();
+       }
+       
+       
+}
+
+
+class FlightDataBranchHandler extends ElementHandler {
+       @SuppressWarnings("unused")
+       private final DocumentLoadingContext context;
+       private final FlightDataType[] types;
+       private final FlightDataBranch branch;
+       
+       public FlightDataBranchHandler(String name, String typeList, DocumentLoadingContext context) {
+               this.context = context;
+               String[] split = typeList.split(",");
+               types = new FlightDataType[split.length];
+               for (int i = 0; i < split.length; i++) {
+                       types[i] = FlightDataType.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(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;
+       }
+       
+       @Override
+       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;
+       }
+       
+       @Override
+       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;
+       }
+       
+       @Override
+       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;
+       }
+       
+       
+       @Override
+       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;
+       }
+       
+       @Override
+       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;
+       }
+       
+       @Override
+       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;
+       }
+       
+       @Override
+       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 {
+       
+       @Override
+       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.findMethod(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 {
+       
+       @Override
+       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/core/src/net/sf/openrocket/file/openrocket/importt/PreferredMotorDigests.java b/core/src/net/sf/openrocket/file/openrocket/importt/PreferredMotorDigests.java
new file mode 100644 (file)
index 0000000..ffcedf7
--- /dev/null
@@ -0,0 +1,887 @@
+package net.sf.openrocket.file.openrocket.importt;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class contains the motor digests of motors included in OpenRocket versions prior to 1.1.1.
+ * Before this the motor digest was not included in the design files, and therefore if the motor
+ * digest is missing when loading a file, the loader should prefer the motors with a digest defined
+ * in this class.
+ * <p>
+ * This is not a requirement for supporting the OpenRocket format, but allows opening older OpenRocket
+ * design files accurately without warnings of multiple motors. 
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+final class PreferredMotorDigests {
+       
+       // FIXME:  This class is obsolete/broken after digesting algorithm changes
+       
+       /**
+        * A set containing the preferred motor digests.
+        */
+       public static final Set<String> DIGESTS;
+       static {
+               /*
+                * The list contains 845 digests, set initial parameters suitably to
+                * prevent any rehashing operations and to minimize size (power of two).
+                *    845/1024 = 0.825
+                */
+               Set<String> set = new HashSet<String>(1024, 0.85f);
+               
+               set.add("000ffb4c8e49ae47b2ab9a659da9e59b");
+               set.add("0039ed088e61360d934d9bd8503fad92");
+               set.add("003eeba358de7ebf9293b0e4c4ca9e66");
+               set.add("00e1a0576a93101d458c1c3d68d3eee0");
+               set.add("0111b89926277a6ea3f6075052343105");
+               set.add("0142c270a670ffff41c43268b0f129b9");
+               set.add("01be1f9100e05fb15df4c13395f7181c");
+               set.add("026f5924c48693077f2b11cdcdeb7452");
+               set.add("029082f7acda395568ca7f7df40764e1");
+               set.add("02dd1b3e2df7daf48b763f5ace35345e");
+               set.add("036e124dce42859ff08efa79e1f202e8");
+               set.add("03b88e64af521b03803247922801c996");
+               set.add("0468d7dc3dca25ac073dac1bd674e271");
+               set.add("048cfb7c2477c6e957d501c5ed3bc252");
+               set.add("049dda2ad1a709321734f393dc8a115b");
+               set.add("056d61b6a268283411e9dc9731dbb5e6");
+               set.add("05b85612f288726b02cdc47af7026aac");
+               set.add("05e205dc5dbd95db25305aa5c77b1192");
+               set.add("0601c6944d02e8736c09c2a8bb7cba49");
+               set.add("0622884d0a0954b1df6694ead24868bf");
+               set.add("063e7748d9a96508a70b1a2a1887aa3d");
+               set.add("06634321a8c5d533eb5efcbb40143257");
+               set.add("069a54372ed2776286160384ca0cac4f");
+               set.add("075539867b13c2afcc5198e00d7f4b5c");
+               set.add("076d9374af5fb0f2469083f9b57b7b96");
+               set.add("07c44b615a67060bca83c6faed56c0c6");
+               set.add("0825628215a980eed5fb4bed4eaec3b8");
+               set.add("082bad018f6d1e5622c371c1fe3148d6");
+               set.add("0837c3014078c8c8e79961b939be83cb");
+               set.add("08abceec22c5f6be5e9864be38df8ad5");
+               set.add("08c3b40a4bcf7a33256e5543e484f995");
+               set.add("08ca5be1a598772a8683016db619de36");
+               set.add("0a80cecafb53ae0ac73e6aec1a4567dd");
+               set.add("0add7ca688bcd32df8c3367a094e7847");
+               set.add("0b175b4beb0057db1b169d61061208a7");
+               set.add("0b955870dc2007c2b5f07eea57609420");
+               set.add("0c60a51d36ee5118fe29173aff2f6e49");
+               set.add("0c96cd95432d8e2ce6a6463ebf50beb1");
+               set.add("0d06d195c29a7f6fde6d002171922f2e");
+               set.add("0d642d7cb1544d19ec471124db97b92e");
+               set.add("0dd49968e2b1c4b1077e3c7ade056a79");
+               set.add("0e0d93ee28216440a5fa9452c9082351");
+               set.add("0e6774068b61579e20b89771b8a8f273");
+               set.add("0eac15679d3ae2fbd41083492b356b03");
+               set.add("0eca4c015dd1de561c2bbc47eaa4daf6");
+               set.add("0f0e1d09c7dec3a05b870b399ddbf6ee");
+               set.add("0f3c31b26b5768b3202f02f9d6bcc71c");
+               set.add("0f47293601d59fbad2076012090665dc");
+               set.add("0f5a1b31c333b722e4a72acbeba3a189");
+               set.add("0f6a55aca8a317f4d3d3236e4944343d");
+               set.add("0ffaa291ee52495d7dfec03b3a845636");
+               set.add("1092f5c5b48c2dcd4ac0bb16fa5383a8");
+               set.add("10a1689703dc533d435bef7265dd9ac0");
+               set.add("11bcc433b82e714f59809d76eed0ba05");
+               set.add("11ce2ec12b216f3e8d71fd9c53782b23");
+               set.add("11d11cdff93f18d10a1286cc3485c8c7");
+               set.add("11eac86852e12b9c3a2d0a4b183c3b79");
+               set.add("120eab6dd03b9bee7f5eb717e4e9d491");
+               set.add("1272d1a6979ea20a2efee3eb04657915");
+               set.add("12f6c5360c83656356c902ece3c0ff1b");
+               set.add("138a603a483dcf4127f1dcf208843e67");
+               set.add("140276d009fde1357ecdcb5d9ddc8a80");
+               set.add("1491fae1c7ce940915dd8296c74320f3");
+               set.add("14955ccec83043f3b1ef92f8524b3e67");
+               set.add("150b1279bc8b7f509a030274ee8e3f35");
+               set.add("153374d45687af1e96d5b8b1b03a2515");
+               set.add("1536a1389a9cd4ecf5bfaac9f4333852");
+               set.add("1539231d9952fdbe0533df405c46356c");
+               set.add("15d6a88bb656579740291df01297aa5c");
+               set.add("15fbf68a7c02161beb6cad00325752c3");
+               set.add("161cd37f60e13b9850e881bac61c840f");
+               set.add("161ed36663b694184f7f4131d1d5f3df");
+               set.add("167df7bf13809a19da8ff90a27f4b522");
+               set.add("170e81af0371550ea20c827669fbf0fd");
+               set.add("177c0df08cb85a4e13bf7412dacf2699");
+               set.add("179b9694bca64255ce9c0b06a08f46e6");
+               set.add("17d55e2cd3df50ef07aff9be6b160915");
+               set.add("1835337dfceafa20029fe6e472e7c7f0");
+               set.add("185820cacfb62e34c1f6c2e1feb42d27");
+               set.add("18981fde366efeca850bdf490253f0ec");
+               set.add("18b7f1dce04bc7838f3d2b234923de27");
+               set.add("18c2d213b8de15fc11ef66f7a7ad04a4");
+               set.add("1914ab609416b8559eeefda814867b9b");
+               set.add("19ae231357c49b6bf9427fa178dc58ab");
+               set.add("19b0b447800ba36f2d4ce76264009e2d");
+               set.add("19c9120e2fb7bbf6d21d71659f77439c");
+               set.add("19c9753bd99d7f0328792a434625f8a5");
+               set.add("1a508ce5b461be1998750bdad26764a3");
+               set.add("1a77681a4646cd21461df84c49074fe3");
+               set.add("1aa169a73004fc66a932576ac2732b15");
+               set.add("1aa1f3cc21a0f6a6eadb6166d468284c");
+               set.add("1ac8dac1b547a064a306bf42e568b5bc");
+               set.add("1af11d2e99f06b69ab5103731592ae8e");
+               set.add("1af30f73640ac1f9f3c8ef32fd04bfb8");
+               set.add("1b337a115a491abfc3abcd62399704d2");
+               set.add("1bb9c002f22ccd24bfcec36957ac0367");
+               set.add("1cbb12c9b58adc33642e1165b77c2e58");
+               set.add("1d30457aa2af0f212a26b9d2c203a216");
+               set.add("1d390d2ede88fb2f77ad7e7432155466");
+               set.add("1d920d4ee2bef0c7ffb28a91b9e325f6");
+               set.add("1e09cd01462e6d4728efafc4a550a5a6");
+               set.add("1e26c7969adb5bfc507da22802f17053");
+               set.add("1e5378337317821ffa4f53e9ecf47fbd");
+               set.add("1e68b1ce7eb224dc65b39546d0892299");
+               set.add("1e757827e2e03a781905e9f983c89933");
+               set.add("1f2564b3f0d78751b4e1d5836d54f7b1");
+               set.add("210bd4536d1c1872d213995420cf9513");
+               set.add("21bdc48d9411ffc8e811e32c45640f58");
+               set.add("21d4e53c3308cf3a1e916ef6cafff873");
+               set.add("21db7fea27e33cbab6fa2984017c241c");
+               set.add("221ab691a72a6f8b65792233b7bdf884");
+               set.add("222b7613b7a9a85d45051fb263b511bf");
+               set.add("224c062a8213f22058c0479c91ce470a");
+               set.add("22777fde378d9610258e4223fb5563f5");
+               set.add("22929b4849129644531a722397786513");
+               set.add("22c31705c3948c39721ced4ca04b2e65");
+               set.add("22e355a9e573b7f6f86c7e0791647ba7");
+               set.add("2320f4b15fb78448ce16a5a625f6f8f2");
+               set.add("234467bcf00a15e7377ceca46b7302f8");
+               set.add("23e140b2126af53487781f63082615e5");
+               set.add("245d147c568faf00dfb47d9c9080871c");
+               set.add("24a5102957c91107a092704f4f166e77");
+               set.add("24b7b0f55cea9329f981f00d922cfe61");
+               set.add("24d9308fa5d88f89365760a6e54f557f");
+               set.add("24fe3f1922a6d16b21f57b9925558296");
+               set.add("2571d40a353d275cdd8a4ba6e80a32fd");
+               set.add("259a0325a52acf54184fd439d1b2521d");
+               set.add("259d90773b3387c58aecb648c2c3812e");
+               set.add("25fd0f44fbbadfb70cee0467f9b53d3e");
+               set.add("26331fa38f2af84b18df5dd1db0244f0");
+               set.add("26a5e7834018943090396d419ca64662");
+               set.add("271f29d0b199d0d3f036e8f99ce76975");
+               set.add("2762f40ffacbc78b4c949cd38101a02a");
+               set.add("2769033a0acfff04e1f427027643c03a");
+               set.add("27b1601bb3a33c7cd2745caa651f0705");
+               set.add("27e522bd25f54343584ae89e90e64ee3");
+               set.add("2815e68ed1683663820682c8e00fd795");
+               set.add("285e598a676de414661a022f72967d29");
+               set.add("2886ee93f5dd4f02b331089928520e4f");
+               set.add("28f53f74ab45da2ab83072143f0d01d0");
+               set.add("2967cd7a160b396ef96f09695429d8e9");
+               set.add("29e99fbfab8c9771f4b5a86195db0c46");
+               set.add("2a1f5f5a829badfd64e2c20cd17bd38b");
+               set.add("2a941643d418880e0e7337aaaa00c555");
+               set.add("2a9d2a64b4601046774c9d27202de593");
+               set.add("2ad8de03de84415f1397cb2d4c77fb84");
+               set.add("2af7bcae566ada617d8888f34a5f70a3");
+               set.add("2bb2cea5465ab43f9b7e83cb44851223");
+               set.add("2bc22736450a8d0efb8d898bdcf52d43");
+               set.add("2c19c0cd4c005877798821dd65a2ff2e");
+               set.add("2c39985a5a49fa07759dc880e3722203");
+               set.add("2c58d5382b8d5fdbe1800e013f200f38");
+               set.add("2c8f6954ba9842ad9fc9bb367d14cf72");
+               set.add("2d13c151bbf6e1d7d7378c86d191d2d8");
+               set.add("2df4ee3f8a2c3611b267936e47bd3d3f");
+               set.add("2e6c8ecf50ee9ea82f407a8b0acd4f85");
+               set.add("2e97a2f015b1247b01b5e022bf6109cc");
+               set.add("2eae476e3eb97e2a1ad54c5b8fa48208");
+               set.add("2f44b9347e986c91ab886dc2e508885f");
+               set.add("2f478d2efa82571d5c3e49fde16c936e");
+               set.add("2f7460de6e7581a6775f739f894d86c6");
+               set.add("2fa429a16950f6c3f19a051b3417aac7");
+               set.add("2fa4545430dae966dce186984c98d0b7");
+               set.add("3027d63763f7aca58b41d52689f38dbd");
+               set.add("302b34ea5ec261fd74a4901d041d3f82");
+               set.add("30b5952157345beb00d753425a728757");
+               set.add("3136fef31b6d0e1d9a0dbbbdac05b0a3");
+               set.add("321377ccf346be4efa1fb8658032298a");
+               set.add("325e3898dc252f6c936301412be06505");
+               set.add("32fe6eecb5e97a6ff9c4f1c005857435");
+               set.add("33197b8e7194af401da6150c68004d7b");
+               set.add("3393a92e46a045c4eaf6b9e18f7044e3");
+               set.add("33a89133876e91fccc4058627b34d617");
+               set.add("3466c5940034ddb1371c4f86dabce964");
+               set.add("348abf304c63a702e4a229db28feee16");
+               set.add("349260e7bc0291ba2e4c26d4db00bee9");
+               set.add("3507c7d2b11a693620235ea3872dce66");
+               set.add("353236b8cb07eef73d80f25e240acddb");
+               set.add("35aeed248d254fbc3542b5cd3aa9842d");
+               set.add("36218bbb51e99aed53ea822ebaa2c873");
+               set.add("3666b06f839465adc5d36a6e75066a47");
+               set.add("36fb9fb79c253ee61e498e459f0cf395");
+               set.add("3703dd15904a118a05d771e7ee6e3f11");
+               set.add("370b98cc77577db1a07021e46c21cd3b");
+               set.add("3719475cc57cf3b5312f21b1efd228ef");
+               set.add("3738564e4327367bc2f359cdbb442304");
+               set.add("37bf1e76b05f333eefc0495e4f725838");
+               set.add("38715f11bf91b5ce06494e1ddd94c444");
+               set.add("387eea945f83c9567fa42c6e150b7ba9");
+               set.add("389687548b9f05e6c99d93a2ecf76307");
+               set.add("38b1e93cc1910ecc5301502fbb9bd8a3");
+               set.add("3a0b2ffd2d4593581c52bdc1094d92d8");
+               set.add("3a99a5318497e7108995a08675fa70d5");
+               set.add("3b4573f1f11db1ffedd14e10d539aad3");
+               set.add("3bc526028cf0be42fcbb75936810d41c");
+               set.add("3bc5834ec0726b10465b67f17b77044e");
+               set.add("3bf858e6c91e0292259a886b8bf793c3");
+               set.add("3c4eea57e65806dc59dd4c206bef79e1");
+               set.add("3c7b9e1836fe07f7a4ffaea90e7f33fc");
+               set.add("3c8aee818229c48b9a882caa6de58c18");
+               set.add("3cf831629486be08e747671a14d113f5");
+               set.add("3d6b990aaee7dff0be939343711dfa74");
+               set.add("3e2d355d7fd39e54ceead835d14df7e9");
+               set.add("3e8697fe46496d41528387e2d37d734a");
+               set.add("3ea538f54677ecaffbed1ae5f2e12d28");
+               set.add("3f654d824783b4392396b34ad2b44974");
+               set.add("3fc4889ea40aea23fedc994704ba4708");
+               set.add("41145e8204991f0b644b831cd859c4e2");
+               set.add("415fecbed32f8de16ffbab8e97edb4cb");
+               set.add("41633604778611785d7453c23823b0b3");
+               set.add("41d37971a99bb08d0f5f4fdcfcd87e8d");
+               set.add("428c0aeb520fe9e77d3b464543620716");
+               set.add("42cc2865a6fc399e689d2d569c58de2a");
+               set.add("43a6db581840e3645459ce51953ca9a5");
+               set.add("43a72eab1f3f874da7d68092e83357ec");
+               set.add("44255564acd68eca32ffab8e6130c5cc");
+               set.add("4448ff245bfd8d2606b418f33797571f");
+               set.add("44a4e02e520657221706cd6d69bcfb13");
+               set.add("44b12361fee8a2385a9b90e44fd079f3");
+               set.add("44b7c1c17e8e4728fadeecb6ba797af0");
+               set.add("44d734a18b45937c3035a047f9063dfd");
+               set.add("44edf41dd7624a6e2259d8e451622527");
+               set.add("4528bda7194c6dfafada95d68c2faa3a");
+               set.add("45a8a995a3614f823c04f3c157effe97");
+               set.add("45d2f014e70681483d6bc5864cf94b20");
+               set.add("46232174087bfb178ad7cc35bfb387a8");
+               set.add("46401106d96b729d330375a63e655374");
+               set.add("46ac2356b12ed7519ae2dd5f199b7c10");
+               set.add("4790684e33d48e3dfe99b6ff7712be8a");
+               set.add("479a2848353fef692063ec37e7d556dc");
+               set.add("47a649cae70a90e7d1ae2b2ab10465f0");
+               set.add("47bc150e2585e61cf9380ed540de4465");
+               set.add("4863872b48ecad3005e7b60d114c0fde");
+               set.add("487c3163ebf25cd3b4479e13e30cba5b");
+               set.add("48c5d84e56a982689f4268ed9b50cded");
+               set.add("493a84bde424f5946290238998d64873");
+               set.add("499e8c7c38dd4d8068eefc4eb58d4cf5");
+               set.add("4a03d963b887b4ade3d574e87d111e9d");
+               set.add("4a5509929d6991507c6e136178942a2d");
+               set.add("4a933f8824eba082555515e69d3bfe43");
+               set.add("4abc93cb926e33fbb97aa0d2ffe7885a");
+               set.add("4ad536d6aee9fffe1e84c9e75698f5cf");
+               set.add("4af14f94870a2e3d47dbd78cc05e58a8");
+               set.add("4b0a7961ee650f518533f03c38ca8320");
+               set.add("4b166cec69dc9ace3a9f598674c35b3c");
+               set.add("4b5a632e55f4dbea5435e151145337a7");
+               set.add("4b797a7d23faae4daf8b2946d6cb24dd");
+               set.add("4b9e8ea91d6bd67b22be67dd40b871a7");
+               set.add("4bd7e46dd429e45ddee5736f86d494cc");
+               set.add("4beec7064114b6e49cc76cc2f71594ec");
+               set.add("4c3f47c263ea5b933ac5184466573f6d");
+               set.add("4c9b11094fa43b8a9aaf1a1568bf60c2");
+               set.add("4ca44906c21909f396774827017e007e");
+               set.add("4ca7dd633f56f76f794faf958416f4c1");
+               set.add("4d6956c8d3db98528dfbdafa4a3316b6");
+               set.add("4d84b18416836b7297b990a408a6eda3");
+               set.add("4e13b8d5d4a77393b2fbfbaebe9ea1ca");
+               set.add("4e3b029d124b898f1d11a8d15d2a6688");
+               set.add("4e9723863a06235d9339cd00912871ed");
+               set.add("4efdf67cd98424e7cb008dd09b169942");
+               set.add("4f25dd1fcb4aedb512f24758961d87f9");
+               set.add("4f86907e557c00d13b42a2393b834d8d");
+               set.add("4fdb3ba6ebc9b3e9ab133c15312f995a");
+               set.add("504bbb5991ad94728e6b73c6ddc6c476");
+               set.add("515f449c1e9fd279dbdadf3cc38fd008");
+               set.add("51d9a0c78486de462af6a490acea1fcb");
+               set.add("52032bb8c1acb8bf7320aa73babd2e50");
+               set.add("5203feb9b0d422a38052d9df4103e3ab");
+               set.add("5222c37a7e8618d4cb43ce8c4a188129");
+               set.add("52731882ea73ad5b1d39c25e2969d9aa");
+               set.add("536af35745c20e4ee25486a31c2fb57c");
+               set.add("5379086fb93464cbdad4459101ed4d07");
+               set.add("542f3505366c2d6575e73064aacf536a");
+               set.add("54350b63fafc31af32bdf49cf0bbfda2");
+               set.add("5498ead583ab6cd6900a533b1cb69df8");
+               set.add("553eb9e396b2f304f963b717bb3a9059");
+               set.add("55c5181d0e1b170cfd05c3a9271b3bc6");
+               set.add("566ff170b185c5cfd893030c97080451");
+               set.add("568c906117202c4d4451dfb3be697921");
+               set.add("56a9926b91222c8943640da0b642d617");
+               set.add("56fcddb2fc61ab068e1ce98a226fd34d");
+               set.add("573f9b1aa16e771da95df44fe3a62167");
+               set.add("5805ae3e1c5fa9f7a604152c40c9d06d");
+               set.add("5844ffd995e179e21476fe41a72c7e85");
+               set.add("5866a0ca3348c1b406e4a4a869b183ae");
+               set.add("5922a04c19e52d4a3844b61b559a89d4");
+               set.add("5957b399b3380e097b70cfc09bae1bd3");
+               set.add("59785d3feccf91b7a9fcd96fe5e686de");
+               set.add("59cc15fde8f2bab7beac6a9542662df3");
+               set.add("59ef8fd572ad56b7c00092f185512c0a");
+               set.add("5a26e5d6effb9634941bbdaecf1cc4ce");
+               set.add("5a94fedb054c29331d22c4442ad619a6");
+               set.add("5b1a41ab325cdfb925f500868f77e058");
+               set.add("5b20fd5088ed40d65a52c38fbe314373");
+               set.add("5b3510c0aa53405e1fbd7a67b3af34fd");
+               set.add("5b96ce711afb37fb511e70ac12cb717f");
+               set.add("5bb8c694f0d7e39aceaa7fe7a885a6e1");
+               set.add("5bc7dae98ed248bc85f4782783c7a383");
+               set.add("5c1e091a898470db28aaddc968071a00");
+               set.add("5c603c37c8ae3b7441a49bfdd93a2426");
+               set.add("5ca4eac1f0b43f03288c19c42e1ccb2b");
+               set.add("5ced682df2330890f2239e8da8d33061");
+               set.add("5d437ac21a6da33b77c980abef5af0ac");
+               set.add("5d4f136bcd4f5f71e0402819f2f666ea");
+               set.add("5d9d43530d78a07475063de4f6b82578");
+               set.add("5e8973f53dfe0e36537e7d88ac84cfaa");
+               set.add("5e8b973df438112549cbd73b38240094");
+               set.add("5ec17176ac8ca3ffe5c7707d4b33aba0");
+               set.add("5ecdf016b2374b2029c58dce498548cf");
+               set.add("5f4d8576e9299aecd4ece33f8b4ffb3d");
+               set.add("5f5bc13ecb72cde7c4c6e89279e836f0");
+               set.add("5fc7b23ca79086fde585ac92b8ddfa61");
+               set.add("5fc8dfad0c6503b16fcbdaf2140f5bd6");
+               set.add("610b21fa92e08d26b0ebbd27ac406558");
+               set.add("618c82b1f690b74209a68062f0b7f50e");
+               set.add("6214548d7b510154960ca3d23da4f38d");
+               set.add("6244a9533207c9f3d89bd48d2c946d72");
+               set.add("628475d3f98ce21920983b2335d29771");
+               set.add("62b5f08d8f9087672b45485f5177b688");
+               set.add("62c0ca2c1be43447418c673a27f69a53");
+               set.add("62dd2d23b56d1991111028754f7d5718");
+               set.add("62fe634a6ec154d4b675a8944ab98a7b");
+               set.add("638e84fef470490263300ed27293aca9");
+               set.add("643181e6ca3418a86b5dac6858805839");
+               set.add("6431d2ee351952b0ca1c8e24aee89d9a");
+               set.add("64cf9d529b625818f06f910fd5a51ebc");
+               set.add("64f5901476b159bd9c4f5ed9aa2b4cc7");
+               set.add("651c5d94aa8b742ea6bf89eb4760d88b");
+               set.add("6535664e59493ee6581d3ec49d666a05");
+               set.add("659bd0331a1348d14e9cd72006008d5b");
+               set.add("659d8f3f58c60862ec21306475d5b86c");
+               set.add("65ed980ed9e129e301e3c219be11999c");
+               set.add("661c50f934f8b021101df91c063c2662");
+               set.add("66289df1c8d41414638696d9847188a7");
+               set.add("667f3707995e365e210a1bb9e1926455");
+               set.add("66ff6174d6a5b1c8a40637c8a3a8a7b9");
+               set.add("673d52c40a3153d07e7a81ad3bf2027c");
+               set.add("67c803799e8e1d877eb3f713dc775444");
+               set.add("680708ce5f383f0c7511eb3d7b7209d9");
+               set.add("68ee06fe147e143b6e1486d292fbc9b4");
+               set.add("690da28e475ebd2dec079e7de4c60719");
+               set.add("693db94b6ffb0c1afa7b82499d17b25f");
+               set.add("6961f9a08f066d0318669a9c9c94017d");
+               set.add("69a38fb26f994ccc04e84e66441e0287");
+               set.add("69f5b82d6acf05cee8615ff5d05f7921");
+               set.add("6a1e040ce59176bcbe4c47654dcf83a7");
+               set.add("6a26a773a6223c79697e12838582f122");
+               set.add("6a6e0e4350ef8d4a3489aa4398bd674b");
+               set.add("6a8abe4a6fe236bf93d9b85681db2c0e");
+               set.add("6aaddb50ae45f1006852479932dfbd54");
+               set.add("6adb0778b8930a8e6a2f1e99de2ef074");
+               set.add("6b54ec7203070bb29e514eb7d684e380");
+               set.add("6b598530a066271059bc94c1fa0cd7a1");
+               set.add("6be4f1c5af0ff30131481d009e87133b");
+               set.add("6be8cb8a938f1ecef2b36c823e8e6ade");
+               set.add("6bfe9b78813cfa4014e630454f7a06a5");
+               set.add("6cb4d52135f005a2c7ba8ccc3b8781e3");
+               set.add("6cd5f8dd36648fcafcfecc8d5b990e9b");
+               set.add("6cfdb07efc0d3c1f36b2d992263253f9");
+               set.add("6d95c9c12fe5498af055b013bf7ceb7d");
+               set.add("6e8f160f1b2b54c3c89b81c4f9a98283");
+               set.add("6eadec5ff4cb05c8ef1a64d2c94d627b");
+               set.add("6eceba3c0a19666f5a9adbc13ceb1ae7");
+               set.add("6f47bff8d62f5bd51cee156f78e8cfcb");
+               set.add("6f484725ba3abcadfe8fbfb4e6e83db6");
+               set.add("7031b89c62590b6a41e7ad524bb0a308");
+               set.add("7058fc792efe7aaddf8a0323bf998f72");
+               set.add("706e502b5a6db25407c2565457944633");
+               set.add("70cfd491765d3e4e6a4e4b1ccaf9c343");
+               set.add("711e7a11c4be36563cae1b71234dc414");
+               set.add("71794c9ad0e60b6d0dcd44b51c3704f0");
+               set.add("7193a4c6f286f7b86b18cc7908498d06");
+               set.add("71b17eeb05fd473e42aa5c4e51e47a15");
+               set.add("71e0014aeaebda1113a12cecb51fd20c");
+               set.add("71e2d06eaa0ab3ae59d0f7b7ef20fc31");
+               set.add("71fe7c7f2a54587c2278b3e630faee56");
+               set.add("729ba9dde60740e6d5e8140fae54f4c6");
+               set.add("72b5be92417a4a6a09f5e01c106cf96a");
+               set.add("72c0c6f5a653bb57a1aba44e7edb202b");
+               set.add("72f6580c0aa3b101acffce59adf7809b");
+               set.add("730ac94082f25931179a313af186b335");
+               set.add("73243d82b8157406558562b5eb70818b");
+               set.add("73371ae751751a5998c3bc8de577b83e");
+               set.add("733c6d4df3b6781473ba0a3a169ca74a");
+               set.add("7376d2568492e6f6c0fadab86e22416b");
+               set.add("737b791d27930ccba061fa36c4046208");
+               set.add("73a47e531e9c0ddf5a334c40508f6361");
+               set.add("73b2859aedfe1bf317841bbc150e0491");
+               set.add("7413c1de6d5f286055e8244101da306c");
+               set.add("741bfaabd99301c5503fd36d17912627");
+               set.add("7423f1b74c8367863a1071bcd0864d42");
+               set.add("747ddec4bc30cbde2ebefac7b8df466c");
+               set.add("7494df6c5102bbfb6e48c9b30281325b");
+               set.add("74c5cb4f540e2c9b27ae60dcc8247eae");
+               set.add("74f6a218f8877fb76f74eacc9df32fc6");
+               set.add("751ff3c7f3e77c8d3ba24c568fd11845");
+               set.add("757c05f3194d6d4b848b83c0e3e5f1a3");
+               set.add("75f724e20c3f2e26d2de13927fbf78f1");
+               set.add("761c6d190a411231fccfeef67f08eacf");
+               set.add("763eddbcb1f6787b3350f65c64b44ba4");
+               set.add("765a1a5d8f09ddffec749d3a6920c4a7");
+               set.add("76acd0d7e4d38152771480bedacba209");
+               set.add("76dc5d4b4f65aacb1dfc1a5a8f61b023");
+               set.add("771cc4503662f2fc2c5f15f46e7e58b6");
+               set.add("772a3193a3cf3e18fd2058c3f45c35f8");
+               set.add("7823311e8d60374d4b37c50d927507c8");
+               set.add("78282abebd03a40f2dd21f1505a5f789");
+               set.add("782e12a60bbef943f13f2fa1af1e39f1");
+               set.add("782e6df5b10a60b26117e0648e76c6c4");
+               set.add("7875a865fbaf33ba617cdb7c7f0f7140");
+               set.add("78ee37b2f7acb3d892e54f0e9d2e0c14");
+               set.add("791f7d21ea119ccd49df31b2f614a0d6");
+               set.add("795036eafd006f62ee5a68ba1c309865");
+               set.add("795fcaf2d6d9e1c37a4c283876f26cec");
+               set.add("79688fa15c424b73454d9cd0a070472f");
+               set.add("796c759040e72b8efd4630754bd3f30b");
+               set.add("798ad9ae186a9d89e6f69e065bc22a86");
+               set.add("7a2a604d923e5bd87846902e47acc438");
+               set.add("7a39ea82b6b2bb75f9f6a7b817dab9cb");
+               set.add("7a62872422cf100af636b434f4a0b307");
+               set.add("7acda66f5d077fa708de7d153882b97c");
+               set.add("7b1aca3caab3a61147d4ebf5f7971d42");
+               set.add("7b5bc0bfd0de5126b4d278aa5775abd7");
+               set.add("7bd0735d3b5d579f0c97e11309a12451");
+               set.add("7be2fb055d29d5c8e42c559295ee8113");
+               set.add("7c14e11e0126b310217db2488f898127");
+               set.add("7c4ab23d9b1db15ea1f49fe017edf346");
+               set.add("7c6080928783078289d9a473efecc134");
+               set.add("7ccde35451846731eff4ae16e40f661f");
+               set.add("7cce66eec1313c11f5b9005db8f2823d");
+               set.add("7d40723bc0200f13675626309559ce6d");
+               set.add("7da7fa494528cd0836f9988f3e7ada96");
+               set.add("7e3bc2bc33f34ad791573e94021381d5");
+               set.add("7fb1e485fa41841779a0a1f95a2b7cd8");
+               set.add("809b63d7a153ee60272ffc224766fd72");
+               set.add("80fc9ff72737286ad64fe7de1524c799");
+               set.add("82b602bacfe681cee58d5530ac9e8b99");
+               set.add("82f69b66499f2bc375467ee933fe4577");
+               set.add("83243e87941f4ec7f8841571dd90b3b2");
+               set.add("836481fe9bfd7612506e0545bdcf279d");
+               set.add("83a498353a59dea68538962eb2642ba8");
+               set.add("83eafb190276935630f43cddf3d78143");
+               set.add("845c54809778f5b838e32443a7d44072");
+               set.add("849b5885cbf965447675610ee1d0dca2");
+               set.add("84a895acdcd487244b6803082036fad7");
+               set.add("84bdf63a67691194e37082e3f7f6d712");
+               set.add("84c99be383e4ada00f4e6bd335774655");
+               set.add("84ed2fb163b5b2525a9a731056ffd144");
+               set.add("8517e14d6f657f1244c0344d4f1a828b");
+               set.add("8541aca6dd06f2dc6984d5e1b664900c");
+               set.add("85cc38b178bd721bf7225144dd301b0f");
+               set.add("85d00ae1ce88ace2bc6918750a97502f");
+               set.add("868af0eab556081846fdbff18df40b28");
+               set.add("871f7fe309f61ec7e45e4b29833349d9");
+               set.add("878e7848ab58bf9271fc04766e969c8f");
+               set.add("87b872efe9433147c61d5d2c3dcca14f");
+               set.add("87cd3518578a2ef76341b33f9c95198f");
+               set.add("87cd3a0a86f398ba1026cdb969e55090");
+               set.add("87cdeb3fcaa050209091a1600ce1df11");
+               set.add("88008ed2e9b600fa2e453390688aaa7e");
+               set.add("8833c25743e0f9725ca22dbc6e54d1bf");
+               set.add("88693556ff80aacd354c1948675c0674");
+               set.add("888664c26a3296f9571d561723b44255");
+               set.add("88ed07b96422ec99767fb35bf6a51966");
+               set.add("88ed43ef6f483b9a7e34c34b46335dea");
+               set.add("8a2e4445364c3c9151dcf4622c6add51");
+               set.add("8a73ce2e18dacf250a961dac49338407");
+               set.add("8ba75b207cc0bee8ec624e9f33019161");
+               set.add("8bc592cc7aaa336637b9c82c43cbb081");
+               set.add("8c1bdef25d6a6df69c303be204748da9");
+               set.add("8c8b182ec0845de4a5fed3245e5601ea");
+               set.add("8c8d724fba729940b1234e58e64125b8");
+               set.add("8ce47ac01efd8c0ab75ae5371ff0f7ba");
+               set.add("8e1600a04363c86199d660ccb7085735");
+               set.add("8eb548ee8bf98a6426f0b5a11e32c88a");
+               set.add("8ec54a8bd1ab30f324eb0f03ef79d097");
+               set.add("8ede1653debc5617deae6a7632c18502");
+               set.add("903594c774fd5be01132f559c00778b4");
+               set.add("9079d8f7488bca4504d58be0bc76deea");
+               set.add("909a1f7458c8f1f1138dff9ce019fb6c");
+               set.add("90b8dd2817509c2374b20a1975ca1a54");
+               set.add("90d0f3d40769a6219608964628d40e55");
+               set.add("9104737f888d85480d0cc9aef8587e77");
+               set.add("9118a19b2abc5d1d624b10f2bceb18bb");
+               set.add("912e499f9a4a55f11133e01b33542ad1");
+               set.add("915fcc373ba5d8a13814f236c1a9e4e5");
+               set.add("918ca652867678984ae1149a3b5467bd");
+               set.add("91fbebd600bbd88370994b739ae8e1f8");
+               set.add("92fc949a982c897ca4a17750e2ee4afd");
+               set.add("93c0446ee508efe75a176035986627cc");
+               set.add("93d4329e22ed50d3502b2d0bc117baa6");
+               set.add("93f33bcfa6201057376a3fe53eb29959");
+               set.add("944b74b5ff9c617312ca2b0868e8cbc2");
+               set.add("94bacf4caccc653a74a68698be0da4bc");
+               set.add("9572f2ed73f01766b0ede9ec3d52715a");
+               set.add("965e3d6087eec8e991175aada15a550a");
+               set.add("967119411833b80c9ea55f0e64dacad6");
+               set.add("968c5025a59e782d96682b09c4e94746");
+               set.add("97824aa7746b63a32ea6d0dedb3e3f84");
+               set.add("97aa914f28281f67ae3ac5222566c2a0");
+               set.add("97f5a198489144a2f306456c1a346f9b");
+               set.add("98a7e979d454d7f46ceb4a4283794d3c");
+               set.add("98ff8ee9107e864d7c52d631070fff3b");
+               set.add("993739fad4a47f34eb65e3ee48d15c09");
+               set.add("99bb411f89eb34ebfa59900de36581fc");
+               set.add("9a13940746bcf4cbe210150833b2a58b");
+               set.add("9a3d7af6ccb7d277e3ed582d8932b5db");
+               set.add("9a76e86b4275099983c5ede78626e0dd");
+               set.add("9a9caad4a9c674daf41b5cb616092501");
+               set.add("9ae6b0ad5010301ea610f49e008adf8c");
+               set.add("9b6033bd4470408ecf2949d88531d6a1");
+               set.add("9bfc7853ff00c7ea0e2f8972dc2595d4");
+               set.add("9c8bdd485912f9d9eaaba3d5872be726");
+               set.add("9cba07b76b4e79c0265deda5df2e2381");
+               set.add("9e082b9bb6c1361965c0f0e06f051acb");
+               set.add("9e24dbadcadc67447af65d571ffaee55");
+               set.add("9e6a5f03a8b524ffa3264a3f32818e1c");
+               set.add("9ead837b9e4f8c922f74ddbff0d2b88a");
+               set.add("9fb7aa659c0475d5dc72bb35567247c9");
+               set.add("a0006978c9a542518b425c0caa67042b");
+               set.add("a01bdd6575c3cad9f9a4cb8aac9c716a");
+               set.add("a02500e28eeb7e56e343607a822e2a7e");
+               set.add("a05c1799e061712459e6c46f533263a6");
+               set.add("a0799831bfb3f9b77b63c03fad39cce0");
+               set.add("a0d4911294ccb20c0920a3cc6705f326");
+               set.add("a11dfa1b02b1671d42b1abc858de2f2e");
+               set.add("a11e237bd6d3c4a4ee8a7ee791444ad3");
+               set.add("a148d83d50cf0852f6c08ceacbea0296");
+               set.add("a1d8b81c03860585fb40613e828c1b2e");
+               set.add("a20c867fdbb93bbe1d1231d9a8ea76c5");
+               set.add("a21e0795fe0977d50a4496ba60e685e1");
+               set.add("a260bb11468a2252a8dedff81f5672fd");
+               set.add("a2b01bf43bc1d403e6ab3a9b71f32484");
+               set.add("a2c15ded3e16d9aa12b9c07f3383c098");
+               set.add("a360659a62e2e34f4edc89ce5e8b8a0c");
+               set.add("a3a985e0ae5a9c96c74b8ee647101439");
+               set.add("a3bf05e31288667a43b4c39cc0604c97");
+               set.add("a427397e35b28706c5b8aa50e8f92a1c");
+               set.add("a432e1b27b7d9814368d8f4071cf2dd0");
+               set.add("a4b4800082feb6fcaf1cd56dda3a28c6");
+               set.add("a4b83742cb45f1dd9130250cd72c460e");
+               set.add("a5a8b20a222bd51b707e50303fdae33a");
+               set.add("a5cf16d12d611ddc5ae1b010083690ad");
+               set.add("a67b1720a7f123bb943c3f1ee74b8f00");
+               set.add("a6b31c2e971254f85e231653abdc3a06");
+               set.add("a6f9fe8c867cbef07730db4d1d461960");
+               set.add("a706de20cf1a31e379d98807d1cb2060");
+               set.add("a7b5467023706646a6b8ce2602bba226");
+               set.add("a7bb7f7f68b538fb778856d4fbda50b7");
+               set.add("a7fee39f2151056370c57d383e486348");
+               set.add("a84a5f90f1083075081f135c74314bff");
+               set.add("a8a6b73342c6a92638e41b86e6838958");
+               set.add("a8f1c8b28c017186778e3074637e52ef");
+               set.add("a90e513d9b2d0f55b875c3229e2d9816");
+               set.add("a9e697026e08d1a8765497a9569b04e6");
+               set.add("aa3218984177ce38bfdf06e38fbaa64b");
+               set.add("aaa0291aa11c304b3a2300d4025db74d");
+               set.add("aad63a3685d9f906a7c6c8716d626c0b");
+               set.add("aafee591c7a3ae5f3c4f44f2d0f8a70f");
+               set.add("ab85503c9acb730fcb9ed2a4dd74e2d7");
+               set.add("ab8ad454409604808d1b444b068e602d");
+               set.add("ac4c8af4d29676c8c79ac9ef180fc5df");
+               set.add("ac4cd34387b04786cc5315b464006ec8");
+               set.add("ac9c443698ac77bcb3a73a959f6ca0f0");
+               set.add("acde934989eba2c7fef7cce095ce85c7");
+               set.add("ad053830e5d0bb7e736ab98a8f3f1612");
+               set.add("ad08d0d2d84298deb08b4c4a1cf62f39");
+               set.add("ad0a1f2424a1b831f9777e638e8f829a");
+               set.add("add039636134cb123908df5053304f3e");
+               set.add("adf89cbcb01a2ec6d4afb24914790a67");
+               set.add("ae1ae7c31f46325ce6a28104fa7070e6");
+               set.add("af8c83664fd6eec8089ef1992aec463f");
+               set.add("afcd59e32572ecb7ebe2d9c993d5fa9d");
+               set.add("b012115b4276791c5049dace174933f7");
+               set.add("b218489d2d4d7ddbfee2f073e238ff30");
+               set.add("b251290e1d8690953d9cc0c1ea3bac6f");
+               set.add("b2843a551894de318b80f790565dcfe3");
+               set.add("b2a414aeb8800edfa8945863ffa5fbc9");
+               set.add("b2d68ad2619bbb15a827b7baca6677b0");
+               set.add("b2fe203ee319ae28b9ccdad26a8f21de");
+               set.add("b33afd95fbd9aae903bbe7cb853cbbf3");
+               set.add("b385f0f86168fea4f5f456b7700a8ffe");
+               set.add("b3bd462a51847d58ed348f17c8718dca");
+               set.add("b3d1befe2272f354b85f0ca5a3162dc8");
+               set.add("b3f50d0da11772487101b44ae8aeb4ac");
+               set.add("b42625f51295803ae1f99daf241c0bd0");
+               set.add("b49cdae29a3394a25058e94b4eb5573c");
+               set.add("b4ce8f683ec172aecf22cf8e516cce05");
+               set.add("b4ffd04e41c1b8757149777a894f33f2");
+               set.add("b5a1510fcf6dd596e87f497bfd5317bb");
+               set.add("b5a75d8c18db0a96a3423e06554122c8");
+               set.add("b5d312d32267bd15ee43f36077feefe9");
+               set.add("b6645bb07f58754b8838d54e24562c06");
+               set.add("b69831350ae6a3dfc18c0c05db0c25a8");
+               set.add("b6b70e569be8de2fdecf285730319735");
+               set.add("b6ee0ea7d82d3d7e0ab8bc62057c0385");
+               set.add("b707a076a44ca9b3e6b8dc1dcde7d877");
+               set.add("b77df6081bbeb4da02c075befb4beb9b");
+               set.add("b7bdcedd416cccc742643e8e244f6282");
+               set.add("b7ea4565381c6dc534cf0af8305f27ac");
+               set.add("b7f3fb01d8c41525b103fc5faba23362");
+               set.add("b80bf674f28284a3614596800ec02b3a");
+               set.add("b81ab08e53854aba9462ebbaee1ff248");
+               set.add("b87e12381d354360f7945299ad92a4d2");
+               set.add("b8bd5737f81fddbaf120ebe43a0010e4");
+               set.add("b92f1e45fdb62c1fd6d7f3e774b4b610");
+               set.add("b9769bfc0d93a570d94fa130de750b1f");
+               set.add("b980c7a501ce63ebb6e20b04348298b7");
+               set.add("b9e4b006db3e1597b21fb7aba13f79c2");
+               set.add("ba031cf2febc03ddbff278200dca45a0");
+               set.add("bb0f54037f5ab70e96c7d8ba7f57ca4b");
+               set.add("bb2eb6b3f7383d7ef521409fa7710385");
+               set.add("bb3eb6a5dbe0582b31f35e5dc2b457a7");
+               set.add("bbc22cc7f6851e06fadfac40a6225419");
+               set.add("bc4d886813fe2eba9ccd7bef0b4d05ca");
+               set.add("bc8162e261658ece13f8bc61aa43ab74");
+               set.add("bc89ec14f4629c3afe1e286b07e588f6");
+               set.add("bccdb576cb50b44ae34806c2a2781c52");
+               set.add("bd10772f1554ccd6e245d6869d84efe8");
+               set.add("bd969e90ff4b1362e2f24553124a66cc");
+               set.add("bde145f138ed13399b8a747da20c1826");
+               set.add("be11a726b56813c4b1aea0574c8302b2");
+               set.add("be1cfa6a82eb4fbf7186efd6ddbb0161");
+               set.add("be5f3dcf0badef84475741cc47e2ddc0");
+               set.add("bf316e6ad7e72f7dc4033207dd033c76");
+               set.add("bfadbf9c4cde6071e54e43a5da64aca9");
+               set.add("c029503ea85e7a7742d826bc184d65ce");
+               set.add("c049499ca03fd69115352e5d4be72de7");
+               set.add("c0524ddd036991b4718f6ab0ab4e4f42");
+               set.add("c056cf25df6751f7bb8a94bc4f64750f");
+               set.add("c0a9c2fd4f0ed2b8f2bdc7f2c0a7a7ce");
+               set.add("c165e480073bcdccb3fad1c5e507159f");
+               set.add("c24ac1ab205eb3fbd1c64841f0f288d6");
+               set.add("c26987c1c7e95810bbb6f2e284861494");
+               set.add("c295b3b2493aff880daac1532a063b72");
+               set.add("c2b18390691697037d5698b683eee361");
+               set.add("c2cd680e3032ce3a70d3bffdb7d0582f");
+               set.add("c2defcfb93d217c4be28aa27ec62978b");
+               set.add("c332adf9d689dcbbb38fead7098781b3");
+               set.add("c4d8f1baafe99501b0d80e8a9c8c3086");
+               set.add("c4e5ca3e96b219539e3e61c3c4fbe5a9");
+               set.add("c5e1448d1fb24ebcef161ee65f21a725");
+               set.add("c60db0ccfc2a22a152f7470505eef8d3");
+               set.add("c65e2561352e75a66b5738268b1d126a");
+               set.add("c69c01ed9b781941561c3a9dcfacf7ca");
+               set.add("c76bb0011d2519fc9e3af85de593e8a9");
+               set.add("c7a946bb164a3f642e4c5f1b7af337f1");
+               set.add("c833820441cbbf28a25d1ea7934ad6f8");
+               set.add("c8762972b9325b7ec040c782aa9414d0");
+               set.add("c8b1563c45f4fd4dc8ba5fafd5c566d2");
+               set.add("c8f2a5a0533de5eae8d1d01da8fcfc1c");
+               set.add("c94045226f625ab9a507552f64892fbe");
+               set.add("ca365baface31f6167328e65a0aec03b");
+               set.add("ca3dc74a6eb57042ea911afa05b1021b");
+               set.add("ca5b57fca35c5bfa4281802b13381d0c");
+               set.add("cab05efb1584bddbc5e4f064c1628a13");
+               set.add("cad2db4a8a73a867a6cdacceec4044ac");
+               set.add("cb6b65a06bbb9ba5536936188a08d836");
+               set.add("cba49b7c8d1982635866a32956861df3");
+               set.add("cbd94e882bdb84ec79ea2bebc1eb4aed");
+               set.add("cc49ecf163d383488b44dbb17dd5b4d9");
+               set.add("cc4bcc37de4d7bf87acea95ac914997e");
+               set.add("cc9300ecd7f2799c750ca3efcde7ce20");
+               set.add("ccccf43f691ed8bb94ac27d3eab98659");
+               set.add("cd0d8952d3e5742f4bf62195e4b385ec");
+               set.add("cd57964f0a86f3c9afbcca44733081d2");
+               set.add("cd80d4f5366cd31ae31e377c636a773a");
+               set.add("cd987a30667f7ff04a5852fd3f15fe3b");
+               set.add("ce16d46a26c771b1adbff14cc7272bf2");
+               set.add("ce43a9cf2d81a1e09237ed2279ca04be");
+               set.add("ce7a500ffd8f5925bea7a48401fae95e");
+               set.add("cf3894401e317e2545d0ae284802291f");
+               set.add("cf779cafecefb6bae5137bb77582e7e2");
+               set.add("cfdbc0be3829a6d55a5403ad92c77bcf");
+               set.add("cff24b5ef480cd58324e927e4ba0ed37");
+               set.add("d05a83622e817871306a3878a9d867e9");
+               set.add("d060e6662cda17c2c523c74963d2d556");
+               set.add("d0b058971d8a20e840de043c68c140b1");
+               set.add("d0c122a8a62cb5728f1423816b26c49f");
+               set.add("d141ed3ad5b33d6f92c976ad8d518b3b");
+               set.add("d15c7bdb5fc7b9316d1cc60a85acdc64");
+               set.add("d1c8ba5392f01f13dfef28e4ecd11cc2");
+               set.add("d1e661c0bfe1c23ca4e91cfa0a40a9d3");
+               set.add("d25a8aef0d42d7a31783e3da52dd4ee8");
+               set.add("d26ef38d4ea39ba85171f444a8947443");
+               set.add("d30b7d8663c7852f2be21d886b79a6eb");
+               set.add("d315c862f290b99466e3d406d3104413");
+               set.add("d4007ee1fd6ede4a148bdca7ab778dd3");
+               set.add("d4805374b5f1ece75c0dd51d6283a0f6");
+               set.add("d55b77d76f6ece8bbd33cb2afdbd040f");
+               set.add("d570cb3cbfe88dfdf086bb1ab9ef76f8");
+               set.add("d587ebc9902ba2620d52a610441470cc");
+               set.add("d6196bfd55d758dd5a4899ce84cea85b");
+               set.add("d6815ec0b3e12fea0f470c01c0da3888");
+               set.add("d68971ffd271de2ddde6ac10c2817451");
+               set.add("d68ff175d25d083eee4f31bf0701a0d8");
+               set.add("d7fca16ff4e4ed99b72046f99533eef3");
+               set.add("d815d7a8dd0446ba21ddbc37d3733f36");
+               set.add("d8d40312d0751951b4c45f2100379950");
+               set.add("d97e0d0fbea2a015d5e7ea229b99a6c3");
+               set.add("d98c6a23fafafb0f9981f2123b1a1938");
+               set.add("d99d6bfaede491ceae109a644cf54098");
+               set.add("da0f523e815ce990a3a5f5b5574cec4a");
+               set.add("da686b7f2a26a36051b273bbabdd7ecc");
+               set.add("dadb54d4a8ba762a8d2a2fad5fcd7582");
+               set.add("db03b5399e1a324660934ad81036d987");
+               set.add("db29f48ac45b41ad389b1b4e8e30f553");
+               set.add("db98a87e4be1e5b59f589335e1509996");
+               set.add("db9be69a1dd929be69406f9e75533fd3");
+               set.add("dba45dfda4d06aebf3adc194ca4ec35d");
+               set.add("dc339714def04ec3e5e280faec8445e5");
+               set.add("dc3fb757715c96f7c629b7712d87ca61");
+               set.add("dc5dabf20b3fbee0d3a0b1d800c74d4f");
+               set.add("dcc7b2c56f358641ea9a73c6a81479f5");
+               set.add("dd922d98ecf5096482946d8671b647e3");
+               set.add("dda8214d7b53392f8ed9fbe4178e90b9");
+               set.add("ddd37af5af0a69bed06bc50cc0a6f4c2");
+               set.add("de8d217fad9883d9bfdee5af01472971");
+               set.add("def34fc0fd41527b300d3181a70cdecf");
+               set.add("df00d9361332c48cba23bfcd41e799d4");
+               set.add("df35772f10769bc28701c488d33e89b2");
+               set.add("df769f9dc2477135b0c4320e7e7b4c2f");
+               set.add("df95377a3f69b8fbe5dcdfa041491717");
+               set.add("df98c3766238aa84f9a9dd92cd73fe72");
+               set.add("dfd019d69302047a67434458d0fa8952");
+               set.add("e037a9e26a8b319437ab7c962714dc56");
+               set.add("e0d2f02d29a965fafd85a4ae6ad37246");
+               set.add("e10e651cd85e41be3402f51885bbf107");
+               set.add("e162d7b2e436ae6d369f4fbaf53af4b4");
+               set.add("e176177a2b64669a6bcd1cf8beb35df2");
+               set.add("e194252a63a3433b5a5278f68678b7dc");
+               set.add("e19d16546c555d073454ea56ece1cbd6");
+               set.add("e1cb375938189d4090b000ab41c77a06");
+               set.add("e1ce2428389f0c2356881e626f161ccc");
+               set.add("e27c22419443eb612af1620f4c8be007");
+               set.add("e2a5adcdd7b01611736b6b74f8c506ee");
+               set.add("e3084721ba7ae53996337e82c5f469ab");
+               set.add("e35f4cccfe57bdd7338dadeba55609f1");
+               set.add("e39a2ef2eaaaf7ba74623f14c917ee1d");
+               set.add("e3e11cf57dc3f1c6ca59acb06370698f");
+               set.add("e4557f7733332200116b753408cdb966");
+               set.add("e48c96d6025b38addad2278f24c963ef");
+               set.add("e48db7db130af48cccb2d830d3cbaa14");
+               set.add("e4d169990e34bfeab0c6a780d6a49d58");
+               set.add("e4ea1f6b01c9cdcf9e550792ed336384");
+               set.add("e4ee8ada1fbbe886fb25a7f484609690");
+               set.add("e54846d325334547923d8b64da70f529");
+               set.add("e56ccf6eca77d62dde88c895abfc1c1a");
+               set.add("e591790779db1c866b179d6f85b09dda");
+               set.add("e5bec4799ceef43816054f92de9652b5");
+               set.add("e5db5832d59e14d6999144fa8cd10e3f");
+               set.add("e611fb9e857f9bee391056e1f971a0aa");
+               set.add("e6321fdd099d70352883b45f6c2a20a9");
+               set.add("e682fc42ee7ecdbf595116293cbe8a6b");
+               set.add("e6ae48418d10883fe9657075b476274d");
+               set.add("e6fa2a139e5c56f8f483aaeeee0b7fbb");
+               set.add("e77d3c78240ec60f7f4dd67a2e71085a");
+               set.add("e78ad15fb1fd450f9221147e458b1abd");
+               set.add("e7a1dc89a6cab821776ea61fe6ba10f4");
+               set.add("e7df074666f6caa44b798342bdab6230");
+               set.add("e7f25163d78c2c658300cd0f9a8a3b04");
+               set.add("e80f22347419025053de7da1f07912ec");
+               set.add("e828123fa3cdf86dc0fe1b5c86d7c87d");
+               set.add("e8764c00097a0a1254f43a16c98a1d7f");
+               set.add("e89df60deddf270cbc2232bbe26420d1");
+               set.add("e8d289f3c1aa961cf4ac8d164e286dde");
+               set.add("e9051eac7829dc1a95987230fb21d2d9");
+               set.add("e90846d2c3e16de5ed5dff4c21356edd");
+               set.add("e954907cdfba1cf07f19f64af5cf45b1");
+               set.add("e96e004e988b8e36b2ab9ed1b0f65649");
+               set.add("e984d3924451d3498a3efccd845a77fe");
+               set.add("e98b4097ddb057755e561c33a0f3428d");
+               set.add("e9a2ba17cc4b93063d61813359fd6798");
+               set.add("ea90b42f6ada6e0ac5d179af4129022d");
+               set.add("eab4231af5b3ffab13f9a04b8aef0fad");
+               set.add("eac17a7d30d24e60e6b2ce10059b37a0");
+               set.add("eaf3af4d0b61df60ee8fe3a808de6ffd");
+               set.add("eb3178d36a578fd8b430ea82429ee145");
+               set.add("eb4fbf9835a3584e6015260f0dff2174");
+               set.add("ec3a9437b1481db4d8cbc3b4fc0888a1");
+               set.add("ec47d133c23dba3d015ae730a1b9659f");
+               set.add("ec6d321581a133fee9766eedff4db9d6");
+               set.add("eca16f6d986bd893af3c4a97b99df623");
+               set.add("ecf09182c51e5110d636828364ba3ee6");
+               set.add("ecfbf6f7017f0981ba6d30331c9dc870");
+               set.add("ed1eaef061f0d30503a64f27d8ea2825");
+               set.add("ed2e4441ad7dcbe0a5d3e62bc31aa9bc");
+               set.add("ed6304572c0f1283fd06f9c09ef32b61");
+               set.add("ed7d650fc0f5de8c9da107e53025f220");
+               set.add("ef405c5b0de638c01cf57c379aaff45b");
+               set.add("ef5ec03860cd32e910a3ddb2d8740300");
+               set.add("efdc8c21ee843f98a0dc08ef694b6db7");
+               set.add("f0111af67e822944e1dc1ab24e5b8458");
+               set.add("f0e8026289bc1f9109b25f4599556aaf");
+               set.add("f0ff5a7aa6f4e8785fa406636618c01d");
+               set.add("f19a809facb46a7a094c17039126eb3e");
+               set.add("f1c7524d454c25cdd837662a80709030");
+               set.add("f202d26f911074235ac8e4a5c1ed4dad");
+               set.add("f2250dd8736aa007a7e2530dca1c6081");
+               set.add("f2e9d36561ed03eb23d38005919625d2");
+               set.add("f303e8a2a96635d2f44c414229c349bb");
+               set.add("f35b8eac398bae58ba622ef643f64aa2");
+               set.add("f3a37dbd51e59af2801272fffe457d64");
+               set.add("f3c4afc965427977685da607f8a6edca");
+               set.add("f468c204661ab47379613a1d03f56571");
+               set.add("f4ff8d1667c95377ac96e870170bfe64");
+               set.add("f585dccae7fae67affbf500ecf9a3839");
+               set.add("f59a4193ec432bd26f780a6b74b8be38");
+               set.add("f5d44e9d1523c3e6834108d9c76b2da9");
+               set.add("f69f58acf2b80f5dc29682c64de8da7f");
+               set.add("f6adafaf98b92438e1ad916e51a65366");
+               set.add("f6f175a7910c5039d0fa51393c920df8");
+               set.add("f71a00225b1abf1dddfcace79d0345a2");
+               set.add("f7446eb342242f011c118bb3439335a0");
+               set.add("f76e6e86c9b0d84638c1c26c55b16cc4");
+               set.add("f775463704e3d13817abd9674d62f468");
+               set.add("f80df9b85c1219fd2030ada584fbfc35");
+               set.add("f843fa1d0cd00122fcbcfd7caf1cb8ca");
+               set.add("f88e05d8303a6e5dfbd60ceed3988d78");
+               set.add("f92dbcd91aac65e0770f5fe947fc5a80");
+               set.add("f9512c5cc198adeff74fed3d4b0f4509");
+               set.add("f9e1ffe33f3676d0e39bc24e63cf3a99");
+               set.add("fa492225fbf03ad58ee88b6d84914583");
+               set.add("fa6279cc58de3fe6c617559684afec4f");
+               set.add("fb2a4db1a1a68dae79653dd2e32ade50");
+               set.add("fb2bc93f011d62ac03aed40d92b26ba2");
+               set.add("fb9b6d2d2d5e3c219e0163092181e014");
+               set.add("fbdc7fc274e5c71226372606beedb706");
+               set.add("fbe25dc54e2761c2c5e9f1f3a98d7f0f");
+               set.add("fbec5f910872b333be655c5143b1cb37");
+               set.add("fc372707722b210b257ef9e2f731edc3");
+               set.add("fcedc7e1d4fc17c7c4f2c6f6c7a820e0");
+               set.add("fcff88f351f2535dcbab726dec9060ee");
+               set.add("fd15d45e5f876ac3ff10cef478077e8b");
+               set.add("fd21ff84af0fe74de102f1985d068dee");
+               set.add("fd30f89057cd8ad151d612def38afb41");
+               set.add("fdab6eed0ecadf924ae128f25e8b1a10");
+               set.add("fdced723077daed39d0e4da044f75d64");
+               set.add("fddbc361461ae318e147e420a805a428");
+               set.add("fdee78ddeb6f567a636b9850f942256f");
+               set.add("fe858217631f3aaf02d8aaf849c7b2c9");
+               set.add("fec4bbfe3563397790d48ce6f5b48260");
+               set.add("ff73d7804897f4e1624a3b5571e48fbb");
+               set.add("ff78c3b27889d5365a03b3a3fd3a4c1e");
+               set.add("ffac65c383eb053e54b267fe4dfd2141");
+               
+               DIGESTS = Collections.unmodifiableSet(set);
+       }
+       
+       private PreferredMotorDigests() {
+               // Prevent instantiation
+       }
+       
+}
index a58a8553fcadfeab53fb43ca9a25778f3a97b3e4..6f3a2dc5fed52bd84e9b925e0a14a305d521f1aa 100644 (file)
@@ -3,16 +3,17 @@
  */
 package net.sf.openrocket.file.rocksim.importt;
 
+import java.io.IOException;
+import java.io.InputStream;
+
 import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.AbstractRocketLoader;
 import net.sf.openrocket.file.RocketLoadException;
-import net.sf.openrocket.file.RocketLoader;
 import net.sf.openrocket.file.simplesax.SimpleSAX;
+
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
-import java.io.IOException;
-import java.io.InputStream;
-
 /**
  * This class is the main entry point for Rocksim design file imported to OpenRocket.  Currently only Rocksim v9
  * file formats are supported, although it is possible that v8 formats will work for most components.
@@ -28,7 +29,7 @@ import java.io.InputStream;
  *          setMaterial
  *          getMaterial
  */
-public class RocksimLoader extends RocketLoader {
+public class RocksimLoader extends AbstractRocketLoader {
        /**
         * This method is called by the default implementations of {@link #load(java.io.File)}
         * and {@link #load(java.io.InputStream)} to load the rocket.
index e22243de09270d4ea2c9ec62c954e55e4ceac1c7..5e37831c405635ed9bdc5546932515c2f9fd5d4e 100644 (file)
@@ -19,7 +19,7 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
        private static final int DEFAULT_BORDER_PIXELS_WIDTH = 30;
        private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20;
        
-
+       
        protected final double dpi;
        
        protected double scale = 1.0;
@@ -41,7 +41,7 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
        }
        
        
-
+       
        public abstract void updateFigure();
        
        public abstract double getFigureWidth();
@@ -90,6 +90,11 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
                
                double s = Math.min(zh, zv) / dpi * 0.0254 - 0.001;
                
+               // Restrict to 100%
+               if (s > 1.0) {
+                       s = 1.0;
+               }
+               
                setScaling(s);
        }
        
@@ -124,8 +129,8 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
                // Copy the list before iterating to prevent concurrent modification exceptions.
                EventListener[] list = listeners.toArray(new EventListener[0]);
                for (EventListener l : list) {
-                       if ( l instanceof StateChangeListener ) {
-                               ((StateChangeListener)l).stateChanged(changeEvent);
+                       if (l instanceof StateChangeListener) {
+                               ((StateChangeListener) l).stateChanged(changeEvent);
                        }
                }
        }