refactored file package
[debian/openrocket] / src / net / sf / openrocket / file / openrocket / OpenRocketSaver.java
1 package net.sf.openrocket.file.openrocket;
2
3 import java.io.BufferedWriter;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.io.OutputStreamWriter;
7 import java.io.Writer;
8 import java.util.ArrayList;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.zip.GZIPOutputStream;
12
13 import net.sf.openrocket.aerodynamics.Warning;
14 import net.sf.openrocket.document.OpenRocketDocument;
15 import net.sf.openrocket.document.Simulation;
16 import net.sf.openrocket.document.StorageOptions;
17 import net.sf.openrocket.file.RocketSaver;
18 import net.sf.openrocket.rocketcomponent.FinSet;
19 import net.sf.openrocket.rocketcomponent.Rocket;
20 import net.sf.openrocket.rocketcomponent.RocketComponent;
21 import net.sf.openrocket.rocketcomponent.TubeCoupler;
22 import net.sf.openrocket.simulation.FlightData;
23 import net.sf.openrocket.simulation.FlightDataBranch;
24 import net.sf.openrocket.simulation.FlightEvent;
25 import net.sf.openrocket.simulation.SimulationConditions;
26 import net.sf.openrocket.util.MathUtil;
27 import net.sf.openrocket.util.Pair;
28 import net.sf.openrocket.util.Prefs;
29 import net.sf.openrocket.util.Reflection;
30 import net.sf.openrocket.util.TextUtil;
31
32 public class OpenRocketSaver extends RocketSaver {
33         
34         /**
35          * Divisor used in converting an integer version to the point-represented version.
36          * The integer version divided by this value is the major version and the remainder is
37          * the minor version.  For example 101 corresponds to file version "1.1".
38          */
39         public static final int FILE_VERSION_DIVISOR = 100;
40
41         
42         private static final String OPENROCKET_CHARSET = "UTF-8";
43         
44         private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket.savers";
45         private static final String METHOD_SUFFIX = "Saver";
46         
47         
48         // Estimated storage used by different portions
49         // These have been hand-estimated from saved files
50         private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
51         private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
52         private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
53         private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
54         private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
55         private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
56         
57         
58         private int indent;
59         private Writer dest;
60         
61         @Override
62         public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
63         throws IOException {
64                 
65                 if (options.isCompressionEnabled()) {
66                         output = new GZIPOutputStream(output);
67                 }
68                 
69                 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); 
70                 
71                 final int fileVersion = calculateNecessaryFileVersion(document, options);
72                 final String fileVersionString = 
73                         (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR); 
74                 
75                 
76                 this.indent = 0;
77                 
78                 System.out.println("Writing...");
79                 
80                 writeln("<?xml version='1.0' encoding='utf-8'?>");
81                 writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
82                                 + Prefs.getVersion() + "\">");
83                 indent++;
84                 
85                 // Recursively save the rocket structure
86                 saveComponent(document.getRocket());
87                 
88                 writeln("");
89                 
90                 // Save all simulations
91                 writeln("<simulations>");
92                 indent++;
93                 boolean first = true;
94                 for (Simulation s: document.getSimulations()) {
95                         if (!first)
96                                 writeln("");
97                         first = false;
98                         saveSimulation(s, options.getSimulationTimeSkip());
99                 }
100                 indent--;
101                 writeln("</simulations>");
102                 
103                 indent--;
104                 writeln("</openrocket>");
105                 
106                 dest.flush();
107                 if (options.isCompressionEnabled()) {
108                         ((GZIPOutputStream)output).finish();
109                 }
110         }
111         
112         
113         
114         @Override
115         public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
116                 
117                 long size = 0;
118                 
119                 // Size per component
120                 int componentCount = 0;
121                 Rocket rocket = doc.getRocket();
122                 Iterator<RocketComponent> iterator = rocket.deepIterator(true);
123                 while (iterator.hasNext()) {
124                         iterator.next();
125                         componentCount++;
126                 }
127                 
128                 if (options.isCompressionEnabled())
129                         size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
130                 else
131                         size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
132                 
133                 
134                 // Size per simulation
135                 if (options.isCompressionEnabled())
136                         size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
137                 else
138                         size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
139                 
140                 
141                 // Size per flight data point
142                 int pointCount = 0;
143                 double timeSkip = options.getSimulationTimeSkip();
144                 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
145                         for (Simulation s: doc.getSimulations()) {
146                                 FlightData data = s.getSimulatedData();
147                                 if (data != null) {
148                                         for (int i=0; i < data.getBranchCount(); i++) {
149                                                 pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
150                                         }
151                                 }
152                         }
153                 }
154                 
155                 if (options.isCompressionEnabled())
156                         size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
157                 else
158                         size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
159                 
160                 return size;
161         }
162         
163
164         /**
165          * Determine which file version is required in order to store all the features of the
166          * current design.  By default the oldest version that supports all the necessary features
167          * will be used.
168          * 
169          * @param document      the document to output.
170          * @param opts          the storage options.
171          * @return                      the integer file version to use.
172          */
173         private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
174                 /*
175                  * File version 1.1 is required for:
176                  *  - fin tabs
177                  *  - components attached to tube coupler
178                  * 
179                  * Otherwise use version 1.0.
180                  */
181                 
182                 // Check for fin tabs (version 1.1)
183                 Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
184                 while (iterator.hasNext()) {
185                         RocketComponent c = iterator.next();
186                         
187                         // Check for fin tabs
188                         if (c instanceof FinSet) {
189                                 FinSet fin = (FinSet)c;
190                                 if (!MathUtil.equals(fin.getTabHeight(),0) &&
191                                                 !MathUtil.equals(fin.getTabLength(), 0)) {
192                                         return FILE_VERSION_DIVISOR + 1;
193                                 }
194                         }
195                         
196                         // Check for components attached to tube coupler
197                         if (c instanceof TubeCoupler) {
198                                 if (c.getChildCount() > 0) {
199                                         return FILE_VERSION_DIVISOR + 1;
200                                 }
201                         }
202                 }
203                 
204                 // Default (version 1.0)
205                 return FILE_VERSION_DIVISOR + 0;
206         }
207         
208         
209         
210         @SuppressWarnings("unchecked")
211         private void saveComponent(RocketComponent component) throws IOException {
212                 
213                 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
214                                 "getElements", RocketComponent.class);
215                 if (m==null) {
216                         throw new RuntimeException("Unable to find saving class for component "+
217                                         component.getComponentName());
218                 }
219
220                 // Get the strings to save
221                 List<String> list = (List<String>) m.invokeStatic(component);
222                 int length = list.size();
223                 
224                 if (length == 0)  // Nothing to do
225                         return;
226
227                 if (length < 2) {
228                         throw new RuntimeException("BUG, component data length less than two lines.");
229                 }
230                 
231                 // Open element
232                 writeln(list.get(0));
233                 indent++;
234                 
235                 // Write parameters
236                 for (int i=1; i<length-1; i++) {
237                         writeln(list.get(i));
238                 }
239                 
240                 // Recursively write subcomponents
241                 if (component.getChildCount() > 0) {
242                         writeln("");
243                         writeln("<subcomponents>");
244                         indent++;
245                         boolean emptyline = false;
246                         for (RocketComponent subcomponent: component) {
247                                 if (emptyline)
248                                         writeln("");
249                                 emptyline = true;
250                                 saveComponent(subcomponent);
251                         }
252                         indent--;
253                         writeln("</subcomponents>");
254                 }
255                 
256                 // Close element
257                 indent--;
258                 writeln(list.get(length-1));
259         }
260
261         
262         
263         private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
264                 SimulationConditions cond = simulation.getConditions();
265                 
266                 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
267                 indent++;
268                 
269                 writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
270                 // TODO: MEDIUM: Other simulators/calculators
271                 writeln("<simulator>RK4Simulator</simulator>");
272                 writeln("<calculator>BarrowmanCalculator</calculator>");
273                 writeln("<conditions>");
274                 indent++;
275                 
276                 writeElement("configid", cond.getMotorConfigurationID());
277                 writeElement("launchrodlength", cond.getLaunchRodLength());
278                 writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI); 
279                 writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
280                 writeElement("windaverage", cond.getWindSpeedAverage());
281                 writeElement("windturbulence", cond.getWindTurbulenceIntensity());
282                 writeElement("launchaltitude", cond.getLaunchAltitude());
283                 writeElement("launchlatitude", cond.getLaunchLatitude());
284                 
285                 if (cond.isISAAtmosphere()) {
286                         writeln("<atmosphere model=\"isa\"/>");
287                 } else {
288                         writeln("<atmosphere model=\"extendedisa\">");
289                         indent++;
290                         writeElement("basetemperature", cond.getLaunchTemperature());
291                         writeElement("basepressure", cond.getLaunchPressure());
292                         indent--;
293                         writeln("</atmosphere>");
294                 }
295
296                 writeElement("timestep", cond.getTimeStep());
297                 
298                 indent--;
299                 writeln("</conditions>");
300                 
301                 
302                 for (String s: simulation.getSimulationListeners()) {
303                         writeElement("listener", escapeXML(s));
304                 }
305                 
306                 
307                 // Write basic simulation data
308                 
309                 FlightData data = simulation.getSimulatedData();
310                 if (data != null) {
311                         String str = "<flightdata";
312                         if (!Double.isNaN(data.getMaxAltitude()))
313                                 str += " maxaltitude=\"" + TextUtil.doubleToString(data.getMaxAltitude()) + "\"";
314                         if (!Double.isNaN(data.getMaxVelocity()))
315                                 str += " maxvelocity=\"" + TextUtil.doubleToString(data.getMaxVelocity()) + "\"";
316                         if (!Double.isNaN(data.getMaxAcceleration()))
317                                 str += " maxacceleration=\"" + TextUtil.doubleToString(data.getMaxAcceleration()) + "\"";
318                         if (!Double.isNaN(data.getMaxMachNumber()))
319                                 str += " maxmach=\"" + TextUtil.doubleToString(data.getMaxMachNumber()) + "\"";
320                         if (!Double.isNaN(data.getTimeToApogee()))
321                                 str += " timetoapogee=\"" + TextUtil.doubleToString(data.getTimeToApogee()) + "\"";
322                         if (!Double.isNaN(data.getFlightTime()))
323                                 str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\"";
324                         if (!Double.isNaN(data.getGroundHitVelocity()))
325                                 str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\"";
326                         str += ">";
327                         writeln(str);
328                         indent++;
329                         
330                         for (Warning w: data.getWarningSet()) {
331                                 writeElement("warning", escapeXML(w.toString()));
332                         }
333                         
334                         // Check whether to store data
335                         if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
336                                 timeSkip = 0;
337                         
338                         if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
339                                 for (int i=0; i<data.getBranchCount(); i++) {
340                                         FlightDataBranch branch = data.getBranch(i);
341                                         saveFlightDataBranch(branch, timeSkip);
342                                 }
343                         }
344                         
345                         indent--;
346                         writeln("</flightdata>");
347                 }
348                 
349                 indent--;
350                 writeln("</simulation>");
351                 
352         }
353         
354         
355         
356         private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) 
357         throws IOException {
358                 double previousTime = -100000;
359                 
360                 if (branch == null)
361                         return;
362                 
363                 // Retrieve the types from the branch
364                 FlightDataBranch.Type[] types = branch.getTypes();
365                 
366                 if (types.length == 0)
367                         return;
368                 
369                 // Retrieve the data from the branch
370                 List<List<Double>> data = new ArrayList<List<Double>>(types.length);
371                 for (int i=0; i<types.length; i++) {
372                         data.add(branch.get(types[i]));
373                 }
374                 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
375                 if (timeData == null) {
376                         // TODO: MEDIUM: External data may not have time data
377                         throw new IllegalArgumentException("Data did not contain time data");
378                 }
379                 
380                 // Build the <databranch> tag
381                 StringBuilder sb = new StringBuilder();
382                 sb.append("<databranch name=\"");
383                 sb.append(escapeXML(branch.getBranchName()));
384                 sb.append("\" types=\"");
385                 for (int i=0; i<types.length; i++) {
386                         if (i > 0)
387                                 sb.append(",");
388                         sb.append(escapeXML(types[i].getName()));
389                 }
390                 sb.append("\">");
391                 writeln(sb.toString());
392                 indent++;
393                 
394                 // Write events
395                 for (Pair<Double,FlightEvent> p: branch.getEvents()) {
396                         writeln("<event time=\"" + TextUtil.doubleToString(p.getU())
397                                         + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
398                 }
399                 
400                 // Write the data
401                 int length = branch.getLength();
402                 if (length > 0) {
403                         writeDataPointString(data, 0, sb);
404                         previousTime = timeData.get(0);
405                 }
406                 
407                 for (int i=1; i < length-1; i++) {
408                         if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
409                                         Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
410                                 writeDataPointString(data, i, sb);
411                                 previousTime = timeData.get(i);
412                         }
413                 }
414                 
415                 if (length > 1) {
416                         writeDataPointString(data, length-1, sb);
417                 }
418                 
419                 indent--;
420                 writeln("</databranch>");
421         }
422         
423         
424         
425         /* TODO: LOW: This is largely duplicated from above! */
426         private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
427                 int count = 0;
428
429                 double previousTime = -100000;
430                 
431                 if (branch == null)
432                         return 0;
433                 
434                 // Retrieve the types from the branch
435                 FlightDataBranch.Type[] types = branch.getTypes();
436                 
437                 if (types.length == 0)
438                         return 0;
439                 
440                 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
441                 if (timeData == null) {
442                         // TODO: MEDIUM: External data may not have time data
443                         throw new IllegalArgumentException("Data did not contain time data");
444                 }
445                 
446                 // Write the data
447                 int length = branch.getLength();
448                 if (length > 0) {
449                         count++;
450                         previousTime = timeData.get(0);
451                 }
452                 
453                 for (int i=1; i < length-1; i++) {
454                         if (Math.abs(timeData.get(i) - previousTime - timeSkip) < 
455                                         Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
456                                 count++;
457                                 previousTime = timeData.get(i);
458                         }
459                 }
460                 
461                 if (length > 1) {
462                         count++;
463                 }
464
465                 return count;
466         }
467         
468         
469         
470         private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
471         throws IOException {
472                 sb.setLength(0);
473                 sb.append("<datapoint>");
474                 for (int j=0; j < data.size(); j++) {
475                         if (j > 0)
476                                 sb.append(",");
477                         sb.append(TextUtil.doubleToString(data.get(j).get(index)));
478                 }
479                 sb.append("</datapoint>");
480                 writeln(sb.toString());
481         }
482         
483         
484         
485         private void writeElement(String element, Object content) throws IOException {
486                 if (content == null)
487                         content = "";
488                 writeln("<"+element+">"+content+"</"+element+">");
489         }
490
491
492         
493         private void writeln(String str) throws IOException {
494                 if (str.length() == 0) {
495                         dest.write("\n");
496                         return;
497                 }
498                 String s="";
499                 for (int i=0; i<indent; i++)
500                         s=s+"  ";
501                 s = s+str+"\n";
502                 dest.write(s);
503         }
504         
505         
506         public static void main(String[] arg) {
507                 double d = -0.000000123456789123;
508                 
509                 
510                 for (int i=0; i< 20; i++) {
511                         String str = TextUtil.doubleToString(d);
512                         System.out.println(str + "   ->   " + Double.parseDouble(str));
513                         d *= 10;
514                 }
515                 
516                 
517                 System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
518                 
519         }
520
521         
522         /**
523          * Return the XML equivalent of an enum name.
524          * 
525          * @param e             the enum to save.
526          * @return              the corresponding XML name.
527          */
528         public static String enumToXMLName(Enum<?> e) {
529                 return e.name().toLowerCase().replace("_", "");
530         }
531         
532 }