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