View Javadoc
1   package fr.ifremer.dali.ui.swing.util.table.export;
2   
3   /*
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.base.Joiner;
27  import com.google.common.escape.Escaper;
28  import com.google.common.escape.Escapers;
29  import fr.ifremer.quadrige3.ui.core.dto.QuadrigeBean;
30  import fr.ifremer.dali.dto.DaliBeans;
31  import fr.ifremer.dali.ui.swing.action.AbstractDaliAction;
32  import fr.ifremer.dali.ui.swing.util.DaliUI;
33  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliRowUIModel;
34  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIHandler;
35  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIModel;
36  import fr.ifremer.dali.ui.swing.util.table.DaliColumnIdentifier;
37  import fr.ifremer.quadrige3.core.dao.technical.Assert;
38  import fr.ifremer.quadrige3.core.dao.technical.csv.CSVDao;
39  import fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil;
40  import fr.ifremer.quadrige3.ui.swing.table.CheckTableColumn;
41  import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
42  import org.apache.commons.lang3.CharUtils;
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  import org.nuiton.util.DateUtil;
46  
47  import javax.swing.JEditorPane;
48  import javax.swing.JLabel;
49  import javax.swing.JOptionPane;
50  import javax.swing.event.HyperlinkEvent;
51  import javax.swing.table.TableColumn;
52  import java.awt.Component;
53  import java.io.BufferedWriter;
54  import java.io.File;
55  import java.io.IOException;
56  import java.net.URL;
57  import java.nio.charset.StandardCharsets;
58  import java.nio.file.Files;
59  import java.util.*;
60  
61  import static org.nuiton.i18n.I18n.t;
62  
63  /**
64   * generic Action for CSV table extraction
65   * <p/>
66   * Created by Ludovic on 14/12/2015.
67   */
68  public class ExportToCSVAction<M extends AbstractDaliTableUIModel<?, ?, M>, UI extends DaliUI<M, ?>, H extends AbstractDaliTableUIHandler<?, M, UI>>
69          extends AbstractDaliAction<M, UI, H> {
70  
71      public static final Log LOG = LogFactory.getLog(ExportToCSVAction.class);
72  
73      /**
74       * the localized table name to display
75       */
76      private final String tableName;
77  
78      private Collection<DaliColumnIdentifier<? extends AbstractDaliRowUIModel>> ignoredColumnIdentifiers;
79  
80      private File outputFile;
81  
82      private char separator = 0;
83  
84      private Escaper csvEscaper;
85  
86      /**
87       * <p>Constructor for ExportToCSVAction.</p>
88       *
89       * @param handler a H object.
90       */
91      @SafeVarargs
92      public ExportToCSVAction(H handler, String tableName, DaliColumnIdentifier<? extends AbstractDaliRowUIModel>... identifiersToIgnore) {
93          super(handler, false);
94          setActionDescription(t("dali.action.extract.table.label"));
95          Assert.notBlank(tableName);
96          this.tableName = tableName;
97          if (identifiersToIgnore != null) {
98              getIgnoredColumnIdentifiers().addAll(Arrays.asList(identifiersToIgnore));
99          }
100     }
101 
102     private Collection<DaliColumnIdentifier<? extends AbstractDaliRowUIModel>> getIgnoredColumnIdentifiers() {
103         if (ignoredColumnIdentifiers == null) {
104             ignoredColumnIdentifiers = new ArrayList<>();
105         }
106         return ignoredColumnIdentifiers;
107     }
108 
109     /**
110      * <p>Getter for the field <code>outputFile</code>.</p>
111      *
112      * @return a {@link File} object.
113      */
114     private File getOutputFile() {
115         return outputFile;
116     }
117 
118     /**
119      * <p>getTable.</p>
120      *
121      * @return a {@link SwingTable} object.
122      */
123     protected SwingTable getTable() {
124         return getHandler().getTable();
125     }
126 
127     /**
128      * Get the visible column list, except the CheckTableColumn if exists
129      *
130      * @return column list
131      */
132     protected List<TableColumn> getColumns() {
133         return DaliBeans.filterCollection(getTable().getColumns(false),
134                 column -> column != null
135                         && !(column instanceof CheckTableColumn)
136                         && column.getIdentifier() instanceof DaliColumnIdentifier
137                         && !getIgnoredColumnIdentifiers().contains(column.getIdentifier())
138         );
139     }
140 
141     /**
142      * the CSV separator character
143      *
144      * @return CSV separator character
145      */
146     protected char getSeparator() {
147         if (separator == 0) {
148             separator = getConfig().getCsvSeparator().charAt(0);
149         }
150         return separator;
151     }
152 
153     /**
154      * Build and get an escaper that will remove all unwanted character like \r, \n or the CSV separator, from a string value
155      *
156      * @return a string cleaner
157      */
158     private Escaper getCsvEscaper() {
159         if (csvEscaper == null) {
160             csvEscaper = Escapers.builder().addEscape(CharUtils.CR, "").addEscape(CharUtils.LF, "").addEscape(getSeparator(), "").build();
161         }
162         return csvEscaper;
163     }
164 
165     /**
166      * {@inheritDoc}
167      */
168     @Override
169     public boolean prepareAction() throws Exception {
170         if (!super.prepareAction()) {
171             return false;
172         }
173 
174         // prepare file name
175         String date = DateUtil.formatDate(new Date(), "yyyy-MM-dd");
176         String fileName = String.format("%s-%s-%s", t("dali.service.extraction.file.prefix"), tableName, date);
177         String fileExtension = getConfig().getExtractionResultFileExtension();
178 
179         // ask user file where to export table
180         outputFile = saveFile(
181                 fileName,
182                 fileExtension,
183                 t("dali.action.extract.table.choose.file.title", tableName),
184                 t("dali.action.extract.table.choose.file.buttonLabel"),
185                 "^.*\\." + fileExtension,
186                 t("dali.common.file." + fileExtension));
187 
188         return outputFile != null;
189     }
190 
191     /**
192      * {@inheritDoc}
193      */
194     @Override
195     public void doAction() throws Exception {
196 
197         // create a writer
198         try (BufferedWriter writer = Files.newBufferedWriter(getOutputFile().toPath(), StandardCharsets.UTF_8)) {
199 
200             // first octet of file is the UTF8 BOM to allow encoding recognition (ex: Excel)
201             writer.write(CSVDao.UTF8_BOM);
202 
203             // write the header to file
204             writeHeader(writer);
205 
206             // write lines to file
207             writeLines(writer);
208 
209             // export done
210             writer.flush();
211 
212         }
213 
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     public void postSuccessAction() {
221 
222         String text = t("dali.action.extract.table.done", tableName, outputFile.getAbsoluteFile().toURI(), outputFile.getAbsolutePath());
223 
224         JEditorPane editorPane = new JEditorPane("text/html", text);
225         editorPane.setEditable(false);
226         editorPane.addHyperlinkListener(e -> {
227             if (HyperlinkEvent.EventType.ACTIVATED == e.getEventType()) {
228                 URL url = e.getURL();
229                 if (LOG.isInfoEnabled()) {
230                     LOG.info("open url: " + url);
231                 }
232                 ApplicationUIUtil.openLink(url);
233             }
234         });
235 
236         getContext().getDialogHelper().showOptionDialog(
237                 null,
238                 editorPane,
239                 t("dali.action.extract.table.title", tableName),
240                 JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION);
241 
242     }
243 
244     /**
245      * {@inheritDoc}
246      */
247     @Override
248     protected void releaseAction() {
249         outputFile = null;
250         super.releaseAction();
251     }
252 
253     private void writeHeader(BufferedWriter writer) throws IOException {
254 
255         writer.write(getHeader());
256         writer.newLine();
257 
258     }
259 
260     /**
261      * Get the header string. Built from the identifiers of all columns.
262      *
263      * @return the header string
264      */
265     protected String getHeader() {
266 
267         return Joiner.on(getSeparator()).skipNulls().join(DaliBeans.transformCollection(getColumns(), tableColumn -> {
268             DaliColumnIdentifier identifier = (DaliColumnIdentifier) tableColumn.getIdentifier();
269             return t(identifier.getHeaderI18nKey());
270         }));
271     }
272 
273     /**
274      * Write lines of the table into writer
275      *
276      * @param writer opened writer
277      * @throws IOException if any
278      */
279     private void writeLines(BufferedWriter writer) throws IOException {
280 
281         int nbRows = getTable().getRowCount();
282         // iterate over each line
283         for (int rowIndex = 0; rowIndex < nbRows; rowIndex++) {
284 
285             // the row values to write
286             List<String> rowValues = new ArrayList<>(getColumns().size());
287 
288             // iterate over columns
289             for (TableColumn column : getColumns()) {
290 
291                 // default value
292                 String stringValue = "";
293 
294                 // get the object value from the table model
295                 Object value = getTable().getModel().getValueAt(rowIndex, column.getModelIndex());
296 
297                 // try to stringify the object value
298                 if (value != null) {
299                     stringValue = getStringValue(value, rowIndex, column);
300                 }
301 
302                 // add escaped string value to list
303                 rowValues.add(getCsvEscaper().escape(stringValue));
304             }
305 
306             // write line
307             writer.write(Joiner.on(getSeparator()).join(rowValues));
308             writer.newLine();
309         }
310     }
311 
312     /**
313      * Get the String representation of the value
314      *
315      * @param value    the value to stringify
316      * @param rowIndex the current row index
317      * @param column   the current column
318      * @return the string representing the value
319      */
320     private String getStringValue(Object value, int rowIndex, TableColumn column) {
321 
322         if (value instanceof QuadrigeBean && column.getCellRenderer() != null) {
323             // use the specific renderer
324             Component component = column.getCellRenderer().getTableCellRendererComponent(getTable(), value, false, false, rowIndex, column.getModelIndex());
325             if (component instanceof JLabel) {
326                 return ((JLabel) component).getText();
327             }
328         }
329 
330         // default behavior
331         return value.toString();
332     }
333 }