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