1 package net.sf.openrocket.preset.loader;
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;
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;
20 * Primary entry point for parsing component CSV files that are in Rocksim format.
22 public abstract class RocksimComponentFileLoader {
24 private static final PrintStream LOGGER = System.err;
26 private String basePath = "";
30 protected List<RocksimComponentFileColumnParser> fileColumns = new ArrayList<RocksimComponentFileColumnParser>();
35 * @param theBasePathToLoadFrom base path
37 public RocksimComponentFileLoader(File theBasePathToLoadFrom) {
38 dir = theBasePathToLoadFrom;
39 basePath = dir.getAbsolutePath();
45 * @param theBasePathToLoadFrom base path
47 public RocksimComponentFileLoader(String theBasePathToLoadFrom) {
48 dir = new File(basePath);
49 basePath = theBasePathToLoadFrom;
52 protected abstract RocksimComponentFileType getFileType();
57 } catch (FileNotFoundException fex ) {
58 LOGGER.println( fex.getLocalizedMessage() );
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.
66 * @param type the type of component file to read; uses the default file name
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.
72 private void load(RocksimComponentFileType type) throws FileNotFoundException {
74 throw new IllegalArgumentException(basePath + " does not exist");
76 if (!dir.isDirectory()) {
77 throw new IllegalArgumentException(basePath + " is not directory");
80 throw new IllegalArgumentException(basePath + " is not readable");
82 FileInputStream fis = new FileInputStream(new File(dir, type.getDefaultFileName()));
87 * Read a comma separated component file and return the parsed contents as a list of string arrays.
89 * @param file the file to read and parse
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.
95 private void load(File file) throws FileNotFoundException {
96 load(new FileInputStream(file));
100 * Read a comma separated component file and return the parsed contents as a list of string arrays.
102 * @param is the stream to read and parse
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.
108 private void load(InputStream is) {
112 InputStreamReader r = null;
114 r = new InputStreamReader(is);
116 // Create the CSV reader. Use comma separator.
117 CSVReader reader = new CSVReader(r, ',', '\'', '\\');
119 //Read and throw away the header row.
120 parseHeaders(reader.readNext());
122 String[] data = null;
123 while ((data = reader.readNext()) != null) {
124 // detect empty lines and skip:
125 if (data.length == 0) {
128 if (data.length == 1 && "".equals(data[0].trim())) {
133 //Read the rest of the file as data rows.
136 catch (IOException e) {
143 catch (IOException e) {
150 protected void parseHeaders(String[] headers) {
151 for (RocksimComponentFileColumnParser column : fileColumns) {
152 column.configure(headers);
156 protected void parseData(String[] data) {
157 if (data == null || data.length == 0) {
160 TypedPropertyMap props = new TypedPropertyMap();
164 for (RocksimComponentFileColumnParser column : fileColumns) {
165 column.parse(data, props);
170 protected void preProcess(String[] data) {
171 for (int i = 0; i < data.length; i++) {
177 d = stripAll(d, '"');
183 protected abstract void postProcess(TypedPropertyMap props);
186 * Rocksim CSV units are either inches or mm. A value of 0 or "in." indicate inches. A value of 1 or "mm" indicate
189 * @param units the value from the file
191 * @return true if it's inches
193 protected static boolean isInches(String units) {
194 String tmp = units.trim().toLowerCase();
195 return "0".equals(tmp) || tmp.startsWith("in");
199 * Convert inches or millimeters to meters.
201 * @param units a Rocksim CSV string representing the kind of units.
202 * @param value the original value within the CSV file
204 * @return the value in meters
206 protected static double convertLength(String units, double value) {
207 if (isInches(units)) {
208 return PrintUnit.INCHES.toMeters(value);
211 return PrintUnit.MILLIMETERS.toMeters(value);
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);
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
228 * @param target the target string to be operated upon
229 * @param toBeRemoved the character to remove
231 * @return target, minus every occurrence of toBeRemoved
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)) {
241 return sb.toString();
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.
250 * @param target the target string to be operated upon
252 * @return target, with the first letter of each word in uppercase
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) {
260 s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
261 sb.append(s).append(" ");
263 return sb.toString().trim();
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