fa03000a2596e0d2866c5e4cac178f18bd27a030
[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.gui.print.PrintUnit;
5 import net.sf.openrocket.preset.TypedPropertyMap;
6 import net.sf.openrocket.unit.Unit;
7 import net.sf.openrocket.unit.UnitGroup;
8 import net.sf.openrocket.util.ArrayList;
9
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.util.List;
17
18 /**
19  * Primary entry point for parsing component CSV files that are in Rocksim format.
20  */
21 public abstract class RocksimComponentFileLoader {
22
23     private String basePath = "";
24
25     private File dir;
26
27     protected List<RocksimComponentFileColumnParser> fileColumns = new ArrayList<RocksimComponentFileColumnParser>();
28
29     /**
30      * Constructor.
31      *
32      * @param theBasePathToLoadFrom base path
33      */
34     public RocksimComponentFileLoader(File theBasePathToLoadFrom) {
35         dir = theBasePathToLoadFrom;
36         basePath = dir.getAbsolutePath();
37     }
38
39     /**
40      * Constructor.
41      *
42      * @param theBasePathToLoadFrom base path
43      */
44     public RocksimComponentFileLoader(String theBasePathToLoadFrom) {
45         dir = new File(basePath);
46         basePath = theBasePathToLoadFrom;
47     }
48
49     protected abstract RocksimComponentFileType getFileType();
50
51     public void load() {
52         load(getFileType());
53     }
54
55     /**
56      * Read a comma separated component file and return the parsed contents as a list of string arrays.  Not for
57      * production use - just here for smoke testing.
58      *
59      * @param type the type of component file to read; uses the default file name
60      *
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     private void load(RocksimComponentFileType type) {
66         if (!dir.exists()) {
67             throw new IllegalArgumentException(basePath + " does not exist");
68         }
69         if (!dir.isDirectory()) {
70             throw new IllegalArgumentException(basePath + " is not directory");
71         }
72         if (!dir.canRead()) {
73             throw new IllegalArgumentException(basePath + " is not readable");
74         }
75         try {
76             FileInputStream fis = new FileInputStream(new File(dir, type.getDefaultFileName()));
77             load(fis);
78         }
79         catch (FileNotFoundException ex) {
80             // FIXME?
81         }
82     }
83
84     /**
85      * Read a comma separated component file and return the parsed contents as a list of string arrays.
86      *
87      * @param file the file to read and parse
88      *
89      * @return a list (guaranteed never to be null) of string arrays.  Each element of the list represents a row in the
90      *         component data file; the element in the list itself is an array of String, where each item in the array
91      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
92      */
93     private void load(File file) throws FileNotFoundException {
94         load(new FileInputStream(file));
95     }
96
97     /**
98      * Read a comma separated component file and return the parsed contents as a list of string arrays.
99      *
100      * @param is the stream to read and parse
101      *
102      * @return a list (guaranteed never to be null) of string arrays.  Each element of the list represents a row in the
103      *         component data file; the element in the list itself is an array of String, where each item in the array
104      *         is a column (cell) in the row.  The string array is in sequential order as it appeared in the file.
105      */
106     private void load(InputStream is) {
107         if (is == null) {
108             return;
109         }
110         InputStreamReader r = null;
111         try {
112             r = new InputStreamReader(is);
113
114             // Create the CSV reader.  Use comma separator.
115             CSVReader reader = new CSVReader(r, ',', '\'', '\\');
116
117             //Read and throw away the header row.
118             parseHeaders(reader.readNext());
119
120             String[] data = null;
121             while ((data = reader.readNext()) != null) {
122                 // detect empty lines and skip:
123                 if (data.length == 0) {
124                     continue;
125                 }
126                 if (data.length == 1 && "".equals(data[0].trim())) {
127                     continue;
128                 }
129                 parseData(data);
130             }
131             //Read the rest of the file as data rows.
132             return;
133         }
134         catch (IOException e) {
135         }
136         finally {
137             if (r != null) {
138                 try {
139                     r.close();
140                 }
141                 catch (IOException e) {
142                 }
143             }
144         }
145
146     }
147
148     protected void parseHeaders(String[] headers) {
149         for (RocksimComponentFileColumnParser column : fileColumns) {
150             column.configure(headers);
151         }
152     }
153
154     protected void parseData(String[] data) {
155         if (data == null || data.length == 0) {
156             return;
157         }
158         TypedPropertyMap props = new TypedPropertyMap();
159
160         preProcess(data);
161
162         for (RocksimComponentFileColumnParser column : fileColumns) {
163             column.parse(data, props);
164         }
165         postProcess(props);
166     }
167
168     protected void preProcess(String[] data) {
169         for (int i = 0; i < data.length; i++) {
170             String d = data[i];
171             if (d == null) {
172                 continue;
173             }
174             d = d.trim();
175             d = stripAll(d, '"');
176
177             data[i] = d;
178         }
179     }
180
181     protected abstract void postProcess(TypedPropertyMap props);
182
183     /**
184      * Rocksim CSV units are either inches or mm.  A value of 0 or "in." indicate inches.  A value of 1 or "mm" indicate
185      * millimeters.
186      *
187      * @param units the value from the file
188      *
189      * @return true if it's inches
190      */
191     protected static boolean isInches(String units) {
192         String tmp = units.trim().toLowerCase();
193         return "0".equals(tmp) || tmp.startsWith("in");
194     }
195
196     /**
197      * Convert inches or millimeters to meters.
198      *
199      * @param units a Rocksim CSV string representing the kind of units.
200      * @param value the original value within the CSV file
201      *
202      * @return the value in meters
203      */
204     protected static double convertLength(String units, double value) {
205         if (isInches(units)) {
206             return PrintUnit.INCHES.toMeters(value);
207         }
208         else {
209             return PrintUnit.MILLIMETERS.toMeters(value);
210         }
211     }
212
213     protected static double convertMass(String units, double value) {
214         if ("oz".equals(units)) {
215             Unit u = UnitGroup.UNITS_MASS.getUnit(2);
216             return u.fromUnit(value);
217         }
218         return value;
219     }
220
221     /**
222      * Remove all occurrences of the given character.  Note: this is done because some manufacturers embed double quotes
223      * in their descriptions or material names.  Those are stripped away because they cause all sorts of matching/lookup
224      * issues.
225      *
226      * @param target      the target string to be operated upon
227      * @param toBeRemoved the character to remove
228      *
229      * @return target, minus every occurrence of toBeRemoved
230      */
231     protected static String stripAll(String target, Character toBeRemoved) {
232         StringBuilder sb = new StringBuilder();
233         for (int i = 0; i < target.length(); i++) {
234             Character c = target.charAt(i);
235             if (!c.equals(toBeRemoved)) {
236                 sb.append(c);
237             }
238         }
239         return sb.toString();
240     }
241
242     /**
243      * Convert all words in a given string to Camel Case (first letter capitalized). Words are assumed to be separated
244      * by a space.  Note: this is done because some manufacturers define their material name in Camel Case but the
245      * component part references the material in lower case.  That causes matching/lookup issues that's easiest handled
246      * this way (rather than converting everything to lower case.
247      *
248      * @param target the target string to be operated upon
249      *
250      * @return target, with the first letter of each word in uppercase
251      */
252     protected static String toCamelCase(String target) {
253         StringBuilder sb = new StringBuilder();
254         String[] t = target.split("[ ]");
255         if (t != null && t.length > 0) {
256             for (String aT : t) {
257                 String s = aT;
258                 s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
259                 sb.append(s).append(" ");
260             }
261             return sb.toString().trim();
262         }
263         else {
264             return target;
265         }
266     }
267
268 }
269
270 //Errata:
271 //The oddities I've found thus far in the stock Rocksim data:
272 //1. BTDATA.CSV - Totally Tubular goofed up their part no. and description columns (They messed up TCDATA also)
273 //2. NCDATA.CSV - Estes Balsa nose cones are classified as G10 Fiberglass
274 //3. TRDATA.CSV - Apogee Saturn LEM Transition has no part number; Balsa Machining transitions have blank diameter