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