From 525190b37fced095149c50fae8c1361a6137e528 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Mon, 26 Aug 2024 02:26:46 +0200 Subject: [PATCH] [#2525] Allow component analysis to be exported to a CSV file --- .../info/openrocket/core/file/CSVExport.java | 179 ++++++++++++++-- .../openrocket/core/util/StringUtils.java | 3 + .../main/resources/l10n/messages.properties | 2 + .../componentanalysis/CAExportPanel.java | 106 ++++++++-- .../ComponentAnalysisPlotExportPanel.java | 4 + .../info/openrocket/swing/gui/plot/Util.java | 3 +- .../gui/simulation/SimulationExportPanel.java | 2 +- .../swing/gui/util/SaveCSVWorker.java | 199 ++++++++++++------ 8 files changed, 397 insertions(+), 101 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/file/CSVExport.java b/core/src/main/java/info/openrocket/core/file/CSVExport.java index 404b009a5..9c96f0fe6 100644 --- a/core/src/main/java/info/openrocket/core/file/CSVExport.java +++ b/core/src/main/java/info/openrocket/core/file/CSVExport.java @@ -7,15 +7,23 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import info.openrocket.core.componentanalysis.CADataBranch; +import info.openrocket.core.componentanalysis.CADataType; +import info.openrocket.core.componentanalysis.CADomainDataType; +import info.openrocket.core.componentanalysis.CAParameters; import info.openrocket.core.logging.Warning; import info.openrocket.core.logging.WarningSet; import info.openrocket.core.document.Simulation; +import info.openrocket.core.rocketcomponent.AxialStage; +import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.simulation.FlightData; import info.openrocket.core.simulation.FlightDataBranch; import info.openrocket.core.simulation.FlightDataType; import info.openrocket.core.simulation.FlightEvent; import info.openrocket.core.unit.Unit; +import info.openrocket.core.util.StringUtils; import info.openrocket.core.util.TextUtil; public class CSVExport { @@ -51,7 +59,6 @@ public class CSVExport { PrintWriter writer = null; try { - writer = new PrintWriter(stream, false, StandardCharsets.UTF_8); // Write the initial comments @@ -88,10 +95,66 @@ public class CSVExport { } } - private static void writeData(PrintWriter writer, FlightDataBranch branch, - FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, - boolean isExponentialNotation, - boolean eventComments, String commentStarter) { + public static void exportCSV(OutputStream stream, CAParameters parameters, CADataBranch branch, + CADomainDataType domainDataType, CADataType[] fields, + Map> components, Unit[] units, + String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, + boolean analysisComments, boolean fieldDescriptions, String commentStarter) throws IOException { + if (fields.length != units.length) { + throw new IllegalArgumentException("fields and units lengths must be equal " + + "(" + fields.length + " vs " + units.length + ")"); + } + if (fields.length != components.size()) { + throw new IllegalArgumentException("fields and components lengths must be equal " + + "(" + fields.length + " vs " + components.size() + ")"); + } + + PrintWriter writer = null; + try { + writer = new PrintWriter(stream, false, StandardCharsets.UTF_8); + + // Component analysis comments + if (analysisComments) { + writeComponentAnalysisComments(writer, parameters, branch, domainDataType, fields, components, + fieldSeparator, commentStarter); + } + + // Field names + if (fieldDescriptions) { + writer.print(prependComment(commentStarter, StringUtils.removeHTMLTags(domainDataType.getName()))); + writer.print(fieldSeparator); + for (int i = 0; i < fields.length; i++) { + for (int j = 0; j < components.get(fields[i]).size(); j++) { + writer.print(StringUtils.removeHTMLTags(fields[i].getName()) + + " (" + components.get(fields[i]).get(j).getName() + ") (" + + units[i].getUnit() + ")"); + if (i < fields.length - 1) { + writer.print(fieldSeparator); + } else if (j < components.get(fields[i]).size() - 1) { + writer.print(fieldSeparator); + } + } + } + writer.println(); + } + + writeData(writer, branch, domainDataType, fields, components, units, fieldSeparator, decimalPlaces, + isExponentialNotation); + + } finally { + if (writer != null) { + try { + writer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + private static void writeData(PrintWriter writer, FlightDataBranch branch, FlightDataType[] fields, Unit[] units, + String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, + boolean eventComments, String commentStarter) { // Time variable List time = branch.get(FlightDataType.TYPE_TIME); @@ -120,7 +183,6 @@ public class CSVExport { // Loop over all data points for (int pos = 0; pos < n; pos++) { - // Check for events to store if (eventComments && time != null) { double t = time.get(pos); @@ -142,7 +204,6 @@ public class CSVExport { } } writer.println(); - } // Store any remaining events @@ -152,13 +213,54 @@ public class CSVExport { eventPosition++; } } + } + private static void writeData(PrintWriter writer, CADataBranch branch, CADomainDataType domainDataType, + CADataType[] fields, Map> components, Unit[] units, + String fieldSeparator, int decimalPlaces, boolean isExponentialNotation) { + List domainValues = branch.get(domainDataType); + + int n = domainValues != null ? domainValues.size() : branch.getLength(); + + // List of field values + List> fieldValues = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + Unit unit = units[i]; + for (RocketComponent c : components.get(fields[i])) { + List values = branch.get(fields[i], c); + + // Convert the values to the correct unit + values.replaceAll(unit::toUnit); + + fieldValues.add(values); + } + } + + // Loop over all data points + for (int pos = 0; pos < n; pos++) { + // Store domain type + if (domainValues != null) { + writer.print(TextUtil.doubleToString(domainValues.get(pos), decimalPlaces, isExponentialNotation)); + writer.print(fieldSeparator); + } + + // Store CSV line + for (int i = 0; i < fieldValues.size(); i++) { + double value = fieldValues.get(i).get(pos); + writer.print(TextUtil.doubleToString(value, decimalPlaces, isExponentialNotation)); + + if (i < fieldValues.size() - 1) { + writer.print(fieldSeparator); + } + } + writer.println(); + } } private static void printEvent(PrintWriter writer, FlightEvent e, String commentStarter) { - writer.println(commentStarter + " Event " + e.getType().name() + - " occurred at t=" + TextUtil.doubleToString(e.getTime()) + " seconds"); + writer.println(prependComment(commentStarter, "Event " + e.getType().name() + + " occurred at t=" + TextUtil.doubleToString(e.getTime()) + " seconds")); } private static void writeSimulationComments(PrintWriter writer, @@ -193,23 +295,70 @@ public class CSVExport { break; } - writer.println(commentStarter + " " + line); + writer.println(prependComment(commentStarter,line)); - writer.println(commentStarter + " " + branch.getLength() + " data points written for " - + fields.length + " variables."); + writer.println(prependComment(commentStarter, branch.getLength() + " data points written for " + + fields.length + " variables.")); if (data == null) { - writer.println(commentStarter + " No simulation data available."); + writer.println(prependComment(commentStarter, "No simulation data available.")); return; } WarningSet warnings = data.getWarningSet(); if (!warnings.isEmpty()) { - writer.println(commentStarter + " Simulation warnings:"); + writer.println(prependComment(commentStarter,"Simulation warnings:")); for (Warning w : warnings) { - writer.println(commentStarter + " " + w.toString()); + writer.println(prependComment(commentStarter, " " + w.toString())); } } } + private static void writeComponentAnalysisComments(PrintWriter writer, CAParameters parameters, CADataBranch branch, + CADomainDataType domainDataType, CADataType[] fields, + Map> components, + String fieldSeparator, String commentStarter) { + StringBuilder line = new StringBuilder(prependComment(commentStarter, "Parameters:")).append(fieldSeparator); + + // TODO: use proper units for the parameters + if (domainDataType != CADomainDataType.WIND_DIRECTION) { + line.append("Wind direction:").append(fieldSeparator); + line.append(parameters.getTheta()).append("°").append(fieldSeparator); + } + if (domainDataType != CADomainDataType.AOA) { + line.append("Angle of attack:").append(fieldSeparator); + line.append(parameters.getAOA()).append("°").append(fieldSeparator); + } + if (domainDataType != CADomainDataType.MACH) { + line.append("Mach:").append(fieldSeparator); + line.append(parameters.getMach()).append(fieldSeparator); + } + if (domainDataType != CADomainDataType.ROLL_RATE) { + line.append("Roll rate:").append(fieldSeparator); + line.append(parameters.getRollRate()).append("°/s").append(fieldSeparator); + } + + line.append("Active stages:").append(fieldSeparator); + List activeStages = parameters.getSelectedConfiguration().getActiveStages(); + for (AxialStage stage: activeStages) { + line.append(stage.getName()).append(fieldSeparator); + } + + line.append("Flight configuration:").append(fieldSeparator); + line.append(parameters.getSelectedConfiguration().getName()); + + writer.println(line); + + int nrOfVariables = 0; + for (CADataType t : fields) { + nrOfVariables += components.get(t).size(); + } + + writer.println(prependComment(commentStarter, branch.getLength() + " data points written for " + + nrOfVariables + " variables.")); + } + + private static String prependComment(String commentStarter, String comment) { + return commentStarter + " " + comment; + } } diff --git a/core/src/main/java/info/openrocket/core/util/StringUtils.java b/core/src/main/java/info/openrocket/core/util/StringUtils.java index d918e473a..2b8712da2 100644 --- a/core/src/main/java/info/openrocket/core/util/StringUtils.java +++ b/core/src/main/java/info/openrocket/core/util/StringUtils.java @@ -104,4 +104,7 @@ public class StringUtils { return sb.toString(); } + public static String removeHTMLTags(String input) { + return input.replaceAll("<[^>]*>", ""); + } } diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 08298568c..4e0851b09 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -968,6 +968,8 @@ CAPlotExportDialog.tab.Export = Export ! CAExportPanel CAExportPanel.Col.Components = Components +CAExportPanel.checkbox.Includecadesc = Include component analysis description +CAExportPanel.checkbox.ttip.Includecadesc = Include information at the beginning of the file describing the component analysis. ! CADataTypeGroup CADataTypeGroup.DOMAIN = Domain Parameter diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java index 751a335d4..1ae8b2afc 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java @@ -1,5 +1,6 @@ package info.openrocket.swing.gui.dialogs.componentanalysis; +import info.openrocket.core.componentanalysis.CADataBranch; import info.openrocket.core.componentanalysis.CADataType; import info.openrocket.core.l10n.Translator; import info.openrocket.core.rocketcomponent.RocketComponent; @@ -7,6 +8,7 @@ import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; import info.openrocket.swing.gui.components.CsvOptionPanel; import info.openrocket.swing.gui.util.FileHelper; +import info.openrocket.swing.gui.util.SaveCSVWorker; import info.openrocket.swing.gui.util.SwingPreferences; import info.openrocket.swing.gui.widgets.CSVExportPanel; import info.openrocket.swing.gui.widgets.SaveFileChooser; @@ -19,6 +21,7 @@ import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; +import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; @@ -41,15 +44,20 @@ import java.util.concurrent.atomic.AtomicBoolean; public class CAExportPanel extends CSVExportPanel { private static final long serialVersionUID = 4423905472892675964L; private static final Translator trans = Application.getTranslator(); - private static final int OPTION_FIELD_DESCRIPTIONS = 0; private static final int FIXED_COMPONENT_COLUMN_WIDTH = 500; + private static final int OPTION_COMPONENT_ANALYSIS_COMMENTS = 0; + private static final int OPTION_FIELD_DESCRIPTIONS = 1; + private final List> selectedComponents; + private final ComponentAnalysisPlotExportPanel parent; private CAExportPanel(ComponentAnalysisPlotExportPanel parent, CADataType[] types, boolean[] selected, CsvOptionPanel csvOptions, Component... extraComponents) { super(types, selected, csvOptions, true, extraComponents); + this.parent = parent; + selectedComponents = new ArrayList<>(types.length); Map componentSelectedMap; List components; @@ -80,6 +88,8 @@ public class CAExportPanel extends CSVExportPanel { } CsvOptionPanel csvOptions = new CsvOptionPanel(CAExportPanel.class, false, + trans.get("CAExportPanel.checkbox.Includecadesc"), + trans.get("CAExportPanel.checkbox.ttip.Includecadesc"), trans.get("SimExpPan.checkbox.Includefielddesc"), trans.get("SimExpPan.checkbox.ttip.Includefielddesc")); @@ -94,9 +104,16 @@ public class CAExportPanel extends CSVExportPanel { table.getColumnModel().getColumn(1).setCellRenderer(new LeftAlignedRenderer()); table.getColumnModel().getColumn(2).setCellRenderer(new LeftAlignedRenderer()); + ComponentCheckBoxPanel.ComponentSelectionListener listener = (newStates) -> { + int row = table.getSelectedRow(); + if (row != -1) { + selectedComponents.set(row, newStates); + } + }; + TableColumn componentColumn = table.getColumnModel().getColumn(3); - componentColumn.setCellRenderer(new ComponentCheckBoxRenderer()); - componentColumn.setCellEditor(new ComponentCheckBoxEditor()); + componentColumn.setCellRenderer(new ComponentCheckBoxRenderer(listener)); + componentColumn.setCellEditor(new ComponentCheckBoxEditor(listener)); componentColumn.setPreferredWidth(FIXED_COMPONENT_COLUMN_WIDTH); // Set specific client properties for FlatLaf @@ -105,6 +122,8 @@ public class CAExportPanel extends CSVExportPanel { @Override public boolean doExport() { + CADataBranch branch = this.parent.runParameterSweep(); + JFileChooser chooser = new SaveFileChooser(); chooser.setFileFilter(FileHelper.CSV_FILTER); chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); @@ -126,7 +145,8 @@ public class CAExportPanel extends CSVExportPanel { String fieldSep = csvOptions.getFieldSeparator(); int decimalPlaces = csvOptions.getDecimalPlaces(); boolean isExponentialNotation = csvOptions.isExponentialNotation(); - boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS); + boolean analysisComments = csvOptions.getSelectionOption(OPTION_COMPONENT_ANALYSIS_COMMENTS); + boolean fieldDescriptions = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS); csvOptions.storePreferences(); // Store preferences and export @@ -150,6 +170,22 @@ public class CAExportPanel extends CSVExportPanel { } } + Map> components = new HashMap<>(); + // Iterate through the table to get selected items + for (int i = 0; i < selected.length; i++) { + if (!selected[i]) { + continue; + } + for (Map.Entry entry : selectedComponents.get(i).entrySet()) { + if (entry.getValue()) { + CADataType type = types[i]; + List typeComponents = components.getOrDefault(type, new ArrayList<>()); + typeComponents.add(entry.getKey()); + components.put(type, typeComponents); + } + } + } + if (fieldSep.equals(SPACE)) { fieldSep = " "; } else if (fieldSep.equals(TAB)) { @@ -157,9 +193,9 @@ public class CAExportPanel extends CSVExportPanel { } - /*SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces, - isExponentialNotation, commentChar, simulationComment, fieldComment, eventComment, - SwingUtilities.getWindowAncestor(this));*/ + SaveCSVWorker.exportCAData(file, parent.getParameters(), branch, parent.getSelectedParameter(), fieldTypes, + components, fieldUnits, fieldSep, decimalPlaces, isExponentialNotation, commentChar, analysisComments, + fieldDescriptions, SwingUtilities.getWindowAncestor(this)); return true; } @@ -256,6 +292,11 @@ public class CAExportPanel extends CSVExportPanel { private static class ComponentCheckBoxRenderer implements TableCellRenderer { private ComponentCheckBoxPanel panel; + private final ComponentCheckBoxPanel.ComponentSelectionListener listener; + + public ComponentCheckBoxRenderer(ComponentCheckBoxPanel.ComponentSelectionListener listener) { + this.listener = listener; + } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { @@ -270,6 +311,7 @@ public class CAExportPanel extends CSVExportPanel { if (panel == null) { panel = new ComponentCheckBoxPanel(componentMap); + panel.setComponentSelectionListener(listener); } else { panel.updateComponentStates(componentMap); } @@ -284,7 +326,6 @@ public class CAExportPanel extends CSVExportPanel { private static class ComponentCheckBoxPanel extends JPanel { private final Map checkBoxMap = new HashMap<>(); - private final ItemListener checkBoxListener; private final AtomicBoolean updatingState = new AtomicBoolean(false); private Color gridColor = Color.GRAY; private boolean isSelected = false; @@ -297,20 +338,6 @@ public class CAExportPanel extends CSVExportPanel { gbc.weightx = 1.0; gbc.insets = new Insets(2, 2, 2, 2); - checkBoxListener = e -> { - if (updatingState.get()) return; - - JCheckBox source = (JCheckBox) e.getSource(); - if (e.getStateChange() == ItemEvent.DESELECTED) { - if (checkBoxMap.values().stream().noneMatch(JCheckBox::isSelected)) { - source.setSelected(true); - } - } - - // Notify the table that the value has changed - firePropertyChange("value", null, getComponentStates()); - }; - createCheckBoxes(componentMap, gbc); // Add an empty component to push everything to the top-left @@ -405,10 +432,44 @@ public class CAExportPanel extends CSVExportPanel { g.fillRect(0, 0, getWidth(), getHeight() - 1); } } + + public interface ComponentSelectionListener { + void onComponentSelectionChanged(Map newStates); + } + + private ComponentSelectionListener listener; + + public void setComponentSelectionListener(ComponentSelectionListener listener) { + this.listener = listener; + } + + private final ItemListener checkBoxListener = e -> { + if (updatingState.get()) return; + + JCheckBox source = (JCheckBox) e.getSource(); + if (e.getStateChange() == ItemEvent.DESELECTED) { + if (checkBoxMap.values().stream().noneMatch(JCheckBox::isSelected)) { + source.setSelected(true); + } + } + + // Notify the listener of the change + if (listener != null) { + listener.onComponentSelectionChanged(getComponentStates()); + } + + // Notify the table that the value has changed + firePropertyChange("value", null, getComponentStates()); + }; } private static class ComponentCheckBoxEditor extends AbstractCellEditor implements TableCellEditor { private ComponentCheckBoxPanel panel; + private final ComponentCheckBoxPanel.ComponentSelectionListener listener; + + public ComponentCheckBoxEditor(ComponentCheckBoxPanel.ComponentSelectionListener listener) { + this.listener = listener; + } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { @@ -424,6 +485,7 @@ public class CAExportPanel extends CSVExportPanel { if (panel == null) { panel = new ComponentCheckBoxPanel(componentMap); + panel.setComponentSelectionListener(listener); } else { panel.updateComponentStates(componentMap); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java index d254cdb7a..0938929a4 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java @@ -218,6 +218,10 @@ public class ComponentAnalysisPlotExportPanel extends JPanel { return (CADomainDataType) parameterSelector.getSelectedItem(); } + public CAParameters getParameters() { + return parameters; + } + private void updateModels(CADomainDataType type) { if (type == null) { throw new IllegalArgumentException("CADomainDataType cannot be null"); diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Util.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Util.java index 8daa64c55..1e4c59472 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/Util.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Util.java @@ -9,6 +9,7 @@ import info.openrocket.core.l10n.Translator; import info.openrocket.core.simulation.DataBranch; import info.openrocket.core.simulation.DataType; import info.openrocket.core.startup.Application; +import info.openrocket.core.util.StringUtils; public abstract class Util { private static final Translator trans = Application.getTranslator(); @@ -81,7 +82,7 @@ public abstract class Util { public static String formatHTMLString(String input) { // TODO: Use AttributeString to format the string // Remove the HTML-like tags from the final string - return input.replaceAll("|||||", ""); + return StringUtils.removeHTMLTags(input); } public static Color getPlotColor(int index) { diff --git a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java index c27d53eff..2e4d79258 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java @@ -159,7 +159,7 @@ public class SimulationExportPanel extends CSVExportPanel { } - SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces, + SaveCSVWorker.exportSimulationData(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces, isExponentialNotation, commentChar, simulationComment, fieldComment, eventComment, SwingUtilities.getWindowAncestor(this)); diff --git a/swing/src/main/java/info/openrocket/swing/gui/util/SaveCSVWorker.java b/swing/src/main/java/info/openrocket/swing/gui/util/SaveCSVWorker.java index 8f328e286..fbd7e7320 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/util/SaveCSVWorker.java +++ b/swing/src/main/java/info/openrocket/swing/gui/util/SaveCSVWorker.java @@ -5,11 +5,18 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import javax.swing.JOptionPane; import javax.swing.SwingWorker; +import info.openrocket.core.componentanalysis.CADataBranch; +import info.openrocket.core.componentanalysis.CADataType; +import info.openrocket.core.componentanalysis.CADomainDataType; +import info.openrocket.core.componentanalysis.CAParameters; +import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.swing.gui.dialogs.SwingWorkerDialog; import info.openrocket.core.document.Simulation; @@ -21,34 +28,47 @@ import info.openrocket.core.unit.Unit; import info.openrocket.core.util.BugException; import info.openrocket.core.util.TextUtil; - public class SaveCSVWorker extends SwingWorker { - + private static final int BYTES_PER_FIELD_PER_POINT = 7; private final File file; - private final Simulation simulation; - private final FlightDataBranch branch; - private final FlightDataType[] fields; - private final Unit[] units; private final String fieldSeparator; private final int decimalPlaces; private final boolean isExponentialNotation; private final String commentStarter; - private final boolean simulationComments; - private final boolean fieldComments; - private final boolean eventComments; - - + + // Simulation-specific fields + private Simulation simulation; + private FlightDataBranch flightDataBranch; + private FlightDataType[] flightDataFields; + private Unit[] flightDataUnits; + private boolean simulationComments; + private boolean fieldComments; + private boolean eventComments; + + // CA-specific fields + private CAParameters caParameters; + private CADataBranch caDataBranch; + private CADomainDataType caDomainDataType; + private CADataType[] caDataFields; + private Map> caComponents; + private Unit[] caUnits; + private boolean analysisComments; + private boolean fieldDescriptions; + + private boolean isCAData; + + // Constructor for simulation data public SaveCSVWorker(File file, Simulation simulation, FlightDataBranch branch, - FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, - boolean isExponentialNotation, String commentStarter, boolean simulationComments, - boolean fieldComments, boolean eventComments) { + FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, + boolean isExponentialNotation, String commentStarter, boolean simulationComments, + boolean fieldComments, boolean eventComments) { this.file = file; this.simulation = simulation; - this.branch = branch; - this.fields = fields; - this.units = units; + this.flightDataBranch = branch; + this.flightDataFields = fields; + this.flightDataUnits = units; this.fieldSeparator = fieldSeparator; this.decimalPlaces = decimalPlaces; this.isExponentialNotation = isExponentialNotation; @@ -56,90 +76,145 @@ public class SaveCSVWorker extends SwingWorker { this.simulationComments = simulationComments; this.fieldComments = fieldComments; this.eventComments = eventComments; + this.isCAData = false; } + // Constructor for CA data + public SaveCSVWorker(File file, CAParameters parameters, CADataBranch branch, + CADomainDataType domainDataType, CADataType[] fields, + Map> components, Unit[] units, + String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, + String commentStarter, boolean analysisComments, boolean fieldDescriptions) { + this.file = file; + this.caParameters = parameters; + this.caDataBranch = branch; + this.caDomainDataType = domainDataType; + this.caDataFields = fields; + this.caComponents = components; + this.caUnits = units; + this.fieldSeparator = fieldSeparator; + this.decimalPlaces = decimalPlaces; + this.isExponentialNotation = isExponentialNotation; + this.commentStarter = commentStarter; + this.analysisComments = analysisComments; + this.fieldDescriptions = fieldDescriptions; + this.isCAData = true; + } @Override protected Void doInBackground() throws Exception { - - int estimate = BYTES_PER_FIELD_PER_POINT * fields.length * branch.getLength(); + int estimate = BYTES_PER_FIELD_PER_POINT * (isCAData ? caDataFields.length : flightDataFields.length) * + (isCAData ? caDataBranch.getLength() : flightDataBranch.getLength()); estimate = Math.max(estimate, 1000); - - // Create the ProgressOutputStream that provides progress estimates - @SuppressWarnings("resource") - ProgressOutputStream os = new ProgressOutputStream( - new BufferedOutputStream(new FileOutputStream(file)), + + try (ProgressOutputStream os = new ProgressOutputStream( + new BufferedOutputStream(new FileOutputStream(file)), estimate, this) { - @Override protected void setProgress(int progress) { SaveCSVWorker.this.setProgress(progress); } - - }; - - try { - CSVExport.exportCSV(os, simulation, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, - commentStarter, simulationComments, fieldComments, eventComments); - } finally { - try { - os.close(); - } catch (Exception e) { - Application.getExceptionHandler().handleErrorCondition("Error closing file", e); + }) { + if (isCAData) { + CSVExport.exportCSV(os, caParameters, caDataBranch, caDomainDataType, caDataFields, caComponents, caUnits, + fieldSeparator, decimalPlaces, isExponentialNotation, analysisComments, fieldDescriptions, commentStarter); + } else { + CSVExport.exportCSV(os, simulation, flightDataBranch, flightDataFields, flightDataUnits, fieldSeparator, + decimalPlaces, isExponentialNotation, commentStarter, simulationComments, fieldComments, eventComments); } + } catch (Exception e) { + Application.getExceptionHandler().handleErrorCondition("Error writing file", e); } return null; } - public static boolean export(File file, Simulation simulation, FlightDataBranch branch, + public static boolean exportSimulationData(File file, Simulation simulation, FlightDataBranch branch, FlightDataType[] fields, Unit[] units, String fieldSeparator, String commentStarter, boolean simulationComments, boolean fieldComments, boolean eventComments, Window parent) { - return export(file, simulation, branch, fields, units, fieldSeparator, TextUtil.DEFAULT_DECIMAL_PLACES, true, + return exportSimulationData(file, simulation, branch, fields, units, fieldSeparator, TextUtil.DEFAULT_DECIMAL_PLACES, true, commentStarter, simulationComments, fieldComments, eventComments, parent); } - + /** * Exports a CSV file using a progress dialog if necessary. * * @return true if the save was successful, false otherwise. */ - public static boolean export(File file, Simulation simulation, FlightDataBranch branch, - FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, - boolean isExponentialNotation, String commentStarter, boolean simulationComments, - boolean fieldComments, boolean eventComments, Window parent) { - + public static boolean exportSimulationData(File file, Simulation simulation, FlightDataBranch branch, + FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, + boolean isExponentialNotation, String commentStarter, boolean simulationComments, + boolean fieldComments, boolean eventComments, Window parent) { + SaveCSVWorker worker = new SaveCSVWorker(file, simulation, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation, commentStarter, simulationComments, fieldComments, eventComments); - - if (!SwingWorkerDialog.runWorker(parent, "Exporting flight data", - "Writing " + file.getName() + "...", worker)) { - - // User cancelled the save - file.delete(); - return false; - } - - try { + + if (!SwingWorkerDialog.runWorker(parent, "Exporting flight data", + "Writing " + file.getName() + "...", worker)) { + + // User cancelled the save + file.delete(); + return false; + } + + try { worker.get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); - + if (cause instanceof IOException) { - JOptionPane.showMessageDialog(parent, new String[] { - "An I/O error occurred while saving:", - e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); - return false; + JOptionPane.showMessageDialog(parent, new String[] { + "An I/O error occurred while saving:", + e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + return false; } else { throw new BugException("Unknown error when saving file", e); } - + } catch (InterruptedException e) { throw new BugException("EDT was interrupted", e); } - + return true; } -} + + // New export method for CA data + public static boolean exportCAData(File file, CAParameters parameters, CADataBranch branch, + CADomainDataType domainDataType, CADataType[] fields, + Map> components, Unit[] units, + String fieldSeparator, int decimalPlaces, boolean isExponentialNotation, + String commentStarter, boolean analysisComments, boolean fieldDescriptions, + Window parent) { + SaveCSVWorker worker = new SaveCSVWorker(file, parameters, branch, domainDataType, fields, components, units, + fieldSeparator, decimalPlaces, isExponentialNotation, commentStarter, analysisComments, fieldDescriptions); + + if (!SwingWorkerDialog.runWorker(parent, "Exporting component analysis data", + "Writing " + file.getName() + "...", worker)) { + // User cancelled the save + file.delete(); + return false; + } + + try { + worker.get(); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + + if (cause instanceof IOException) { + JOptionPane.showMessageDialog(parent, new String[] { + "An I/O error occurred while saving:", + e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + return false; + } else { + throw new BugException("Unknown error when saving file", e); + } + + } catch (InterruptedException e) { + throw new BugException("EDT was interrupted", e); + } + + return true; + } +} \ No newline at end of file