]> git.gag.com Git - debian/openrocket/blob - core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java
4bf2952df1fa5f194e13ab33dd12ea64ee253c2d
[debian/openrocket] / core / src / net / sf / openrocket / preset / loader / RocksimComponentFileLoader.java
1 package net.sf.openrocket.preset.loader;
2
3 import au.com.bytecode.opencsv.CSVReader;
4 import net.sf.openrocket.database.Databases;
5 import net.sf.openrocket.file.preset.ColumnDefinition;
6 import net.sf.openrocket.file.rocksim.RocksimNoseConeCode;
7 import net.sf.openrocket.gui.print.PrintUnit;
8 import net.sf.openrocket.gui.util.SwingPreferences;
9 import net.sf.openrocket.material.Material;
10 import net.sf.openrocket.preset.ComponentPreset;
11 import net.sf.openrocket.preset.ComponentPresetFactory;
12 import net.sf.openrocket.preset.InvalidComponentPresetException;
13 import net.sf.openrocket.preset.TypedKey;
14 import net.sf.openrocket.preset.TypedPropertyMap;
15 import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
16 import net.sf.openrocket.startup.Application;
17 import net.sf.openrocket.unit.UnitGroup;
18 import net.sf.openrocket.util.ArrayList;
19 import net.sf.openrocket.util.BugException;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.StringReader;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33
34 /**
35  * Primary entry point for parsing component CSV files that are in Rocksim format.
36  */
37 public class RocksimComponentFileLoader {
38
39     /**
40      * Common unit of measure key.  Rocksim format allows different types of units.
41      */
42     public final static TypedKey<String> UNITS_OF_MEASURE = new TypedKey<String>("Units", String.class);
43
44     /**
45      * Read a comma separated component file and return the parsed contents as a list of string arrays.  Not for
46      * production use - just here for smoke testing.
47      *
48      * @param type the type of component file to read; uses the default file name
49      * @return a list (guaranteed never to be null) of string arrays.  Each element of the list represents a row in the
50      *         component data file; the element in the list itself is an array of String, where each item in the array
51      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
52      */
53     public static List<String[]> load(RocksimComponentFileType type) {
54         return load(RocksimComponentFileLoader.class.getResourceAsStream("/giantleaprocketry/" + type.getDefaultFileName()));
55     }
56
57     /**
58      * Read a comma separated component file and return the parsed contents as a list of string arrays.
59      *
60      * @param file the file to read and parse
61      * @return a list (guaranteed never to be null) of string arrays.  Each element of the list represents a row in the
62      *         component data file; the element in the list itself is an array of String, where each item in the array
63      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
64      */
65     public static List<String[]> load(File file) throws FileNotFoundException {
66         return load(new FileInputStream(file));
67     }
68
69     /**
70      * Read a comma separated component file and return the parsed contents as a list of string arrays.
71      *
72      * @param is the stream to read and parse
73      * @return a list (guaranteed never to be null) of string arrays.  Each element of the list represents a row in the
74      *         component data file; the element in the list itself is an array of String, where each item in the array
75      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
76      */
77     public static List<String[]> load(InputStream is) {
78         if (is == null) {
79             return new ArrayList<String[]>();
80         }
81         InputStreamReader r = null;
82         try {
83             r = new InputStreamReader(is);
84
85             // Create the CSV reader.  Use comma separator.
86             CSVReader reader = new CSVReader(r, ',', '\'', '\\');
87
88             //Read and throw away the header row.
89             reader.readNext();
90
91             //Read the rest of the file as data rows.
92             return reader.readAll();
93         }
94         catch (IOException e) {
95         }
96         finally {
97             if (r != null) {
98                 try {
99                     r.close();
100                 }
101                 catch (IOException e) {
102                 }
103             }
104         }
105
106         return new ArrayList<String[]>();
107     }
108
109     /**
110      * Rocksim CSV units are either inches or mm.  A value of 0 or "in." indicate inches.  A value of 1 or "mm" indicate
111      * millimeters.
112      *
113      * @param units the value from the file
114      * @return true if it's inches
115      */
116     private static boolean isInches(String units) {
117         String tmp = units.trim().toLowerCase();
118         return "0".equals(tmp) || tmp.startsWith("in");
119     }
120
121     /**
122      * Convert inches or millimeters to meters.
123      *
124      * @param units a Rocksim CSV string representing the kind of units.
125      * @param value the original value within the CSV file
126      * @return the value in meters
127      */
128     private static double convertLength(String units, double value) {
129         if (isInches(units)) {
130             return PrintUnit.INCHES.toMeters(value);
131         }
132         else {
133             return PrintUnit.MILLIMETERS.toMeters(value);
134         }
135     }
136
137     /**
138      * Remove all occurrences of the given character.  Note: this is done because some manufacturers embed double
139      * quotes in their descriptions or material names.  Those are stripped away because they cause all sorts of
140      * matching/lookup issues.
141      *
142      * @param target      the target string to be operated upon
143      * @param toBeRemoved the character to remove
144      * @return target, minus every occurrence of toBeRemoved
145      */
146     private static String stripAll(String target, Character toBeRemoved) {
147         StringBuilder sb = new StringBuilder();
148         for (int i = 0; i < target.length(); i++) {
149             Character c = target.charAt(i);
150             if (!c.equals(toBeRemoved)) {
151                 sb.append(c);
152             }
153         }
154         return sb.toString();
155     }
156
157     /**
158      * Convert all words in a given string to Camel Case (first letter capitalized). Words are assumed to be
159      * separated by a space.  Note: this is done because some manufacturers define their material name in Camel Case
160      * but the component part references the material in lower case.  That causes matching/lookup issues that's
161      * easiest handled this way (rather than converting everything to lower case.
162      *
163      * @param target the target string to be operated upon
164      * @return target, with the first letter of each word in uppercase
165      */
166     private static String toCamelCase(String target) {
167         StringBuilder sb = new StringBuilder();
168         String[] t = target.split("[ ]");
169         if (t != null && t.length > 0) {
170             for (String aT : t) {
171                 String s = aT;
172                 s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
173                 sb.append(s).append(" ");
174             }
175             return sb.toString().trim();
176         }
177         else {
178             return target;
179         }
180     }
181
182     /**
183      * The core loading method, shared by all component types.
184      *
185      * @param theData     the data as read from the CSV file
186      * @param keyMap      the list of typed keys that specify the preset's expected columns
187      * @param materialMap a map of material name to OR Material; this is sourced from a MATERIAL.CSV file that must
188      *                    accompany the component CSV file.
189      * @param type        the kind of component
190      * @return a collection of preset's
191      */
192     private static Collection<ComponentPreset> commonLoader(final List<String[]> theData,
193                                                             final List<TypedKey<?>> keyMap,
194                                                             final Map<String, Material> materialMap,
195                                                             final ComponentPreset.Type type) {
196         Collection<ComponentPreset> result = new ArrayList<ComponentPreset>();
197         List<TypedPropertyMap> templates = new java.util.ArrayList<TypedPropertyMap>();
198         Set<String> favorites = Application.getPreferences().getComponentFavorites();
199         Integer uom = null;
200
201         ColumnDefinition[] columns = new ColumnDefinition[keyMap.size()];
202         for (int i = 0; i < keyMap.size(); i++) {
203             TypedKey key = keyMap.get(i);
204             if (key != null) {
205                 columns[i] = new ColumnDefinition(key);
206                 if (key.getName().equals("Units")) {
207                     uom = i;
208                 }
209             }
210         }
211
212         for (int i = 0; i < theData.size(); i++) {
213             String[] item = theData.get(i);
214             TypedPropertyMap preset = new TypedPropertyMap();
215
216             for (int j = 0; j < columns.length; j++) {
217                 if (j < item.length) {
218                     String value = item[j];
219                     if (value == null) {
220                         continue;
221                     }
222                     value = value.trim();
223                     value = stripAll(value, '"');
224                     if (value.length() == 0) {
225                         continue;
226                     }
227                     final TypedKey typedKey = columns[j].getKey();
228                     //If it's the material, then pull it out of our internal map.  The map references the
229                     //data from the associated MATERIAL.CSV file that is mandatory.
230                     if (typedKey.equals(ComponentPreset.MATERIAL)) {
231                         preset.put(ComponentPreset.MATERIAL, materialMap.get(value));
232                     }
233                     //The shape of a nosecone or transition must get mapped from Rocksim to OR.
234                     else if (typedKey.equals(ComponentPreset.SHAPE)) {
235                         preset.put(ComponentPreset.SHAPE, RocksimNoseConeCode.fromShapeNameOrCode(value).asOpenRocket());
236                     }
237                     else {
238                         //Rocksim allows different types of length units.  They must be converted and normalized to OR.
239                         final UnitGroup unitGroup = typedKey.getUnitGroup();
240                         if (unitGroup != null && unitGroup.equals(UnitGroup.UNITS_LENGTH)) {
241                             columns[j].setProperty(preset, convertLength(item[uom], Double.valueOf(value)));
242                         }
243                         else {
244                             columns[j].setProperty(preset, value);
245                         }
246                     }
247                 }
248             }
249             //Set what kind of component this is.
250             preset.put(ComponentPreset.TYPE, type);
251             //Add to the collection.
252             templates.add(preset);
253         }
254
255         for (TypedPropertyMap o : templates) {
256             try {
257                 ComponentPreset preset = ComponentPresetFactory.create(o);
258                 if (favorites.contains(preset.preferenceKey())) {
259                     preset.setFavorite(true);
260                 }
261                 result.add(preset);
262             }
263             catch (InvalidComponentPresetException ex) {
264                 throw new BugException(ex);
265             }
266         }
267
268         return result;
269     }
270
271     static class BodyTubeLoader {
272         private final static int MFG_INDEX = 0;
273         private final static int PART_NO_INDEX = 1;
274         private final static int DESCRIPTION_INDEX = 2;
275         private final static int UNITS_INDEX = 3;
276         private final static int ID_INDEX = 4;
277         private final static int OD_INDEX = 5;
278         private final static int LENGTH_INDEX = 6;
279         private final static int MATERIAL_INDEX = 7;
280
281         public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
282
283         static {
284             keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
285             keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
286             keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
287             keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
288             keyMap.add(ID_INDEX, ComponentPreset.INNER_DIAMETER);
289             keyMap.add(OD_INDEX, ComponentPreset.OUTER_DIAMETER);
290             keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
291             keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
292         }
293
294         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
295             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BODY_TUBE);
296             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
297         }
298
299         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
300                                                                                               FileNotFoundException {
301             List<String[]> data = RocksimComponentFileLoader.load(file);
302             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
303         }
304
305     }
306
307     /**
308      * Tube coupler parser.  Although there are additional fields in the file, they are not used by
309      * most (any?) manufacturers so we ignore them entirely.
310      */
311     static class TubeCouplerLoader extends BodyTubeLoader {
312         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
313             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TUBE_COUPLER);
314             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
315         }
316
317         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
318                                                                                               FileNotFoundException {
319             List<String[]> data = RocksimComponentFileLoader.load(file);
320             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
321         }
322     }
323
324     /**
325      * Engine block parser.  Although there are additional fields in the file, they are not used by
326      * most (any?) manufacturers so we ignore them entirely.
327      */
328     static class EngineBlockLoader extends BodyTubeLoader {
329         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
330             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.ENGINE_BLOCK);
331             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
332         }
333
334         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
335                                                                                               FileNotFoundException {
336             List<String[]> data = RocksimComponentFileLoader.load(file);
337             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
338         }
339     }
340
341
342     static class BulkheadLoader extends BodyTubeLoader {
343         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
344             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BULKHEAD);
345             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
346         }
347
348         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
349                                                                                               FileNotFoundException {
350             List<String[]> data = RocksimComponentFileLoader.load(file);
351             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
352         }
353     }
354
355     static class CenteringRingLoader extends BodyTubeLoader {
356         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
357             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.CENTERING_RING);
358             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
359         }
360
361         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
362                                                                                               FileNotFoundException {
363             List<String[]> data = RocksimComponentFileLoader.load(file);
364             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
365         }
366     }
367
368     static class NoseConeLoader {
369         public static final int MFG_INDEX = 0;
370         public static final int PART_NO_INDEX = 1;
371         public static final int DESCRIPTION_INDEX = 2;
372         public static final int UNITS_INDEX = 3;
373         public static final int LENGTH_INDEX = 4;
374         public static final int OUTER_DIA_INDEX = 5;
375         public static final int LD_RATIO_INDEX = 6;
376         public static final int INSERT_LENGTH_INDEX = 7;
377         public static final int INSERT_OD_INDEX = 8;
378         public static final int THICKNESS_INDEX = 9;
379         public static final int SHAPE_INDEX = 10;
380         public static final int CONFIG_INDEX = 11;
381         public static final int MATERIAL_INDEX = 12;
382         public static final int CG_LOC_INDEX = 13;
383         public static final int MASS_UNITS_INDEX = 14;
384         public static final int MASS_INDEX = 15;
385         public static final int BASE_EXT_LEN_INDEX = 16;
386
387         public final static TypedKey<Double> LD_RATIO = new TypedKey<Double>("Len/Dia Ratio", Double.class);
388         public final static TypedKey<Double> BASE_EXT_LEN = new TypedKey<Double>("Base Ext Len", Double.class, UnitGroup.UNITS_LENGTH);
389         public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
390         public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
391         public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(17);
392
393         static {
394             keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
395             keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
396             keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
397             keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
398             keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
399             keyMap.add(OUTER_DIA_INDEX, ComponentPreset.AFT_OUTER_DIAMETER);
400             keyMap.add(LD_RATIO_INDEX, LD_RATIO);
401             keyMap.add(INSERT_LENGTH_INDEX, ComponentPreset.AFT_SHOULDER_LENGTH);
402             keyMap.add(INSERT_OD_INDEX, ComponentPreset.AFT_SHOULDER_DIAMETER);
403             keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
404             keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
405             keyMap.add(CONFIG_INDEX, CONFIG);
406             keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
407             keyMap.add(CG_LOC_INDEX, CG_LOC);
408             keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
409             keyMap.add(MASS_INDEX, ComponentPreset.MASS);
410             keyMap.add(BASE_EXT_LEN_INDEX, BASE_EXT_LEN);
411         }
412
413         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
414             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.NOSE_CONE);
415             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
416         }
417
418         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
419                                                                                               FileNotFoundException {
420             List<String[]> data = RocksimComponentFileLoader.load(file);
421             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
422         }
423     }
424
425     static class TransitionLoader {
426         public static final int MFG_INDEX = 0;
427         public static final int PART_NO_INDEX = 1;
428         public static final int DESCRIPTION_INDEX = 2;
429         public static final int UNITS_INDEX = 3;
430         public static final int FRONT_INSERT_LENGTH_INDEX = 4;
431         public static final int FRONT_INSERT_OD_INDEX = 5;
432         public static final int FRONT_OD_INDEX = 6;
433         public static final int LENGTH_INDEX = 7;
434         public static final int REAR_OD_INDEX = 8;
435         public static final int CORE_DIA_INDEX = 9;
436         public static final int REAR_INSERT_LENGTH_INDEX = 10;
437         public static final int REAR_INSERT_OD_INDEX = 11;
438         public static final int THICKNESS_INDEX = 12;
439         public static final int CONFIG_INDEX = 13;
440         public static final int MATERIAL_INDEX = 14;
441         public static final int CG_LOC_INDEX = 15;
442         public static final int MASS_UNITS_INDEX = 16;
443         public static final int MASS_INDEX = 17;
444         public static final int SHAPE_INDEX = 18;
445
446         public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
447         public final static TypedKey<String> IGNORE = new TypedKey<String>("Ignore", String.class);
448         public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
449         public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(19);
450
451         static {
452             keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
453             keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
454             keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
455             keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
456             keyMap.add(FRONT_INSERT_LENGTH_INDEX, ComponentPreset.FORE_SHOULDER_LENGTH);
457             keyMap.add(FRONT_INSERT_OD_INDEX, ComponentPreset.FORE_SHOULDER_DIAMETER);
458             keyMap.add(FRONT_OD_INDEX, ComponentPreset.FORE_OUTER_DIAMETER);
459             keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
460             keyMap.add(REAR_OD_INDEX, ComponentPreset.AFT_OUTER_DIAMETER);
461             keyMap.add(CORE_DIA_INDEX, IGNORE);
462             keyMap.add(REAR_INSERT_LENGTH_INDEX, ComponentPreset.AFT_SHOULDER_LENGTH);
463             keyMap.add(REAR_INSERT_OD_INDEX, ComponentPreset.AFT_SHOULDER_DIAMETER);
464             keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
465             keyMap.add(CONFIG_INDEX, CONFIG);
466             keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
467             keyMap.add(CG_LOC_INDEX, CG_LOC);
468             keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
469             keyMap.add(MASS_INDEX, ComponentPreset.MASS);
470             keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
471         }
472
473         public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
474             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TRANSITION);
475             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
476         }
477
478         public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
479                                                                                               FileNotFoundException {
480             List<String[]> data = RocksimComponentFileLoader.load(file);
481             return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
482         }
483     }
484
485     static class MaterialLoader {
486         private final static int MATERIAL_INDEX = 0;
487         private final static int UNITS_INDEX = 1;
488         private final static int DENSITY_INDEX = 2;
489         private final static int LOW_INDEX = 3;
490         private final static int HIGH_INDEX = 4;
491         private final static int CLASS_INDEX = 5;
492         private final static int ROCKETRY_USE_INDEX = 6;
493         private final static int BODY_TUBES_INDEX = 7;
494         public static final int FIN_SETS_INDEX = 8;
495         public static final int LAUNCH_LUGS_INDEX = 9;
496         public static final int CORDS_INDEX = 10;
497         public static final int NOSE_INDEX = 11;
498         public static final int PARACHUTE_INDEX = 12;
499         public static final int STREAMER_INDEX = 13;
500         public static final int TRANSITION_INDEX = 14;
501         public static final int RING_INDEX = 15;
502         public static final int BULKHEAD_INDEX = 16;
503         public static final int ENGINE_BLOCK_INDEX = 17;
504         public static final int SLEEVE_INDEX = 18;
505         public static final int TUBE_COUPLER_INDEX = 19;
506         public static final int KNOWN_DIM_TYPE_INDEX = 27;
507         public static final int KNOWN_DIM_UNITS_INDEX = 28;
508         public static final int KNOWN_DIM_VALUE_INDEX = 29;
509
510         public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
511         public final static TypedKey<String> MATERIAL_NAME = new TypedKey<String>("Material Name", String.class);
512         public final static TypedKey<Double> DENSITY = new TypedKey<Double>("Density", Double.class);
513
514         static class MaterialAdapter {
515             Material.Type type;
516             double conversionFactor;
517
518             MaterialAdapter(Material.Type theType, double cf) {
519                 type = theType;
520                 conversionFactor = cf;
521             }
522         }
523
524         private final static Map<String, MaterialAdapter> materialAdapterMap = new HashMap<String, MaterialAdapter>();
525
526         static {
527             materialAdapterMap.put("g/cm", new MaterialAdapter(Material.Type.LINE, 0.1d));
528             materialAdapterMap.put("g/cm2", new MaterialAdapter(Material.Type.SURFACE, 10.0d));
529             materialAdapterMap.put("g/cm3", new MaterialAdapter(Material.Type.BULK, 1000.0d));
530             materialAdapterMap.put("kg/m3", new MaterialAdapter(Material.Type.BULK, 1d));
531             materialAdapterMap.put("lb/ft3", new MaterialAdapter(Material.Type.BULK, 16.0184634d));
532             materialAdapterMap.put("oz/in", new MaterialAdapter(Material.Type.LINE, 1.11612296d));
533             materialAdapterMap.put("oz/in2", new MaterialAdapter(Material.Type.SURFACE, 43.9418487));
534
535             keyMap.add(MATERIAL_INDEX, MATERIAL_NAME);
536             keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
537             keyMap.add(DENSITY_INDEX, DENSITY);
538         }
539
540         static Map<String, Material> load() {
541             List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.MATERIAL);
542             Map<String, Material> materialMap = new HashMap<String, Material>();
543
544             for (int i = 0; i < data.size(); i++) {
545                 try {
546                     String[] strings = data.get(i);
547                     MaterialAdapter ma = materialAdapterMap.get(strings[UNITS_INDEX]);
548                     double metricDensity = ma.conversionFactor * Double.parseDouble(strings[DENSITY_INDEX]);
549                     final String cleanedMaterialName = stripAll(strings[MATERIAL_INDEX], '"').trim();
550                     final Material material = Databases.findMaterial(ma.type, cleanedMaterialName,
551                             metricDensity, true);
552                     materialMap.put(cleanedMaterialName, material);
553                     materialMap.put(cleanedMaterialName.toLowerCase(), material);
554                     materialMap.put(toCamelCase(cleanedMaterialName), material);
555                 }
556                 catch (Exception e) {
557                     //Trap a bad row and move on
558                     //TODO: log it?  Display to user?
559                 }
560             }
561             return materialMap;
562         }
563     }
564
565     public static void main(String[] args) {
566         Application.setPreferences(new SwingPreferences());
567         Map<String, Material> materialMap = MaterialLoader.load();
568         Collection<ComponentPreset> presetNC = new NoseConeLoader().load(materialMap);
569         Collection<ComponentPreset> presetBC = new BodyTubeLoader().load(materialMap);
570         Collection<ComponentPreset> presetBH = new BulkheadLoader().load(materialMap);
571         Collection<ComponentPreset> presetCR = new CenteringRingLoader().load(materialMap);
572         Collection<ComponentPreset> presetTC = new TubeCouplerLoader().load(materialMap);
573         Collection<ComponentPreset> presetTR = new TransitionLoader().load(materialMap);
574         Collection<ComponentPreset> presetEB = new EngineBlockLoader().load(materialMap);
575 /*
576         for (Iterator<ComponentPreset> iterator = presetNC.iterator(); iterator.hasNext(); ) {
577             ComponentPreset next = iterator.next();
578             System.err.println(next);
579         }
580         for (Iterator<ComponentPreset> iterator = presetBC.iterator(); iterator.hasNext(); ) {
581             ComponentPreset next = iterator.next();
582             System.err.println(next);
583         }
584         for (Iterator<ComponentPreset> iterator = presetBH.iterator(); iterator.hasNext(); ) {
585             ComponentPreset next = iterator.next();
586             System.err.println(next);
587         }
588         for (Iterator<ComponentPreset> iterator = presetCR.iterator(); iterator.hasNext(); ) {
589             ComponentPreset next = iterator.next();
590             System.err.println(next);
591         }
592         for (Iterator<ComponentPreset> iterator = presetTC.iterator(); iterator.hasNext(); ) {
593             ComponentPreset next = iterator.next();
594             System.err.println(next);
595         }
596         for (Iterator<ComponentPreset> iterator = presetTR.iterator(); iterator.hasNext(); ) {
597             ComponentPreset next = iterator.next();
598             System.err.println(next);
599         }
600         for (Iterator<ComponentPreset> iterator = presetEB.iterator(); iterator.hasNext(); ) {
601             ComponentPreset next = iterator.next();
602             System.err.println(next);
603         }
604 */
605         List<ComponentPreset> allPresets = new ArrayList<ComponentPreset>();
606         allPresets.addAll(presetBC);
607         allPresets.addAll(presetBH);
608         allPresets.addAll(presetCR);
609         allPresets.addAll(presetEB);
610         allPresets.addAll(presetNC);
611         allPresets.addAll(presetTC);
612         allPresets.addAll(presetTR);
613
614         String xml = new OpenRocketComponentSaver().marshalToOpenRocketComponent(new ArrayList<Material>(materialMap.values()), allPresets);
615         System.err.println(xml);
616         try {
617             List<ComponentPreset> presets = new OpenRocketComponentSaver().unmarshalFromOpenRocketComponent(new StringReader(xml));
618         }
619         catch (InvalidComponentPresetException e) {
620             e.printStackTrace();
621         }
622     }
623 }
624
625 //Errata:
626 //The oddities I've found thus far in the stock Rocksim data:
627 //1. BTDATA.CSV - Totally Tubular goofed up their part no. and description columns (They messed up TCDATA also)
628 //2. NCDATA.CSV - Estes Balsa nose cones are classified as G10 Fiberglass
629 //3. TRDATA.CSV - Apogee Saturn LEM Transition has no part number; Balsa Machining transitions have blank diameter