]> git.gag.com Git - debian/openrocket/commitdiff
Refactored the rocksim csv component parser system to allow for more flexible column...
authorkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 26 Apr 2012 21:10:34 +0000 (21:10 +0000)
committerkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 26 Apr 2012 21:10:34 +0000 (21:10 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@621 180e2498-e6e9-4542-8430-84ac67f01cd8

21 files changed:
core/src/net/sf/openrocket/preset/loader/BaseColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/BaseComponentLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/BaseUnitColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/BodyTubeLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/BulkHeadLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/CenteringRingLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/DoubleColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/DoubleUnitColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/EngineBlockLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/ManufacturerColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/MaterialColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/MaterialLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/NoseConeLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/RocksimComponentFileColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java
core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/ShapeColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/StringColumnParser.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/TransitionLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/loader/TubeCouplerLoader.java [new file with mode: 0644]
core/src/net/sf/openrocket/preset/xml/OpenRocketComponentSaver.java

diff --git a/core/src/net/sf/openrocket/preset/loader/BaseColumnParser.java b/core/src/net/sf/openrocket/preset/loader/BaseColumnParser.java
new file mode 100644 (file)
index 0000000..9c95197
--- /dev/null
@@ -0,0 +1,42 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Locale;
+
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+
+public abstract class BaseColumnParser implements RocksimComponentFileColumnParser {
+
+       protected String columnHeader;
+       protected boolean isConfigured = false;
+       protected int columnIndex;
+
+       public BaseColumnParser(String columnHeader) {
+               super();
+               this.columnHeader = columnHeader.toLowerCase(Locale.US);
+       }
+
+       @Override
+       public void configure(String[] headers) {
+               if ( headers == null ) {
+                       return;
+               }
+               for( int i =0; i< headers.length; i++ ) {
+                       if ( columnHeader.equals(headers[i].toLowerCase(Locale.US))) {
+                               columnIndex = i;
+                               isConfigured = true;
+                               return;
+                       }
+               }
+       }
+
+       @Override
+       final public void parse(String[] data, TypedPropertyMap props) {
+               if ( isConfigured ) {
+                       doParse(data[columnIndex], data, props);
+               }
+       }
+       
+       protected abstract void doParse(String columnData, String[] data, TypedPropertyMap props );
+       
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/BaseComponentLoader.java b/core/src/net/sf/openrocket/preset/loader/BaseComponentLoader.java
new file mode 100644 (file)
index 0000000..6f39709
--- /dev/null
@@ -0,0 +1,47 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPresetFactory;
+import net.sf.openrocket.preset.InvalidComponentPresetException;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public abstract class BaseComponentLoader extends RocksimComponentFileLoader {
+
+       List<ComponentPreset> presets;
+
+       public BaseComponentLoader(Map<String, Material> materials) {
+               super();
+               presets = new ArrayList<ComponentPreset>();
+
+               fileColumns.add( new ManufacturerColumnParser() );
+               fileColumns.add( new StringColumnParser("Part No.", ComponentPreset.PARTNO));
+               fileColumns.add( new StringColumnParser("Desc.", ComponentPreset.DESCRIPTION));
+               fileColumns.add(new MaterialColumnParser(materials));
+               fileColumns.add(new DoubleUnitColumnParser("Mass","Mass units",ComponentPreset.MASS));
+
+       }
+
+       protected abstract ComponentPreset.Type getComponentPresetType();
+       
+       public List<ComponentPreset> getPresets() {
+               return presets;
+       }
+       
+       @Override
+       protected void postProcess(TypedPropertyMap props) {
+               try {
+                       props.put(ComponentPreset.TYPE, getComponentPresetType());
+                       ComponentPreset preset = ComponentPresetFactory.create(props);
+                       presets.add(preset);
+               } catch ( InvalidComponentPresetException ex ) {
+                       System.err.println(ex.getMessage());
+                       System.err.println(props);
+               }
+       }
+
+}
\ No newline at end of file
diff --git a/core/src/net/sf/openrocket/preset/loader/BaseUnitColumnParser.java b/core/src/net/sf/openrocket/preset/loader/BaseUnitColumnParser.java
new file mode 100644 (file)
index 0000000..4cdb398
--- /dev/null
@@ -0,0 +1,53 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+
+
+public abstract class BaseUnitColumnParser extends BaseColumnParser {
+
+       protected String unitHeader;
+       protected int unitIndex;
+       protected boolean unitConfigured;
+       
+       protected static Map<String,Unit> rocksimUnits;
+       
+       static {
+               rocksimUnits = new HashMap<String,Unit>();
+               rocksimUnits.put("0", UnitGroup.UNITS_LENGTH.getUnit("in"));
+               rocksimUnits.put("1", UnitGroup.UNITS_LENGTH.getUnit("mm"));
+       }
+
+       protected String mungeUnitNameString( String name ) {
+               String newString = name.toLowerCase(Locale.US);
+               return newString.replace(".", "");
+       }
+       
+       public BaseUnitColumnParser(String columnHeader, String unitHeader) {
+               super(columnHeader);
+               this.unitHeader = unitHeader.toLowerCase(Locale.US);
+       }
+
+       @Override
+       public void configure(String[] headers) {
+               // super configure will set columnIndex;
+               super.configure(headers);
+               
+               // This indicates the actual dimension column was found.
+               if ( isConfigured ) {
+                       // Look for the unit column proceeding it
+                       for( int i=columnIndex-1; i>=0; i-- ) {
+                               if ( unitHeader.equals(headers[i].toLowerCase(Locale.US))) {
+                                       unitConfigured = true;
+                                       unitIndex = i;
+                                       return;
+                               }
+                       }
+               }
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/BodyTubeLoader.java b/core/src/net/sf/openrocket/preset/loader/BodyTubeLoader.java
new file mode 100644 (file)
index 0000000..364dd86
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+
+public class BodyTubeLoader extends BaseComponentLoader {
+
+       public BodyTubeLoader(Map<String, Material> materials) {
+               super(materials);
+               fileColumns.add(new DoubleUnitColumnParser("ID","Units",ComponentPreset.INNER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("OD","Units",ComponentPreset.OUTER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Length","Units",ComponentPreset.LENGTH));
+
+       }
+
+       
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.BODY_TUBE;
+       }
+
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.BODY_TUBE;
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/BulkHeadLoader.java b/core/src/net/sf/openrocket/preset/loader/BulkHeadLoader.java
new file mode 100644 (file)
index 0000000..0ebafa2
--- /dev/null
@@ -0,0 +1,37 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class BulkHeadLoader extends BaseComponentLoader {
+
+       public BulkHeadLoader(Map<String, Material> materials) {
+               super(materials);
+               fileColumns.add(new DoubleUnitColumnParser("OD","Units",ComponentPreset.OUTER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Length","Units",ComponentPreset.LENGTH));
+
+       }
+       
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.BULK_HEAD;
+       }
+
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.BULKHEAD;
+       }
+
+       @Override
+       protected void postProcess(TypedPropertyMap props) {
+               props.put(ComponentPreset.FILLED, true);
+               super.postProcess(props);
+       }
+
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/CenteringRingLoader.java b/core/src/net/sf/openrocket/preset/loader/CenteringRingLoader.java
new file mode 100644 (file)
index 0000000..58cf497
--- /dev/null
@@ -0,0 +1,25 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+
+public class CenteringRingLoader extends BodyTubeLoader {
+
+       public CenteringRingLoader(Map<String, Material> materials) {
+               super(materials);
+       }
+
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.CENTERING_RING;
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.CENTERING_RING;
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/DoubleColumnParser.java b/core/src/net/sf/openrocket/preset/loader/DoubleColumnParser.java
new file mode 100644 (file)
index 0000000..e4f5413
--- /dev/null
@@ -0,0 +1,21 @@
+package net.sf.openrocket.preset.loader;
+
+import net.sf.openrocket.preset.TypedKey;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class DoubleColumnParser extends BaseColumnParser {
+
+       private TypedKey<Double> propKey;
+       
+       public DoubleColumnParser(String columnHeader, TypedKey<Double> propKey) {
+               super(columnHeader);
+               this.propKey = propKey;
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+               double value = Double.valueOf(columnData);
+               props.put(propKey, value);
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/DoubleUnitColumnParser.java b/core/src/net/sf/openrocket/preset/loader/DoubleUnitColumnParser.java
new file mode 100644 (file)
index 0000000..f997917
--- /dev/null
@@ -0,0 +1,55 @@
+package net.sf.openrocket.preset.loader;
+
+import net.sf.openrocket.preset.TypedKey;
+import net.sf.openrocket.preset.TypedPropertyMap;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+
+public class DoubleUnitColumnParser extends BaseUnitColumnParser {
+
+       private TypedKey<Double> propKey;
+
+       public DoubleUnitColumnParser(String columnHeader, String unitHeader,
+                       TypedKey<Double> propKey) {
+               super(columnHeader, unitHeader);
+               this.propKey = propKey;
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+               try {
+                       if (columnData == null || "".equals(columnData) ) {
+                               return;
+                       }
+                       double value = Double.valueOf(columnData);
+                       
+                       if ( unitConfigured ) {
+                               String unitName = data[unitIndex];
+                               
+                               Unit unit = rocksimUnits.get(unitName);
+                               if ( unit == null ) {
+                                       if ( unitName == null || "" .equals(unitName) ) {
+                                               // Hmm no data...  Lets assume SI
+                                               if ( propKey.getUnitGroup() == UnitGroup.UNITS_LENGTH ) {
+                                                       unit = UnitGroup.UNITS_LENGTH.getUnit("in");
+                                               } else {
+                                                       unit= UnitGroup.UNITS_MASS.getUnit("oz");
+                                               }
+                                       } else {
+                                               unitName = super.mungeUnitNameString(unitName);
+                                               UnitGroup group = propKey.getUnitGroup();
+                                               unit = group.getUnit(unitName);
+                                       }
+                               }
+                               
+                               value = unit.fromUnit(value);
+                       }
+                       
+                       props.put(propKey, value);
+               }
+               catch ( NumberFormatException nex) {
+               }
+       }
+
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/EngineBlockLoader.java b/core/src/net/sf/openrocket/preset/loader/EngineBlockLoader.java
new file mode 100644 (file)
index 0000000..bcdb0fe
--- /dev/null
@@ -0,0 +1,25 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+
+public class EngineBlockLoader extends BodyTubeLoader {
+
+       public EngineBlockLoader(Map<String, Material> materials) {
+               super(materials);
+       }
+
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.ENGINE_BLOCK;
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.ENGINE_BLOCK;
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/ManufacturerColumnParser.java b/core/src/net/sf/openrocket/preset/loader/ManufacturerColumnParser.java
new file mode 100644 (file)
index 0000000..7d019c0
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.preset.loader;
+
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class ManufacturerColumnParser extends BaseColumnParser {
+
+       public ManufacturerColumnParser() {
+               super("Mfg.");
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+               Manufacturer m = Manufacturer.getManufacturer(columnData);
+               props.put(ComponentPreset.MANUFACTURER, m);
+
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/MaterialColumnParser.java b/core/src/net/sf/openrocket/preset/loader/MaterialColumnParser.java
new file mode 100644 (file)
index 0000000..c6ef8c5
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Collections;
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class MaterialColumnParser extends BaseColumnParser {
+
+       private Map<String,Material> materialMap = Collections.<String,Material>emptyMap();
+       
+       // FIXME - BULK vs other types.
+       
+       public MaterialColumnParser(Map<String,Material> materialMap) {
+               super("Material");
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+
+               Material m = materialMap.get(columnData);
+               if ( m == null ) {
+                       m = new Material.Bulk(columnData, 0.0, true);
+               }
+               props.put(ComponentPreset.MATERIAL, m);
+               
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/MaterialLoader.java b/core/src/net/sf/openrocket/preset/loader/MaterialLoader.java
new file mode 100644 (file)
index 0000000..c8edaf6
--- /dev/null
@@ -0,0 +1,62 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.TypedKey;
+import net.sf.openrocket.preset.TypedPropertyMap;
+import net.sf.openrocket.util.BugException;
+
+public class MaterialLoader extends RocksimComponentFileLoader {
+
+       private Map<String,Material> materialMap = new HashMap<String,Material>();
+       
+       private final static TypedKey<String> MATERIALNAME = new TypedKey<String>("MaterialName", String.class);
+       private final static TypedKey<String> UNITS = new TypedKey<String>("Units", String.class);
+       private final static TypedKey<Double> DENSITY = new TypedKey<Double>("Density", Double.class);
+       
+       public MaterialLoader() {
+               super();
+               fileColumns.add( new StringColumnParser("Material Name", MATERIALNAME) );
+               fileColumns.add( new StringColumnParser("Units", UNITS));
+               fileColumns.add( new DoubleColumnParser("Density", DENSITY));
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.MATERIAL;
+       }
+
+       public Map<String, Material> getMaterialMap() {
+               return materialMap;
+       }
+
+       @Override
+       protected void postProcess(TypedPropertyMap props) {
+               String name = props.get(MATERIALNAME);
+               String unit = props.get(UNITS);
+               double density = props.get(DENSITY);
+               
+               String cleanedMaterialName = stripAll(name, '"').trim();
+               
+               if ( "g/cm".equals( unit ) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Line(cleanedMaterialName, 0.1d * density, true));
+               } else if ( "g/cm2".equals(unit) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Surface(cleanedMaterialName, 10.0d * density, true));
+               } else if ( "g/cm3".equals(unit) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Bulk(cleanedMaterialName, 1000.0d * density, true));
+               } else if ( "kg/m3".equals(unit) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Bulk(cleanedMaterialName, density, true));
+               } else if ( "lb/ft3".equals(unit) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Bulk(cleanedMaterialName, 16.0184634d * density, true));
+               } else if ( "oz/in".equals(unit) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Line(cleanedMaterialName, 1.11612296d * density, true));
+               } else if ( "oz/in2".equals(unit ) ) {
+                       materialMap.put( cleanedMaterialName, new Material.Surface(cleanedMaterialName, 43.94184876d * density, true));
+               } else {
+                       throw new BugException("Unknown unit in Materials file: " + unit);
+               }
+       }
+
+}
\ No newline at end of file
diff --git a/core/src/net/sf/openrocket/preset/loader/NoseConeLoader.java b/core/src/net/sf/openrocket/preset/loader/NoseConeLoader.java
new file mode 100644 (file)
index 0000000..5e774a5
--- /dev/null
@@ -0,0 +1,45 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class NoseConeLoader extends BaseComponentLoader {
+
+       public NoseConeLoader(Map<String, Material> materials) {
+               super(materials);
+               fileColumns.add(new DoubleUnitColumnParser("Outer Dia","Units",ComponentPreset.AFT_OUTER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Length","Units",ComponentPreset.LENGTH));
+               fileColumns.add(new DoubleUnitColumnParser("Insert Length","Units",ComponentPreset.AFT_SHOULDER_LENGTH));
+               fileColumns.add(new DoubleUnitColumnParser("Insert OD","Units",ComponentPreset.AFT_SHOULDER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Thickness","Units",ComponentPreset.THICKNESS));
+               fileColumns.add(new ShapeColumnParser() );
+       }
+
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.NOSE_CONE;
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.NOSE_CONE;
+       }
+
+       @Override
+       protected void postProcess(TypedPropertyMap props) {
+
+               if ( props.containsKey( ComponentPreset.THICKNESS )) {
+                       double thickness = props.get(ComponentPreset.THICKNESS);
+                       if ( thickness == 0d ) {
+                               props.remove( ComponentPreset.THICKNESS );
+                               props.put(ComponentPreset.FILLED, true);
+                       }
+               }
+               super.postProcess(props);
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileColumnParser.java b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileColumnParser.java
new file mode 100644 (file)
index 0000000..cfaf091
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.preset.loader;
+
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public interface RocksimComponentFileColumnParser {
+
+       /**
+        * Examine the array of column headers and configure parsing for this type. 
+        * 
+        * @param headers
+        */
+       public void configure( String[] headers );
+       
+       /**
+        * Examine the data array, parse the appropriate data and push into props.
+        * 
+        * @param data
+        * @param props
+        */
+       public void parse( String[] data, TypedPropertyMap props );
+       
+}
index 4bf2952df1fa5f194e13ab33dd12ea64ee253c2d..bef1b4fe0ab82c9550606c9dcd42f5259c223493 100644 (file)
@@ -1,47 +1,40 @@
 package net.sf.openrocket.preset.loader;
 
-import au.com.bytecode.opencsv.CSVReader;
-import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.file.preset.ColumnDefinition;
-import net.sf.openrocket.file.rocksim.RocksimNoseConeCode;
-import net.sf.openrocket.gui.print.PrintUnit;
-import net.sf.openrocket.gui.util.SwingPreferences;
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.preset.ComponentPreset;
-import net.sf.openrocket.preset.ComponentPresetFactory;
-import net.sf.openrocket.preset.InvalidComponentPresetException;
-import net.sf.openrocket.preset.TypedKey;
-import net.sf.openrocket.preset.TypedPropertyMap;
-import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.ArrayList;
-import net.sf.openrocket.util.BugException;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
+
+import net.sf.openrocket.gui.print.PrintUnit;
+import net.sf.openrocket.preset.TypedPropertyMap;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.ArrayList;
+import au.com.bytecode.opencsv.CSVReader;
 
 /**
  * Primary entry point for parsing component CSV files that are in Rocksim format.
  */
-public class RocksimComponentFileLoader {
-
-    /**
-     * Common unit of measure key.  Rocksim format allows different types of units.
-     */
-    public final static TypedKey<String> UNITS_OF_MEASURE = new TypedKey<String>("Units", String.class);
-
-    /**
+public abstract class RocksimComponentFileLoader {
+
+       public static String basePath = "";
+       
+       protected List<RocksimComponentFileColumnParser> fileColumns;
+       
+    public RocksimComponentFileLoader() {
+               super();
+               fileColumns = new ArrayList<RocksimComponentFileColumnParser>();
+       }
+
+    protected abstract RocksimComponentFileType getFileType();
+    
+    public void load() {
+       load( getFileType() );
+    }
+       /**
      * Read a comma separated component file and return the parsed contents as a list of string arrays.  Not for
      * production use - just here for smoke testing.
      *
@@ -50,8 +43,23 @@ public class RocksimComponentFileLoader {
      *         component data file; the element in the list itself is an array of String, where each item in the array
      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
      */
-    public static List<String[]> load(RocksimComponentFileType type) {
-        return load(RocksimComponentFileLoader.class.getResourceAsStream("/giantleaprocketry/" + type.getDefaultFileName()));
+    private void load(RocksimComponentFileType type) {
+       File dir = new File(basePath);
+       if ( !dir.exists() ) {
+               throw new IllegalArgumentException( basePath + " does not exist" );
+       }
+       if ( !dir.isDirectory() ) {
+               throw new IllegalArgumentException( basePath + " is not directory" );
+       }
+       if ( !dir.canRead() ) {
+               throw new IllegalArgumentException ( basePath + " is not readable" );
+       }
+       try {
+               FileInputStream fis = new FileInputStream( new File(dir, type.getDefaultFileName()));
+               load(fis);
+       } catch (FileNotFoundException ex) {
+               // FIXME?
+       }
     }
 
     /**
@@ -62,8 +70,8 @@ public class RocksimComponentFileLoader {
      *         component data file; the element in the list itself is an array of String, where each item in the array
      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
      */
-    public static List<String[]> load(File file) throws FileNotFoundException {
-        return load(new FileInputStream(file));
+    private void load(File file) throws FileNotFoundException {
+        load(new FileInputStream(file));
     }
 
     /**
@@ -74,9 +82,9 @@ public class RocksimComponentFileLoader {
      *         component data file; the element in the list itself is an array of String, where each item in the array
      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
      */
-    public static List<String[]> load(InputStream is) {
+    private void load(InputStream is) {
         if (is == null) {
-            return new ArrayList<String[]>();
+               return;
         }
         InputStreamReader r = null;
         try {
@@ -86,10 +94,21 @@ public class RocksimComponentFileLoader {
             CSVReader reader = new CSVReader(r, ',', '\'', '\\');
 
             //Read and throw away the header row.
-            reader.readNext();
-
+            parseHeaders(reader.readNext());
+
+            String[] data = null;
+            while( (data = reader.readNext()) != null  ) {
+               // detect empty lines and skip:
+               if ( data.length == 0 ) {
+                       continue;
+               }
+               if ( data.length == 1 && "".equals(data[0].trim())) {
+                       continue;
+               }
+               parseData(data);
+            }
             //Read the rest of the file as data rows.
-            return reader.readAll();
+            return;
         }
         catch (IOException e) {
         }
@@ -103,9 +122,43 @@ public class RocksimComponentFileLoader {
             }
         }
 
-        return new ArrayList<String[]>();
     }
 
+    protected void parseHeaders( String[] headers ) {
+       for( RocksimComponentFileColumnParser column : fileColumns ) {
+               column.configure(headers);
+       }
+    }
+    
+    protected void parseData( String[] data ) {
+       if ( data == null || data.length == 0 ) {
+               return;
+       }
+       TypedPropertyMap props = new TypedPropertyMap();
+       
+       preProcess( data );
+       
+       for( RocksimComponentFileColumnParser column : fileColumns ) {
+               column.parse(data, props);
+       }
+       postProcess( props );
+    }
+    
+    protected void preProcess( String[] data ) {
+       for( int i = 0; i< data.length; i++ ) {
+               String d = data[i];
+            if (d == null) {
+                continue;
+            }
+            d = d.trim();
+            d = stripAll(d, '"');
+
+            data[i] = d;
+       }
+    }
+    
+    protected abstract void postProcess( TypedPropertyMap props );
+
     /**
      * Rocksim CSV units are either inches or mm.  A value of 0 or "in." indicate inches.  A value of 1 or "mm" indicate
      * millimeters.
@@ -113,7 +166,7 @@ public class RocksimComponentFileLoader {
      * @param units the value from the file
      * @return true if it's inches
      */
-    private static boolean isInches(String units) {
+    protected static boolean isInches(String units) {
         String tmp = units.trim().toLowerCase();
         return "0".equals(tmp) || tmp.startsWith("in");
     }
@@ -125,7 +178,7 @@ public class RocksimComponentFileLoader {
      * @param value the original value within the CSV file
      * @return the value in meters
      */
-    private static double convertLength(String units, double value) {
+    protected static double convertLength(String units, double value) {
         if (isInches(units)) {
             return PrintUnit.INCHES.toMeters(value);
         }
@@ -133,6 +186,14 @@ public class RocksimComponentFileLoader {
             return PrintUnit.MILLIMETERS.toMeters(value);
         }
     }
+    
+    protected static double convertMass(String units, double value ) {
+       if ( "oz".equals(units) ) {
+               Unit u = UnitGroup.UNITS_MASS.getUnit(2);
+               return u.fromUnit(value);
+       }
+       return value;
+    }
 
     /**
      * Remove all occurrences of the given character.  Note: this is done because some manufacturers embed double
@@ -143,7 +204,7 @@ public class RocksimComponentFileLoader {
      * @param toBeRemoved the character to remove
      * @return target, minus every occurrence of toBeRemoved
      */
-    private static String stripAll(String target, Character toBeRemoved) {
+    protected static String stripAll(String target, Character toBeRemoved) {
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < target.length(); i++) {
             Character c = target.charAt(i);
@@ -163,7 +224,7 @@ public class RocksimComponentFileLoader {
      * @param target the target string to be operated upon
      * @return target, with the first letter of each word in uppercase
      */
-    private static String toCamelCase(String target) {
+    protected static String toCamelCase(String target) {
         StringBuilder sb = new StringBuilder();
         String[] t = target.split("[ ]");
         if (t != null && t.length > 0) {
@@ -179,447 +240,6 @@ public class RocksimComponentFileLoader {
         }
     }
 
-    /**
-     * The core loading method, shared by all component types.
-     *
-     * @param theData     the data as read from the CSV file
-     * @param keyMap      the list of typed keys that specify the preset's expected columns
-     * @param materialMap a map of material name to OR Material; this is sourced from a MATERIAL.CSV file that must
-     *                    accompany the component CSV file.
-     * @param type        the kind of component
-     * @return a collection of preset's
-     */
-    private static Collection<ComponentPreset> commonLoader(final List<String[]> theData,
-                                                            final List<TypedKey<?>> keyMap,
-                                                            final Map<String, Material> materialMap,
-                                                            final ComponentPreset.Type type) {
-        Collection<ComponentPreset> result = new ArrayList<ComponentPreset>();
-        List<TypedPropertyMap> templates = new java.util.ArrayList<TypedPropertyMap>();
-        Set<String> favorites = Application.getPreferences().getComponentFavorites();
-        Integer uom = null;
-
-        ColumnDefinition[] columns = new ColumnDefinition[keyMap.size()];
-        for (int i = 0; i < keyMap.size(); i++) {
-            TypedKey key = keyMap.get(i);
-            if (key != null) {
-                columns[i] = new ColumnDefinition(key);
-                if (key.getName().equals("Units")) {
-                    uom = i;
-                }
-            }
-        }
-
-        for (int i = 0; i < theData.size(); i++) {
-            String[] item = theData.get(i);
-            TypedPropertyMap preset = new TypedPropertyMap();
-
-            for (int j = 0; j < columns.length; j++) {
-                if (j < item.length) {
-                    String value = item[j];
-                    if (value == null) {
-                        continue;
-                    }
-                    value = value.trim();
-                    value = stripAll(value, '"');
-                    if (value.length() == 0) {
-                        continue;
-                    }
-                    final TypedKey typedKey = columns[j].getKey();
-                    //If it's the material, then pull it out of our internal map.  The map references the
-                    //data from the associated MATERIAL.CSV file that is mandatory.
-                    if (typedKey.equals(ComponentPreset.MATERIAL)) {
-                        preset.put(ComponentPreset.MATERIAL, materialMap.get(value));
-                    }
-                    //The shape of a nosecone or transition must get mapped from Rocksim to OR.
-                    else if (typedKey.equals(ComponentPreset.SHAPE)) {
-                        preset.put(ComponentPreset.SHAPE, RocksimNoseConeCode.fromShapeNameOrCode(value).asOpenRocket());
-                    }
-                    else {
-                        //Rocksim allows different types of length units.  They must be converted and normalized to OR.
-                        final UnitGroup unitGroup = typedKey.getUnitGroup();
-                        if (unitGroup != null && unitGroup.equals(UnitGroup.UNITS_LENGTH)) {
-                            columns[j].setProperty(preset, convertLength(item[uom], Double.valueOf(value)));
-                        }
-                        else {
-                            columns[j].setProperty(preset, value);
-                        }
-                    }
-                }
-            }
-            //Set what kind of component this is.
-            preset.put(ComponentPreset.TYPE, type);
-            //Add to the collection.
-            templates.add(preset);
-        }
-
-        for (TypedPropertyMap o : templates) {
-            try {
-                ComponentPreset preset = ComponentPresetFactory.create(o);
-                if (favorites.contains(preset.preferenceKey())) {
-                    preset.setFavorite(true);
-                }
-                result.add(preset);
-            }
-            catch (InvalidComponentPresetException ex) {
-                throw new BugException(ex);
-            }
-        }
-
-        return result;
-    }
-
-    static class BodyTubeLoader {
-        private final static int MFG_INDEX = 0;
-        private final static int PART_NO_INDEX = 1;
-        private final static int DESCRIPTION_INDEX = 2;
-        private final static int UNITS_INDEX = 3;
-        private final static int ID_INDEX = 4;
-        private final static int OD_INDEX = 5;
-        private final static int LENGTH_INDEX = 6;
-        private final static int MATERIAL_INDEX = 7;
-
-        public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
-
-        static {
-            keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
-            keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
-            keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
-            keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(ID_INDEX, ComponentPreset.INNER_DIAMETER);
-            keyMap.add(OD_INDEX, ComponentPreset.OUTER_DIAMETER);
-            keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
-            keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BODY_TUBE);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
-        }
-
-    }
-
-    /**
-     * Tube coupler parser.  Although there are additional fields in the file, they are not used by
-     * most (any?) manufacturers so we ignore them entirely.
-     */
-    static class TubeCouplerLoader extends BodyTubeLoader {
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TUBE_COUPLER);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
-        }
-    }
-
-    /**
-     * Engine block parser.  Although there are additional fields in the file, they are not used by
-     * most (any?) manufacturers so we ignore them entirely.
-     */
-    static class EngineBlockLoader extends BodyTubeLoader {
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.ENGINE_BLOCK);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
-        }
-    }
-
-
-    static class BulkheadLoader extends BodyTubeLoader {
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BULKHEAD);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
-        }
-    }
-
-    static class CenteringRingLoader extends BodyTubeLoader {
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.CENTERING_RING);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
-        }
-    }
-
-    static class NoseConeLoader {
-        public static final int MFG_INDEX = 0;
-        public static final int PART_NO_INDEX = 1;
-        public static final int DESCRIPTION_INDEX = 2;
-        public static final int UNITS_INDEX = 3;
-        public static final int LENGTH_INDEX = 4;
-        public static final int OUTER_DIA_INDEX = 5;
-        public static final int LD_RATIO_INDEX = 6;
-        public static final int INSERT_LENGTH_INDEX = 7;
-        public static final int INSERT_OD_INDEX = 8;
-        public static final int THICKNESS_INDEX = 9;
-        public static final int SHAPE_INDEX = 10;
-        public static final int CONFIG_INDEX = 11;
-        public static final int MATERIAL_INDEX = 12;
-        public static final int CG_LOC_INDEX = 13;
-        public static final int MASS_UNITS_INDEX = 14;
-        public static final int MASS_INDEX = 15;
-        public static final int BASE_EXT_LEN_INDEX = 16;
-
-        public final static TypedKey<Double> LD_RATIO = new TypedKey<Double>("Len/Dia Ratio", Double.class);
-        public final static TypedKey<Double> BASE_EXT_LEN = new TypedKey<Double>("Base Ext Len", Double.class, UnitGroup.UNITS_LENGTH);
-        public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
-        public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
-        public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(17);
-
-        static {
-            keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
-            keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
-            keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
-            keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
-            keyMap.add(OUTER_DIA_INDEX, ComponentPreset.AFT_OUTER_DIAMETER);
-            keyMap.add(LD_RATIO_INDEX, LD_RATIO);
-            keyMap.add(INSERT_LENGTH_INDEX, ComponentPreset.AFT_SHOULDER_LENGTH);
-            keyMap.add(INSERT_OD_INDEX, ComponentPreset.AFT_SHOULDER_DIAMETER);
-            keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
-            keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
-            keyMap.add(CONFIG_INDEX, CONFIG);
-            keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
-            keyMap.add(CG_LOC_INDEX, CG_LOC);
-            keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(MASS_INDEX, ComponentPreset.MASS);
-            keyMap.add(BASE_EXT_LEN_INDEX, BASE_EXT_LEN);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.NOSE_CONE);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
-        }
-    }
-
-    static class TransitionLoader {
-        public static final int MFG_INDEX = 0;
-        public static final int PART_NO_INDEX = 1;
-        public static final int DESCRIPTION_INDEX = 2;
-        public static final int UNITS_INDEX = 3;
-        public static final int FRONT_INSERT_LENGTH_INDEX = 4;
-        public static final int FRONT_INSERT_OD_INDEX = 5;
-        public static final int FRONT_OD_INDEX = 6;
-        public static final int LENGTH_INDEX = 7;
-        public static final int REAR_OD_INDEX = 8;
-        public static final int CORE_DIA_INDEX = 9;
-        public static final int REAR_INSERT_LENGTH_INDEX = 10;
-        public static final int REAR_INSERT_OD_INDEX = 11;
-        public static final int THICKNESS_INDEX = 12;
-        public static final int CONFIG_INDEX = 13;
-        public static final int MATERIAL_INDEX = 14;
-        public static final int CG_LOC_INDEX = 15;
-        public static final int MASS_UNITS_INDEX = 16;
-        public static final int MASS_INDEX = 17;
-        public static final int SHAPE_INDEX = 18;
-
-        public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
-        public final static TypedKey<String> IGNORE = new TypedKey<String>("Ignore", String.class);
-        public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
-        public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(19);
-
-        static {
-            keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
-            keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
-            keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
-            keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(FRONT_INSERT_LENGTH_INDEX, ComponentPreset.FORE_SHOULDER_LENGTH);
-            keyMap.add(FRONT_INSERT_OD_INDEX, ComponentPreset.FORE_SHOULDER_DIAMETER);
-            keyMap.add(FRONT_OD_INDEX, ComponentPreset.FORE_OUTER_DIAMETER);
-            keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
-            keyMap.add(REAR_OD_INDEX, ComponentPreset.AFT_OUTER_DIAMETER);
-            keyMap.add(CORE_DIA_INDEX, IGNORE);
-            keyMap.add(REAR_INSERT_LENGTH_INDEX, ComponentPreset.AFT_SHOULDER_LENGTH);
-            keyMap.add(REAR_INSERT_OD_INDEX, ComponentPreset.AFT_SHOULDER_DIAMETER);
-            keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
-            keyMap.add(CONFIG_INDEX, CONFIG);
-            keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
-            keyMap.add(CG_LOC_INDEX, CG_LOC);
-            keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(MASS_INDEX, ComponentPreset.MASS);
-            keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TRANSITION);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
-        }
-
-        public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
-                                                                                              FileNotFoundException {
-            List<String[]> data = RocksimComponentFileLoader.load(file);
-            return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
-        }
-    }
-
-    static class MaterialLoader {
-        private final static int MATERIAL_INDEX = 0;
-        private final static int UNITS_INDEX = 1;
-        private final static int DENSITY_INDEX = 2;
-        private final static int LOW_INDEX = 3;
-        private final static int HIGH_INDEX = 4;
-        private final static int CLASS_INDEX = 5;
-        private final static int ROCKETRY_USE_INDEX = 6;
-        private final static int BODY_TUBES_INDEX = 7;
-        public static final int FIN_SETS_INDEX = 8;
-        public static final int LAUNCH_LUGS_INDEX = 9;
-        public static final int CORDS_INDEX = 10;
-        public static final int NOSE_INDEX = 11;
-        public static final int PARACHUTE_INDEX = 12;
-        public static final int STREAMER_INDEX = 13;
-        public static final int TRANSITION_INDEX = 14;
-        public static final int RING_INDEX = 15;
-        public static final int BULKHEAD_INDEX = 16;
-        public static final int ENGINE_BLOCK_INDEX = 17;
-        public static final int SLEEVE_INDEX = 18;
-        public static final int TUBE_COUPLER_INDEX = 19;
-        public static final int KNOWN_DIM_TYPE_INDEX = 27;
-        public static final int KNOWN_DIM_UNITS_INDEX = 28;
-        public static final int KNOWN_DIM_VALUE_INDEX = 29;
-
-        public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
-        public final static TypedKey<String> MATERIAL_NAME = new TypedKey<String>("Material Name", String.class);
-        public final static TypedKey<Double> DENSITY = new TypedKey<Double>("Density", Double.class);
-
-        static class MaterialAdapter {
-            Material.Type type;
-            double conversionFactor;
-
-            MaterialAdapter(Material.Type theType, double cf) {
-                type = theType;
-                conversionFactor = cf;
-            }
-        }
-
-        private final static Map<String, MaterialAdapter> materialAdapterMap = new HashMap<String, MaterialAdapter>();
-
-        static {
-            materialAdapterMap.put("g/cm", new MaterialAdapter(Material.Type.LINE, 0.1d));
-            materialAdapterMap.put("g/cm2", new MaterialAdapter(Material.Type.SURFACE, 10.0d));
-            materialAdapterMap.put("g/cm3", new MaterialAdapter(Material.Type.BULK, 1000.0d));
-            materialAdapterMap.put("kg/m3", new MaterialAdapter(Material.Type.BULK, 1d));
-            materialAdapterMap.put("lb/ft3", new MaterialAdapter(Material.Type.BULK, 16.0184634d));
-            materialAdapterMap.put("oz/in", new MaterialAdapter(Material.Type.LINE, 1.11612296d));
-            materialAdapterMap.put("oz/in2", new MaterialAdapter(Material.Type.SURFACE, 43.9418487));
-
-            keyMap.add(MATERIAL_INDEX, MATERIAL_NAME);
-            keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
-            keyMap.add(DENSITY_INDEX, DENSITY);
-        }
-
-        static Map<String, Material> load() {
-            List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.MATERIAL);
-            Map<String, Material> materialMap = new HashMap<String, Material>();
-
-            for (int i = 0; i < data.size(); i++) {
-                try {
-                    String[] strings = data.get(i);
-                    MaterialAdapter ma = materialAdapterMap.get(strings[UNITS_INDEX]);
-                    double metricDensity = ma.conversionFactor * Double.parseDouble(strings[DENSITY_INDEX]);
-                    final String cleanedMaterialName = stripAll(strings[MATERIAL_INDEX], '"').trim();
-                    final Material material = Databases.findMaterial(ma.type, cleanedMaterialName,
-                            metricDensity, true);
-                    materialMap.put(cleanedMaterialName, material);
-                    materialMap.put(cleanedMaterialName.toLowerCase(), material);
-                    materialMap.put(toCamelCase(cleanedMaterialName), material);
-                }
-                catch (Exception e) {
-                    //Trap a bad row and move on
-                    //TODO: log it?  Display to user?
-                }
-            }
-            return materialMap;
-        }
-    }
-
-    public static void main(String[] args) {
-        Application.setPreferences(new SwingPreferences());
-        Map<String, Material> materialMap = MaterialLoader.load();
-        Collection<ComponentPreset> presetNC = new NoseConeLoader().load(materialMap);
-        Collection<ComponentPreset> presetBC = new BodyTubeLoader().load(materialMap);
-        Collection<ComponentPreset> presetBH = new BulkheadLoader().load(materialMap);
-        Collection<ComponentPreset> presetCR = new CenteringRingLoader().load(materialMap);
-        Collection<ComponentPreset> presetTC = new TubeCouplerLoader().load(materialMap);
-        Collection<ComponentPreset> presetTR = new TransitionLoader().load(materialMap);
-        Collection<ComponentPreset> presetEB = new EngineBlockLoader().load(materialMap);
-/*
-        for (Iterator<ComponentPreset> iterator = presetNC.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetBC.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetBH.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetCR.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetTC.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetTR.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-        for (Iterator<ComponentPreset> iterator = presetEB.iterator(); iterator.hasNext(); ) {
-            ComponentPreset next = iterator.next();
-            System.err.println(next);
-        }
-*/
-        List<ComponentPreset> allPresets = new ArrayList<ComponentPreset>();
-        allPresets.addAll(presetBC);
-        allPresets.addAll(presetBH);
-        allPresets.addAll(presetCR);
-        allPresets.addAll(presetEB);
-        allPresets.addAll(presetNC);
-        allPresets.addAll(presetTC);
-        allPresets.addAll(presetTR);
-
-        String xml = new OpenRocketComponentSaver().marshalToOpenRocketComponent(new ArrayList<Material>(materialMap.values()), allPresets);
-        System.err.println(xml);
-        try {
-            List<ComponentPreset> presets = new OpenRocketComponentSaver().unmarshalFromOpenRocketComponent(new StringReader(xml));
-        }
-        catch (InvalidComponentPresetException e) {
-            e.printStackTrace();
-        }
-    }
 }
 
 //Errata:
diff --git a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java
new file mode 100644 (file)
index 0000000..9a47bb2
--- /dev/null
@@ -0,0 +1,105 @@
+package net.sf.openrocket.preset.loader;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
+
+public class RocksimComponentFileTranslator {
+
+       private static void printUsage() {
+               System.err.println("RocksimComponentFileLoader <dir> <file>");
+               System.err.println("<dir> is base directory for a set of Rocksim component csv files");
+               System.err.println("<file> is where the orc file is written");
+       }
+
+       public static void main(String[] args) throws Exception {
+               
+               // How to control logging?
+               
+               if ( args.length < 2 || args.length > 2 ) {
+                       printUsage();
+                       throw new IllegalArgumentException("Invalid Command Line Params");
+               }
+
+               List<ComponentPreset> allPresets = new ArrayList<ComponentPreset>();
+
+               RocksimComponentFileLoader.basePath = args[0];
+               
+               System.err.println("Loading csv files from directory " + args[0]);
+               
+               Application.setPreferences(new SwingPreferences());
+
+               MaterialLoader mats = new MaterialLoader();
+               mats.load();
+
+               Map<String, Material> materialMap = mats.getMaterialMap();
+               System.err.println("\tMaterial types loaded: " + materialMap.size());
+               
+               {
+                       BodyTubeLoader bts = new BodyTubeLoader(materialMap);
+                       bts.load();
+                       allPresets.addAll(bts.getPresets());
+                       System.err.println("\tBody Tubes loaded: " + bts.getPresets().size());
+               }
+               {
+                       BulkHeadLoader bhs = new BulkHeadLoader(materialMap);
+                       bhs.load();
+                       allPresets.addAll(bhs.getPresets());
+                       System.err.println("\tBody Tubes loaded: " + bhs.getPresets().size());
+               }
+               {
+                       CenteringRingLoader crs = new CenteringRingLoader(materialMap);
+                       crs.load();
+                       allPresets.addAll(crs.getPresets());
+                       System.err.println("\tCentering Rings loaded: " + crs.getPresets().size());
+               }
+               {
+                       TubeCouplerLoader tcs = new TubeCouplerLoader(materialMap);
+                       tcs.load();
+                       allPresets.addAll(tcs.getPresets());
+                       System.err.println("\tTube Couplers loaded: " + tcs.getPresets().size());
+               }
+               {
+                       EngineBlockLoader ebs = new EngineBlockLoader(materialMap);
+                       ebs.load();
+                       allPresets.addAll(ebs.getPresets());
+                       System.err.println("\tEngine Blocks loaded: " + ebs.getPresets().size());
+               }
+               {
+                       NoseConeLoader ncs = new NoseConeLoader(materialMap);
+                       ncs.load();
+                       allPresets.addAll(ncs.getPresets());
+                       System.err.println("\tNose Cones loaded: " + ncs.getPresets().size());
+               }
+               {
+                       TransitionLoader trs = new TransitionLoader(materialMap);
+                       trs.load();
+                       allPresets.addAll(trs.getPresets());
+                       System.err.println("\tTransitions loaded: " + trs.getPresets().size());
+               }
+               System.err.println("\tMarshalling to XML");
+               String xml = new OpenRocketComponentSaver().marshalToOpenRocketComponent(new ArrayList<Material>(materialMap.values()), allPresets);
+               
+               // Try parsing the file
+               System.err.println("\tValidating XML");
+               List<ComponentPreset> presets = new OpenRocketComponentSaver().unmarshalFromOpenRocketComponent(new StringReader(xml));
+               
+               System.err.println("\tWriting to file " + args[1]);
+               File outfile = new File(args[1]);
+               FileWriter fos = new FileWriter(outfile);
+               fos.write(xml);
+               fos.flush();
+               fos.close();
+               
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/ShapeColumnParser.java b/core/src/net/sf/openrocket/preset/loader/ShapeColumnParser.java
new file mode 100644 (file)
index 0000000..55fb394
--- /dev/null
@@ -0,0 +1,54 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Locale;
+
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.TypedPropertyMap;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.Transition.Shape;
+import net.sf.openrocket.util.BugException;
+
+public class ShapeColumnParser extends BaseColumnParser {
+
+       public ShapeColumnParser() {
+               super("Shape");
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+               Transition.Shape shape = null;
+               String lc = columnData.toLowerCase(Locale.US);
+               if ( "ogive".equals(lc) ) {
+                       shape = Shape.OGIVE;
+               }
+               if ( "conical".equals(lc) ) {
+                       shape = Shape.CONICAL;
+               }
+               if ( "cone".equals(lc) ) {
+                       shape = Shape.CONICAL;
+               }
+               if ( "elliptical".equals(lc) ) {
+                       shape = Shape.ELLIPSOID;
+               }
+               if ( "parabolic".equals(lc) ) {
+                       shape = Shape.PARABOLIC;
+               }
+               if ( "sears-haack".equals(lc) ) {
+                       shape = Shape.HAACK;
+               }
+               if ( "power-series".equals(lc) ) {
+                       shape = Shape.POWER;
+               }
+               if ( "1".equals(lc) ) {
+                       shape = Shape.OGIVE;
+               }
+               if( "0". equals(lc) ) {
+                       shape = Shape.CONICAL;
+               }
+               if ( shape == null ) {
+                       throw new BugException("Invalid shape parameter: " + columnData);
+               }
+               props.put(ComponentPreset.SHAPE, shape);
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/StringColumnParser.java b/core/src/net/sf/openrocket/preset/loader/StringColumnParser.java
new file mode 100644 (file)
index 0000000..c4d96a7
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.preset.loader;
+
+import net.sf.openrocket.preset.TypedKey;
+import net.sf.openrocket.preset.TypedPropertyMap;
+
+public class StringColumnParser extends BaseColumnParser {
+
+       private TypedKey<String> propKey;
+       
+       public StringColumnParser(String columnHeader, TypedKey<String> propKey) {
+               super(columnHeader);
+               this.propKey = propKey;
+       }
+
+       @Override
+       protected void doParse(String columnData, String[] data, TypedPropertyMap props) {
+               props.put(propKey, columnData);
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/TransitionLoader.java b/core/src/net/sf/openrocket/preset/loader/TransitionLoader.java
new file mode 100644 (file)
index 0000000..a8d2903
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+
+public class TransitionLoader extends NoseConeLoader {
+
+       public TransitionLoader(Map<String, Material> materials) {
+               super(materials);
+               fileColumns.add(new DoubleUnitColumnParser("Front Insert Len","Units",ComponentPreset.FORE_SHOULDER_LENGTH));
+               fileColumns.add(new DoubleUnitColumnParser("Front Insert OD","Units",ComponentPreset.FORE_SHOULDER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Front OD","Units",ComponentPreset.FORE_OUTER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Rear Insert Len","Units",ComponentPreset.AFT_SHOULDER_LENGTH));
+               fileColumns.add(new DoubleUnitColumnParser("Rear Insert OD","Units",ComponentPreset.AFT_SHOULDER_DIAMETER));
+               fileColumns.add(new DoubleUnitColumnParser("Rear OD","Units",ComponentPreset.AFT_OUTER_DIAMETER));
+       }
+
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.TRANSITION;
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.TRANSITION;
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/preset/loader/TubeCouplerLoader.java b/core/src/net/sf/openrocket/preset/loader/TubeCouplerLoader.java
new file mode 100644 (file)
index 0000000..615a00d
--- /dev/null
@@ -0,0 +1,25 @@
+package net.sf.openrocket.preset.loader;
+
+import java.util.Map;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPreset.Type;
+
+public class TubeCouplerLoader extends BodyTubeLoader {
+
+       public TubeCouplerLoader(Map<String, Material> materials) {
+               super(materials);
+       }
+
+       @Override
+       protected Type getComponentPresetType() {
+               return ComponentPreset.Type.TUBE_COUPLER;
+       }
+
+       @Override
+       protected RocksimComponentFileType getFileType() {
+               return RocksimComponentFileType.TUBE_COUPLER;
+       }
+
+}
index 5563bac8467decee81f8f91aa805cb7184c1c376..76a226523cf0c0674d6d95b92e7d68a65829f7d6 100644 (file)
@@ -1,15 +1,5 @@
 package net.sf.openrocket.preset.xml;
 
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.preset.ComponentPreset;
-import net.sf.openrocket.preset.InvalidComponentPresetException;
-import net.sf.openrocket.startup.Application;
-
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.Unmarshaller;
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -18,144 +8,134 @@ import java.io.Reader;
 import java.io.StringWriter;
 import java.util.List;
 
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.InvalidComponentPresetException;
+
 /**
  * The active manager class that is the entry point for writing *.orc files.
  */
 public class OpenRocketComponentSaver {
 
-    /**
-     * The logger.
-     */
-    private static final LogHelper log = Application.getLogger();
-
-    /**
-     * This method marshals an OpenRocketDocument (OR design) to Rocksim-compliant XML.
-     *
-     * @param theMaterialList the list of materials to be included
-     * @param thePresetList   the list of presets to be included
-     *
-     * @return ORC-compliant XML
-     */
-    public String marshalToOpenRocketComponent(List<Material> theMaterialList, List<ComponentPreset> thePresetList) {
-
-        try {
-            JAXBContext binder = JAXBContext.newInstance(OpenRocketComponentDTO.class);
-            Marshaller marshaller = binder.createMarshaller();
-            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
-            StringWriter sw = new StringWriter();
-
-            marshaller.marshal(toOpenRocketComponentDTO(theMaterialList, thePresetList), sw);
-            return sw.toString();
-        }
-        catch (Exception e) {
-            log.error("Could not marshal a preset list. " + e.getMessage());
-        }
-
-        return null;
-    }
-
-    /**
-     * This method unmarshals from a Reader that is presumed to be open on an XML file in .orc format.
-     *
-     * @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle
-     *           UTF characters correctly
-     *
-     * @return a list of ComponentPresets
-     *
-     * @throws InvalidComponentPresetException
-     *
-     */
-    public List<ComponentPreset> unmarshalFromOpenRocketComponent(Reader is) throws InvalidComponentPresetException {
-        return fromOpenRocketComponent(is).asComponentPresets();
-    }
-
-    /**
-     * Write an XML representation of a list of presets.
-     *
-     * @param dest            the stream to write the data to
-     * @param theMaterialList the list of materials to be included
-     * @param thePresetList   the list of presets to be included
-     *
-     * @throws IOException thrown if the stream could not be written
-     */
-    public void save(OutputStream dest, List<Material> theMaterialList, List<ComponentPreset> thePresetList) throws
-                                                                                                             IOException {
-        log.info("Saving .orc file");
-
-        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, "UTF-8"));
-        writer.write(marshalToOpenRocketComponent(theMaterialList, thePresetList));
-        writer.flush();
-        writer.close();
-    }
-
-    /**
-     * Read from an open Reader instance XML in .orc format and reconstruct an OpenRocketComponentDTO instance.
-     *
-     * @param is an open Reader; assumed to be opened on a file of XML in .orc format
-     *
-     * @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read
-     *         or was in an invalid format
-     */
-    private OpenRocketComponentDTO fromOpenRocketComponent(Reader is) {
-        try {
-            JAXBContext bind = JAXBContext.newInstance(OpenRocketComponentDTO.class);
-            Unmarshaller unmarshaller = bind.createUnmarshaller();
-            return (OpenRocketComponentDTO) unmarshaller.unmarshal(is);
-        }
-        catch (JAXBException e) {
-            log.error("Could not unmarshal the .orc file. ", e);
-        }
-        return null;
-    }
-
-    /**
-     * Root conversion method.  It iterates over all subcomponents.
-     *
-     * @return a corresponding ORC representation
-     */
-    private OpenRocketComponentDTO toOpenRocketComponentDTO(List<Material> theMaterialList, List<ComponentPreset> thePresetList) {
-        OpenRocketComponentDTO rsd = new OpenRocketComponentDTO();
-
-        if (theMaterialList != null) {
-            for (Material material : theMaterialList) {
-                rsd.addMaterial(new MaterialDTO(material));
-            }
-        }
-
-        if (thePresetList != null) {
-            for (ComponentPreset componentPreset : thePresetList) {
-                rsd.addComponent(toComponentDTO(componentPreset));
-            }
-        }
-        return rsd;
-    }
-
-    /**
-     * Factory method that maps a preset to the corresponding DTO handler.
-     *
-     * @param thePreset the preset for which a handler will be found
-     *
-     * @return a subclass of BaseComponentDTO that can be used for marshalling/unmarshalling a preset; null if not found
-     *         for the preset type
-     */
-    private static BaseComponentDTO toComponentDTO(ComponentPreset thePreset) {
-        switch (thePreset.getType()) {
-            case BODY_TUBE:
-                return new BodyTubeDTO(thePreset);
-            case TUBE_COUPLER:
-                return new TubeCouplerDTO(thePreset);
-            case NOSE_CONE:
-                return new NoseConeDTO(thePreset);
-            case TRANSITION:
-                return new TransitionDTO(thePreset);
-            case BULK_HEAD:
-                return new BulkHeadDTO(thePreset);
-            case CENTERING_RING:
-                return new CenteringRingDTO(thePreset);
-            case ENGINE_BLOCK:
-                return new EngineBlockDTO(thePreset);
-        }
-
-        return null;
-    }
+       /**
+        * This method marshals an OpenRocketDocument (OR design) to Rocksim-compliant XML.
+        *
+        * @param theMaterialList the list of materials to be included
+        * @param thePresetList   the list of presets to be included
+        *
+        * @return ORC-compliant XML
+        * @throws JAXBException 
+        */
+       public String marshalToOpenRocketComponent(List<Material> theMaterialList, List<ComponentPreset> thePresetList) throws JAXBException {
+
+               JAXBContext binder = JAXBContext.newInstance(OpenRocketComponentDTO.class);
+               Marshaller marshaller = binder.createMarshaller();
+               marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+               StringWriter sw = new StringWriter();
+
+               marshaller.marshal(toOpenRocketComponentDTO(theMaterialList, thePresetList), sw);
+               return sw.toString();
+
+       }
+
+       /**
+        * This method unmarshals from a Reader that is presumed to be open on an XML file in .orc format.
+        *
+        * @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle
+        *           UTF characters correctly
+        *
+        * @return a list of ComponentPresets
+        *
+        * @throws InvalidComponentPresetException
+        *
+        */
+       public List<ComponentPreset> unmarshalFromOpenRocketComponent(Reader is) throws JAXBException, InvalidComponentPresetException {
+               return fromOpenRocketComponent(is).asComponentPresets();
+       }
+
+       /**
+        * Write an XML representation of a list of presets.
+        *
+        * @param dest            the stream to write the data to
+        * @param theMaterialList the list of materials to be included
+        * @param thePresetList   the list of presets to be included
+        * @throws JAXBException 
+        * @throws IOException thrown if the stream could not be written
+        */
+       public void save(OutputStream dest, List<Material> theMaterialList, List<ComponentPreset> thePresetList) throws IOException, JAXBException  {
+               BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, "UTF-8"));
+               writer.write(marshalToOpenRocketComponent(theMaterialList, thePresetList));
+               writer.flush();
+               writer.close();
+       }
+
+       /**
+        * Read from an open Reader instance XML in .orc format and reconstruct an OpenRocketComponentDTO instance.
+        *
+        * @param is an open Reader; assumed to be opened on a file of XML in .orc format
+        *
+        * @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read
+        *         or was in an invalid format
+        */
+       private OpenRocketComponentDTO fromOpenRocketComponent(Reader is) throws JAXBException {
+               JAXBContext bind = JAXBContext.newInstance(OpenRocketComponentDTO.class);
+               Unmarshaller unmarshaller = bind.createUnmarshaller();
+               return (OpenRocketComponentDTO) unmarshaller.unmarshal(is);
+       }
+
+       /**
+        * Root conversion method.  It iterates over all subcomponents.
+        *
+        * @return a corresponding ORC representation
+        */
+       private OpenRocketComponentDTO toOpenRocketComponentDTO(List<Material> theMaterialList, List<ComponentPreset> thePresetList) {
+               OpenRocketComponentDTO rsd = new OpenRocketComponentDTO();
+
+               if (theMaterialList != null) {
+                       for (Material material : theMaterialList) {
+                               rsd.addMaterial(new MaterialDTO(material));
+                       }
+               }
+
+               if (thePresetList != null) {
+                       for (ComponentPreset componentPreset : thePresetList) {
+                               rsd.addComponent(toComponentDTO(componentPreset));
+                       }
+               }
+               return rsd;
+       }
+
+       /**
+        * Factory method that maps a preset to the corresponding DTO handler.
+        *
+        * @param thePreset the preset for which a handler will be found
+        *
+        * @return a subclass of BaseComponentDTO that can be used for marshalling/unmarshalling a preset; null if not found
+        *         for the preset type
+        */
+       private static BaseComponentDTO toComponentDTO(ComponentPreset thePreset) {
+               switch (thePreset.getType()) {
+               case BODY_TUBE:
+                       return new BodyTubeDTO(thePreset);
+               case TUBE_COUPLER:
+                       return new TubeCouplerDTO(thePreset);
+               case NOSE_CONE:
+                       return new NoseConeDTO(thePreset);
+               case TRANSITION:
+                       return new TransitionDTO(thePreset);
+               case BULK_HEAD:
+                       return new BulkHeadDTO(thePreset);
+               case CENTERING_RING:
+                       return new CenteringRingDTO(thePreset);
+               case ENGINE_BLOCK:
+                       return new EngineBlockDTO(thePreset);
+               }
+
+               return null;
+       }
 }