001package jmri.util.table;
002
003import java.awt.event.ActionEvent;
004import java.io.File;
005import java.io.FileOutputStream;
006import java.io.IOException;
007import java.io.OutputStreamWriter;
008import java.nio.charset.StandardCharsets;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.List;
012
013import javax.annotation.Nonnull;
014import javax.swing.AbstractAction;
015import javax.swing.filechooser.FileNameExtensionFilter;
016import javax.swing.JFileChooser;
017import javax.swing.JTable;
018import javax.swing.table.TableModel;
019
020import jmri.util.FileUtil;
021import jmri.util.swing.JmriJOptionPane;
022
023import org.apache.commons.csv.CSVFormat;
024import org.apache.commons.csv.CSVPrinter;
025
026/**
027 * Save a JTable or AbstractTableModel to CSV file after prompting for filename.
028 * <p>
029 * First line contains Column Headings.
030 * Save order can replicate current JTable sort, filters, 
031 * visible columns and column order.
032 * Entire Table Model can be saved by not specifying a JTable.
033 * Can exclude specific columns ( e.g. JButtons ) from the save.
034 *
035 * @author Steve Young Copyright (C) 2020
036 * @since 4.19.5
037 */ 
038public class JTableToCsvAction extends AbstractAction {
039
040    private final JFileChooser _fileChooser;
041    private File _saveFile;
042    private final JTable _table;
043    private final TableModel _model;
044    private final String _defaultFileName;
045    private String _saveFileName;
046    private final int[] _excludedCols;
047    private List<Integer> modelColList;
048    private List<Integer> modelRowList;
049    
050    /**
051     * Create a new Save to CSV Action.
052     * 
053     * @param actionName Action Name
054     * @param jtable to save the view, else null for whole table.
055     * @param model Table Model to use.
056     * @param defaultFileName File Name to use as default.
057     * @param excludedCols int Array of Table Model columns to exclude.
058     */
059    public JTableToCsvAction(String actionName, JTable jtable, @Nonnull TableModel model, 
060        @Nonnull String defaultFileName, @Nonnull int[] excludedCols ){
061        super(actionName);
062        _table = jtable;
063        _model = model;
064        _defaultFileName = defaultFileName;
065        _excludedCols = excludedCols;
066        _fileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
067    }
068    
069    /**
070     * {@inheritDoc}
071     */
072    @Override
073    public void actionPerformed(ActionEvent e) {
074        
075        _fileChooser.setFileFilter(new FileNameExtensionFilter(Bundle.getMessage("CSVFileLabel"), "csv"));
076        _fileChooser.setSelectedFile(new File(_defaultFileName));
077        
078        // handle selection or cancel
079        if (_fileChooser.showSaveDialog(_table) == JFileChooser.APPROVE_OPTION) {
080            _saveFileName = _fileChooser.getSelectedFile().getPath();
081            if (!_saveFileName.regionMatches(true,_saveFileName.length()-4,".csv",0,4)) { // 
082                _saveFileName += ".csv";
083            }
084            _saveFile = new File(_saveFileName);
085            if (continueIfExisting()) {
086                saveToCSV();
087            }
088        }
089    }
090    
091    private boolean continueIfExisting(){
092        return !(_saveFile.isFile() && ( JmriJOptionPane.showConfirmDialog(_table,
093            Bundle.getMessage("ConfirmOverwriteFile"),
094            Bundle.getMessage("ConfirmQuestion"), JmriJOptionPane.YES_NO_OPTION,
095            JmriJOptionPane.QUESTION_MESSAGE)
096            != JmriJOptionPane.YES_OPTION));
097    }
098
099    private void saveToCSV() {
100        
101        createColumnsAndRows();
102        
103        try (CSVPrinter p = new CSVPrinter(new OutputStreamWriter(
104            new FileOutputStream(_saveFileName), StandardCharsets.UTF_8), CSVFormat.DEFAULT)) {
105            
106            addToFile(p);
107            
108            p.flush();
109            p.close();
110        } catch (IOException e) {
111            log.error("Error Saving Table to CSV File {}",e.toString());
112        }
113    }
114    
115    private void addToFile(CSVPrinter p) throws IOException {
116    
117        // Save table per row. _saveFileName
118        // print header labels
119        List<String> headers = new ArrayList<>();
120        for (int i = 0; i < modelColList.size(); i++) {
121            headers.add(_model.getColumnName(modelColList.get(i)));
122        }
123        p.printRecord(headers);
124
125        // print rows
126        for (int i = 0; i < modelRowList.size(); i++) {
127            List<String> row = new ArrayList<>();
128            for (int j = 0; j < modelColList.size(); j++) {
129                String val = String.valueOf( _model.getValueAt(modelRowList.get(i), modelColList.get(j)));
130                if (val.equals("null")) {
131                    val = "";
132                }
133                row.add(val);
134            }
135            p.printRecord(row);
136        }
137    
138    }
139    
140    private void createColumnsAndRows(){
141    
142        modelColList = new ArrayList<>();
143        modelRowList = new ArrayList<>();
144        
145        if ( _table != null ) {
146            for (int i = 0; i < _table.getColumnCount(); i++) {
147                addColtoListIfNotExcluded(modelColList,_table.convertColumnIndexToModel(i),_excludedCols);
148            }
149            for (int i = 0; i < _table.getRowCount(); i++) {
150                modelRowList.add(_table.convertRowIndexToModel(i));
151            }
152        } else {
153            for (int i = 0; i < _model.getColumnCount(); i++) {
154                addColtoListIfNotExcluded(modelColList,i,_excludedCols);
155            }
156            for (int i = 0; i < _model.getRowCount(); i++) {
157                modelRowList.add(i);
158            }
159        }
160    
161    }
162    
163    private void addColtoListIfNotExcluded(@Nonnull List<Integer> list, 
164        int modelCol, @Nonnull int[] colsToNotSave){
165        if (! Arrays.stream(colsToNotSave).anyMatch(j -> j == modelCol)){
166            list.add(modelCol);
167        }
168    }
169
170    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JTableToCsvAction.class);
171
172}