Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
SQLiteViewer.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2018-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.contentviewers;
20 
21 import java.awt.BorderLayout;
22 import java.awt.Component;
23 import java.awt.Cursor;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.function.Consumer;
36 import java.util.logging.Level;
37 import javax.swing.JComboBox;
38 import javax.swing.JFileChooser;
39 import javax.swing.JOptionPane;
40 import javax.swing.filechooser.FileNameExtensionFilter;
41 import org.apache.commons.io.FilenameUtils;
42 import org.openide.util.NbBundle;
43 import org.openide.windows.WindowManager;
48 import org.sleuthkit.datamodel.AbstractFile;
50 
54 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
55 class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
56 
57  private static final long serialVersionUID = 1L;
58  public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
59  private static final int ROWS_PER_PAGE = 100;
60  private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
61  private final SQLiteTableView selectedTableView = new SQLiteTableView();
62  private AbstractFile sqliteDbFile;
63 
64  private SQLiteTableReader viewReader;
65 
66  private Map<String, Object> row = new LinkedHashMap<>();
67  private List<Map<String, Object>> pageOfTableRows = new ArrayList<>();
68  private List<String> currentTableHeader = new ArrayList<>();
69  private String prevTableName;
70 
71  private int numRows; // num of rows in the selected table
72  private int currPage = 0; // curr page of rows being displayed
73 
77  SQLiteViewer() {
78  initComponents();
79  jTableDataPanel.add(selectedTableView, BorderLayout.CENTER);
80  }
81 
87  @SuppressWarnings("unchecked")
88  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
89  private void initComponents() {
90 
91  jHdrPanel = new javax.swing.JPanel();
92  tablesDropdownList = new javax.swing.JComboBox<>();
93  jLabel1 = new javax.swing.JLabel();
94  numEntriesField = new javax.swing.JTextField();
95  jLabel2 = new javax.swing.JLabel();
96  currPageLabel = new javax.swing.JLabel();
97  jLabel3 = new javax.swing.JLabel();
98  numPagesLabel = new javax.swing.JLabel();
99  prevPageButton = new javax.swing.JButton();
100  nextPageButton = new javax.swing.JButton();
101  exportCsvButton = new javax.swing.JButton();
102  jTableDataPanel = new javax.swing.JPanel();
103 
104  jHdrPanel.setPreferredSize(new java.awt.Dimension(536, 40));
105 
106  tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
107  tablesDropdownList.addActionListener(new java.awt.event.ActionListener() {
108  public void actionPerformed(java.awt.event.ActionEvent evt) {
109  tablesDropdownListActionPerformed(evt);
110  }
111  });
112 
113  org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel1.text")); // NOI18N
114 
115  numEntriesField.setEditable(false);
116  numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N
117  numEntriesField.setBorder(null);
118 
119  org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel2.text")); // NOI18N
120 
121  org.openide.awt.Mnemonics.setLocalizedText(currPageLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.currPageLabel.text")); // NOI18N
122 
123  org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel3.text")); // NOI18N
124 
125  org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numPagesLabel.text")); // NOI18N
126 
127  prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
128  org.openide.awt.Mnemonics.setLocalizedText(prevPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.prevPageButton.text")); // NOI18N
129  prevPageButton.setBorderPainted(false);
130  prevPageButton.setContentAreaFilled(false);
131  prevPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
132  prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
133  prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
134  prevPageButton.addActionListener(new java.awt.event.ActionListener() {
135  public void actionPerformed(java.awt.event.ActionEvent evt) {
136  prevPageButtonActionPerformed(evt);
137  }
138  });
139 
140  nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
141  org.openide.awt.Mnemonics.setLocalizedText(nextPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.nextPageButton.text")); // NOI18N
142  nextPageButton.setBorderPainted(false);
143  nextPageButton.setContentAreaFilled(false);
144  nextPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
145  nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
146  nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
147  nextPageButton.addActionListener(new java.awt.event.ActionListener() {
148  public void actionPerformed(java.awt.event.ActionEvent evt) {
149  nextPageButtonActionPerformed(evt);
150  }
151  });
152 
153  org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.exportCsvButton.text")); // NOI18N
154  exportCsvButton.addActionListener(new java.awt.event.ActionListener() {
155  public void actionPerformed(java.awt.event.ActionEvent evt) {
156  exportCsvButtonActionPerformed(evt);
157  }
158  });
159 
160  javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel);
161  jHdrPanel.setLayout(jHdrPanelLayout);
162  jHdrPanelLayout.setHorizontalGroup(
163  jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
164  .addGroup(jHdrPanelLayout.createSequentialGroup()
165  .addContainerGap()
166  .addComponent(jLabel1)
167  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
168  .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
169  .addGap(18, 18, 18)
170  .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
171  .addGap(15, 15, 15)
172  .addComponent(jLabel2)
173  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
174  .addComponent(currPageLabel)
175  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
176  .addComponent(jLabel3)
177  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
178  .addComponent(numPagesLabel)
179  .addGap(18, 18, 18)
180  .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
181  .addGap(0, 0, 0)
182  .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
183  .addGap(29, 29, 29)
184  .addComponent(exportCsvButton)
185  .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
186  );
187  jHdrPanelLayout.setVerticalGroup(
188  jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
189  .addGroup(jHdrPanelLayout.createSequentialGroup()
190  .addContainerGap()
191  .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
192  .addComponent(exportCsvButton)
193  .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
194  .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
195  .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
196  .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
197  .addComponent(jLabel1)
198  .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
199  .addComponent(jLabel2)
200  .addComponent(currPageLabel)
201  .addComponent(jLabel3)
202  .addComponent(numPagesLabel)))
203  .addContainerGap())
204  );
205 
206  jTableDataPanel.setLayout(new java.awt.BorderLayout());
207 
208  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
209  this.setLayout(layout);
210  layout.setHorizontalGroup(
211  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
212  .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
213  .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
214  );
215  layout.setVerticalGroup(
216  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
217  .addGroup(layout.createSequentialGroup()
218  .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
219  .addGap(0, 0, 0)
220  .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
221  );
222  }// </editor-fold>//GEN-END:initComponents
223 
224  private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
225  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
226  currPage++;
227  if (currPage * ROWS_PER_PAGE > numRows) {
228  nextPageButton.setEnabled(false);
229  }
230  currPageLabel.setText(Integer.toString(currPage));
231  prevPageButton.setEnabled(true);
232 
233  // read and display a page of rows
234  String tableName = (String) this.tablesDropdownList.getSelectedItem();
235  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
236  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
237  }//GEN-LAST:event_nextPageButtonActionPerformed
238 
239  private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
240 
241  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
242  currPage--;
243  if (currPage == 1) {
244  prevPageButton.setEnabled(false);
245  }
246  currPageLabel.setText(Integer.toString(currPage));
247  nextPageButton.setEnabled(true);
248 
249  // read and display a page of rows
250  String tableName = (String) this.tablesDropdownList.getSelectedItem();
251  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
252  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
253  }//GEN-LAST:event_prevPageButtonActionPerformed
254 
255  private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed
256  JComboBox<?> cb = (JComboBox<?>) evt.getSource();
257  String tableName = (String) cb.getSelectedItem();
258  if (null == tableName) {
259  return;
260  }
261  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
262  selectTable(tableName);
263  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
264  }//GEN-LAST:event_tablesDropdownListActionPerformed
265 
273  @NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
274  "SQLiteViewer.csvExport.title=Export to csv file",
275  "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
276  private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed
277  Case openCase = Case.getCurrentCase();
278  File caseDirectory = new File(openCase.getExportDirectory());
279  JFileChooser fileChooser = new JFileChooser();
280  fileChooser.setDragEnabled(false);
281  fileChooser.setCurrentDirectory(caseDirectory);
282  //Set a filter to let the filechooser only work for csv files
283  FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("*.csv", "csv");
284  fileChooser.addChoosableFileFilter(csvFilter);
285  fileChooser.setAcceptAllFileFilterUsed(true);
286  fileChooser.setFileFilter(csvFilter);
287  fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
288  String defaultFileName = (String) this.tablesDropdownList.getSelectedItem();
289  fileChooser.setSelectedFile(new File(defaultFileName));
290  int choice = fileChooser.showSaveDialog((Component) evt.getSource()); //TODO
291  if (JFileChooser.APPROVE_OPTION == choice) {
292  File file = fileChooser.getSelectedFile();
293  if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) {
294  if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this,
295  Bundle.SQLiteViewer_csvExport_confirm_msg(),
296  Bundle.SQLiteViewer_csvExport_title(),
297  JOptionPane.YES_NO_OPTION)) {
298  } else {
299  return;
300  }
301  }
302 
303  exportTableToCsv(file);
304  }
305  }//GEN-LAST:event_exportCsvButtonActionPerformed
306 
307  // Variables declaration - do not modify//GEN-BEGIN:variables
308  private javax.swing.JLabel currPageLabel;
309  private javax.swing.JButton exportCsvButton;
310  private javax.swing.JPanel jHdrPanel;
311  private javax.swing.JLabel jLabel1;
312  private javax.swing.JLabel jLabel2;
313  private javax.swing.JLabel jLabel3;
314  private javax.swing.JPanel jTableDataPanel;
315  private javax.swing.JButton nextPageButton;
316  private javax.swing.JTextField numEntriesField;
317  private javax.swing.JLabel numPagesLabel;
318  private javax.swing.JButton prevPageButton;
319  private javax.swing.JComboBox<String> tablesDropdownList;
320  // End of variables declaration//GEN-END:variables
321 
322  @Override
323  public List<String> getSupportedMIMETypes() {
324  return Arrays.asList(SUPPORTED_MIMETYPES);
325  }
326 
327  @Override
328  public void setFile(AbstractFile file) {
329  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
330  sqliteDbFile = file;
331  initReader();
332  processSQLiteFile();
333  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
334  }
335 
336  @Override
337  public Component getComponent() {
338  return this;
339  }
340 
341  @Override
342  public void resetComponent() {
343  tablesDropdownList.setEnabled(true);
344  tablesDropdownList.removeAllItems();
345  numEntriesField.setText("");
346 
347  try {
348  viewReader.close();
349  } catch (SQLiteTableReaderException ex) {
350  //Could not successfully close the reader, nothing we can do to recover.
351  }
352  row = new LinkedHashMap<>();
353  pageOfTableRows = new ArrayList<>();
354  currentTableHeader = new ArrayList<>();
355  viewReader = null;
356  sqliteDbFile = null;
357  }
358 
362  @NbBundle.Messages({
363  "SQLiteViewer.comboBox.noTableEntry=No tables found",
364  "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.",
365  "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.",
366  "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.",
367  "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
368  "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
369  "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
370  private void processSQLiteFile() {
371  try {
372  tablesDropdownList.removeAllItems();
373 
374  Collection<String> dbTablesMap = viewReader.getTableNames();
375  if (dbTablesMap.isEmpty()) {
376  tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
377  tablesDropdownList.setEnabled(false);
378  } else {
379  dbTablesMap.forEach((tableName) -> {
380  tablesDropdownList.addItem(tableName);
381  });
382  }
383  } catch (SQLiteTableReaderException ex) {
384  logger.log(Level.WARNING, String.format("Unable to get table names "
385  + "from sqlite file [%s] with id=[%d].", sqliteDbFile.getName(),
386  sqliteDbFile.getId(), ex.getMessage()));
387  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
388  }
389  }
390 
391  @NbBundle.Messages({"# {0} - tableName",
392  "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
393  })
394  private void selectTable(String tableName) {
395  try {
396  numRows = viewReader.getRowCount(tableName);
397  numEntriesField.setText(numRows + " entries");
398 
399  currPage = 1;
400  currPageLabel.setText(Integer.toString(currPage));
401  numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1));
402 
403  prevPageButton.setEnabled(false);
404 
405  if (numRows > 0) {
406  exportCsvButton.setEnabled(true);
407  nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE)));
408  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
409  } else {
410  exportCsvButton.setEnabled(false);
411  nextPageButton.setEnabled(false);
412 
413  currentTableHeader = new ArrayList<>();
414  viewReader.read(tableName);
415  Map<String, Object> columnRow = new LinkedHashMap<>();
416  for (int i = 0; i < currentTableHeader.size(); i++) {
417  columnRow.put(currentTableHeader.get(i), "");
418  }
419  selectedTableView.setupTable(Collections.singletonList(columnRow));
420  }
421  } catch (SQLiteTableReaderException ex) {
422  logger.log(Level.WARNING, String.format("Failed to load table %s " //NON-NLS
423  + "from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), //NON-NLS
424  sqliteDbFile.getId()), ex.getMessage());
425  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
426  }
427  }
428 
429  @NbBundle.Messages({"# {0} - tableName",
430  "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
431  private void readTable(String tableName, int startRow, int numRowsToRead) {
432  try {
433  //If the table name has changed, then clear our table header. SQLiteTableReader
434  //will also detect the table name has changed and begin reading it as if it
435  //were a brand new table.
436  if (!tableName.equals(prevTableName)) {
437  prevTableName = tableName;
438  }
439  currentTableHeader = new ArrayList<>();
440  viewReader.read(tableName, numRowsToRead, startRow - 1);
441  selectedTableView.setupTable(pageOfTableRows);
442  pageOfTableRows = new ArrayList<>();
443  } catch (SQLiteTableReaderException ex) {
444  logger.log(Level.WARNING, String.format("Failed to read table %s from DB file '%s' " //NON-NLS
445  + "(objId=%d) starting at row [%d] and limit [%d]", //NON-NLS
446  tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
447  startRow - 1, numRowsToRead), ex.getMessage());
448  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
449  }
450  }
451 
458  private void initReader() {
459  viewReader = new SQLiteTableReader.Builder(sqliteDbFile)
460  .forAllColumnNames((columnName) -> {
461  currentTableHeader.add(columnName);
462  })
463  .forAllTableValues(getForAllStrategy()).build();
464  }
465 
478  private Consumer<Object> getForAllStrategy() {
479  return new Consumer<Object>() {
480  private int rowIndex = 0;
481 
482  @Override
483  public void accept(Object t) {
484  rowIndex++;
485  String objectStr = (t instanceof byte[]) ? "BLOB Data not shown"
486  : Objects.toString(t, "");
487 
488  row.put(currentTableHeader.get(rowIndex - 1), objectStr);
489 
490  //If we have built up a full database row, then add it to our page
491  //of rows to be displayed in the UI.
492  if (rowIndex == currentTableHeader.size()) {
493  pageOfTableRows.add(row);
494  row = new LinkedHashMap<>();
495  }
496  rowIndex %= currentTableHeader.size();
497  }
498 
499  };
500  }
501 
502  private int totalColumnCount;
503 
504  @NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
505  "SQLiteViewer.exportTableToCsv.FileName=File name: ",
506  "SQLiteViewer.exportTableToCsv.TableName=Table name: "
507  })
508  private void exportTableToCsv(File file) {
509  File csvFile = new File(file.toString() + ".csv");
510  String tableName = (String) this.tablesDropdownList.getSelectedItem();
511  try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
512  try (SQLiteTableReader sqliteStream = new SQLiteTableReader.Builder(sqliteDbFile)
513  .forAllColumnNames(getColumnNameCSVStrategy(out))
514  .forAllTableValues(getForAllCSVStrategy(out)).build()) {
515  totalColumnCount = sqliteStream.getColumnCount(tableName);
516  sqliteStream.read(tableName);
517  }
518  } catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
519  logger.log(Level.WARNING, String.format("Failed to export table [%s]"
520  + " to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
521  sqliteDbFile.getId()), ex.getMessage()); //NON-NLS
522  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText());
523  }
524  }
525 
540  private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
541  return new Consumer<String>() {
542  private int columnIndex = 0;
543 
544  @Override
545  public void accept(String columnName) {
546  columnIndex++;
547  String csvString = columnName;
548  //Format the value to adhere to the format of a CSV file
549  if (columnIndex == 1) {
550  csvString = "\"" + csvString + "\"";
551  } else {
552  csvString = ",\"" + csvString + "\"";
553  }
554  if (columnIndex == totalColumnCount) {
555  csvString += "\n";
556  }
557 
558  try {
559  out.write(csvString.getBytes());
560  } catch (IOException ex) {
561  /*
562  * If we can no longer write to the output stream, toss a
563  * runtime exception to get out of iteration. We explicitly
564  * catch this in exportTableToCsv() above.
565  */
566  throw new RuntimeException(ex);
567  }
568  }
569  };
570  }
571 
586  private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
587  return new Consumer<Object>() {
588  private int rowIndex = 0;
589 
590  @Override
591  public void accept(Object tableValue) {
592  rowIndex++;
593  //Substitute string representation of blob with placeholder text.
594  //Automatically wrap the value in quotes in case it contains commas.
595  String objectStr = (tableValue instanceof byte[])
596  ? "BLOB Data not shown" : Objects.toString(tableValue, "");
597  objectStr = "\"" + objectStr + "\"";
598 
599  if (rowIndex > 1) {
600  objectStr = "," + objectStr;
601  }
602  if (rowIndex == totalColumnCount) {
603  objectStr += "\n";
604  }
605 
606  try {
607  out.write(objectStr.getBytes());
608  } catch (IOException ex) {
609  /*
610  * If we can no longer write to the output stream, toss a
611  * runtime exception to get out of iteration. We explicitly
612  * catch this in exportTableToCsv() above.
613  */
614  throw new RuntimeException(ex);
615  }
616  rowIndex %= totalColumnCount;
617  }
618  };
619  }
620 
621  @Override
622  public boolean isSupported(AbstractFile file) {
623  return true;
624  }
625 }

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.