19 package org.sleuthkit.autopsy.contentviewers;
 
   21 import java.awt.BorderLayout;
 
   22 import java.awt.Component;
 
   23 import java.awt.Cursor;
 
   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;
 
   34 import java.util.Objects;
 
   35 import java.util.concurrent.ExecutionException;
 
   36 import java.util.function.Consumer;
 
   37 import java.util.logging.Level;
 
   38 import javax.swing.JComboBox;
 
   39 import javax.swing.JFileChooser;
 
   40 import javax.swing.JOptionPane;
 
   41 import javax.swing.SwingWorker;
 
   42 import javax.swing.filechooser.FileNameExtensionFilter;
 
   43 import org.apache.commons.io.FilenameUtils;
 
   44 import org.openide.util.NbBundle;
 
   45 import org.openide.windows.WindowManager;
 
   57 @SuppressWarnings(
"PMD.SingularField") 
 
   58 class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
 
   60     private static final long serialVersionUID = 1L;
 
   61     public static final String[] SUPPORTED_MIMETYPES = 
new String[]{
"application/x-sqlite3"};
 
   62     private static final int ROWS_PER_PAGE = 100;
 
   63     private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
 
   64     private final SQLiteTableView selectedTableView = 
new SQLiteTableView();
 
   65     private AbstractFile sqliteDbFile;
 
   67     private SQLiteTableReader viewReader;
 
   69     private Map<String, Object> row = 
new LinkedHashMap<>();
 
   70     private List<Map<String, Object>> pageOfTableRows = 
new ArrayList<>();
 
   71     private List<String> currentTableHeader = 
new ArrayList<>();
 
   72     private String prevTableName;
 
   75     private int currPage = 0; 
 
   77     SwingWorker<?, ?> worker;
 
   79     private final JFileChooserFactory chooserHelper = 
new JFileChooserFactory();
 
   86         jTableDataPanel.add(selectedTableView, BorderLayout.CENTER);
 
   94     @SuppressWarnings(
"unchecked")
 
   96     private 
void initComponents() {
 
   98         jHdrPanel = 
new javax.swing.JPanel();
 
   99         tablesDropdownList = 
new javax.swing.JComboBox<>();
 
  100         jLabel1 = 
new javax.swing.JLabel();
 
  101         numEntriesField = 
new javax.swing.JTextField();
 
  102         jLabel2 = 
new javax.swing.JLabel();
 
  103         currPageLabel = 
new javax.swing.JLabel();
 
  104         jLabel3 = 
new javax.swing.JLabel();
 
  105         numPagesLabel = 
new javax.swing.JLabel();
 
  106         prevPageButton = 
new javax.swing.JButton();
 
  107         nextPageButton = 
new javax.swing.JButton();
 
  108         exportCsvButton = 
new javax.swing.JButton();
 
  109         jTableDataPanel = 
new javax.swing.JPanel();
 
  111         jHdrPanel.setPreferredSize(
new java.awt.Dimension(536, 40));
 
  113         tablesDropdownList.setModel(
new javax.swing.DefaultComboBoxModel<>(
new String[] { 
"Item 1", 
"Item 2", 
"Item 3", 
"Item 4" }));
 
  114         tablesDropdownList.addActionListener(
new java.awt.event.ActionListener() {
 
  115             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  116                 tablesDropdownListActionPerformed(evt);
 
  120         org.openide.awt.Mnemonics.setLocalizedText(jLabel1, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.jLabel1.text")); 
 
  122         numEntriesField.setEditable(
false);
 
  123         numEntriesField.setText(
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.numEntriesField.text")); 
 
  124         numEntriesField.setBorder(null);
 
  126         org.openide.awt.Mnemonics.setLocalizedText(jLabel2, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.jLabel2.text")); 
 
  128         org.openide.awt.Mnemonics.setLocalizedText(currPageLabel, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.currPageLabel.text")); 
 
  130         org.openide.awt.Mnemonics.setLocalizedText(jLabel3, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.jLabel3.text")); 
 
  132         org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.numPagesLabel.text")); 
 
  134         prevPageButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); 
 
  135         org.openide.awt.Mnemonics.setLocalizedText(prevPageButton, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.prevPageButton.text")); 
 
  136         prevPageButton.setBorderPainted(
false);
 
  137         prevPageButton.setContentAreaFilled(
false);
 
  138         prevPageButton.setDisabledSelectedIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); 
 
  139         prevPageButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
 
  140         prevPageButton.setPreferredSize(
new java.awt.Dimension(23, 23));
 
  141         prevPageButton.addActionListener(
new java.awt.event.ActionListener() {
 
  142             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  143                 prevPageButtonActionPerformed(evt);
 
  147         nextPageButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); 
 
  148         org.openide.awt.Mnemonics.setLocalizedText(nextPageButton, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.nextPageButton.text")); 
 
  149         nextPageButton.setBorderPainted(
false);
 
  150         nextPageButton.setContentAreaFilled(
false);
 
  151         nextPageButton.setDisabledSelectedIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); 
 
  152         nextPageButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
 
  153         nextPageButton.setPreferredSize(
new java.awt.Dimension(23, 23));
 
  154         nextPageButton.addActionListener(
new java.awt.event.ActionListener() {
 
  155             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  156                 nextPageButtonActionPerformed(evt);
 
  160         org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton, 
org.openide.util.NbBundle.getMessage(SQLiteViewer.class, 
"SQLiteViewer.exportCsvButton.text")); 
 
  161         exportCsvButton.addActionListener(
new java.awt.event.ActionListener() {
 
  162             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  163                 exportCsvButtonActionPerformed(evt);
 
  167         javax.swing.GroupLayout jHdrPanelLayout = 
new javax.swing.GroupLayout(jHdrPanel);
 
  168         jHdrPanel.setLayout(jHdrPanelLayout);
 
  169         jHdrPanelLayout.setHorizontalGroup(
 
  170             jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  171             .addGroup(jHdrPanelLayout.createSequentialGroup()
 
  173                 .addComponent(jLabel1)
 
  174                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  175                 .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  177                 .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  179                 .addComponent(jLabel2)
 
  180                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  181                 .addComponent(currPageLabel)
 
  182                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  183                 .addComponent(jLabel3)
 
  184                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  185                 .addComponent(numPagesLabel)
 
  187                 .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  189                 .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  191                 .addComponent(exportCsvButton)
 
  192                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
 
  194         jHdrPanelLayout.setVerticalGroup(
 
  195             jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  196             .addGroup(jHdrPanelLayout.createSequentialGroup()
 
  198                 .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  199                     .addComponent(exportCsvButton)
 
  200                     .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  201                     .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  202                     .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
 
  203                         .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  204                         .addComponent(jLabel1)
 
  205                         .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  206                         .addComponent(jLabel2)
 
  207                         .addComponent(currPageLabel)
 
  208                         .addComponent(jLabel3)
 
  209                         .addComponent(numPagesLabel)))
 
  213         jTableDataPanel.setLayout(
new java.awt.BorderLayout());
 
  215         javax.swing.GroupLayout layout = 
new javax.swing.GroupLayout(
this);
 
  216         this.setLayout(layout);
 
  217         layout.setHorizontalGroup(
 
  218             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  219             .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
 
  220             .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
 
  222         layout.setVerticalGroup(
 
  223             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  224             .addGroup(layout.createSequentialGroup()
 
  225                 .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  227                 .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
 
  231     private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  232         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  234         if (currPage * ROWS_PER_PAGE > numRows) {
 
  235             nextPageButton.setEnabled(
false);
 
  237         currPageLabel.setText(Integer.toString(currPage));
 
  238         prevPageButton.setEnabled(
true);
 
  241         String tableName = (String) this.tablesDropdownList.getSelectedItem();
 
  242         readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
 
  243         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  246     private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  248         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  251             prevPageButton.setEnabled(
false);
 
  253         currPageLabel.setText(Integer.toString(currPage));
 
  254         nextPageButton.setEnabled(
true);
 
  257         String tableName = (String) this.tablesDropdownList.getSelectedItem();
 
  258         readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
 
  259         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  262     private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {
 
  263         JComboBox<?> cb = (JComboBox<?>) evt.getSource();
 
  264         String tableName = (String) cb.getSelectedItem();
 
  265         if (null == tableName) {
 
  268         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  269         selectTable(tableName);
 
  270         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  280     @NbBundle.Messages({
"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
 
  281         "SQLiteViewer.csvExport.title=Export to csv file",
 
  282         "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
 
  283     private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  284         Case openCase = Case.getCurrentCase();
 
  285         File caseDirectory = 
new File(openCase.getExportDirectory());
 
  286         JFileChooser fileChooser = chooserHelper.getChooser();
 
  287         fileChooser.setDragEnabled(
false);
 
  288         fileChooser.setCurrentDirectory(caseDirectory);
 
  290         FileNameExtensionFilter csvFilter = 
new FileNameExtensionFilter(
"*.csv", 
"csv");
 
  291         fileChooser.addChoosableFileFilter(csvFilter);
 
  292         fileChooser.setAcceptAllFileFilterUsed(
true);
 
  293         fileChooser.setFileFilter(csvFilter);
 
  294         fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
 
  295         String defaultFileName = (String) this.tablesDropdownList.getSelectedItem();
 
  296         fileChooser.setSelectedFile(
new File(defaultFileName));
 
  297         int choice = fileChooser.showSaveDialog((Component) evt.getSource()); 
 
  298         if (JFileChooser.APPROVE_OPTION == choice) {
 
  299             File file = fileChooser.getSelectedFile();
 
  300             if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase(
"csv")) {
 
  301                 if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
this,
 
  302                         Bundle.SQLiteViewer_csvExport_confirm_msg(),
 
  303                         Bundle.SQLiteViewer_csvExport_title(),
 
  304                         JOptionPane.YES_NO_OPTION)) {
 
  310             exportTableToCsv(file);
 
  315     private javax.swing.JLabel currPageLabel;
 
  316     private javax.swing.JButton exportCsvButton;
 
  317     private javax.swing.JPanel jHdrPanel;
 
  318     private javax.swing.JLabel jLabel1;
 
  319     private javax.swing.JLabel jLabel2;
 
  320     private javax.swing.JLabel jLabel3;
 
  321     private javax.swing.JPanel jTableDataPanel;
 
  322     private javax.swing.JButton nextPageButton;
 
  323     private javax.swing.JTextField numEntriesField;
 
  324     private javax.swing.JLabel numPagesLabel;
 
  325     private javax.swing.JButton prevPageButton;
 
  326     private javax.swing.JComboBox<String> tablesDropdownList;
 
  330     public List<String> getSupportedMIMETypes() {
 
  331         return Arrays.asList(SUPPORTED_MIMETYPES);
 
  335     public void setFile(AbstractFile file) {
 
  336         if (worker != null) {
 
  346         processSQLiteFile(file);
 
  350     public Component getComponent() {
 
  355     public void resetComponent() {
 
  356         tablesDropdownList.setEnabled(
true);
 
  357         tablesDropdownList.removeAllItems();
 
  358         numEntriesField.setText(
"");
 
  360         if(viewReader != null) {
 
  363             } 
catch (SQLiteTableReaderException ex) {
 
  367         row = 
new LinkedHashMap<>();
 
  368         pageOfTableRows = 
new ArrayList<>();
 
  369         currentTableHeader = 
new ArrayList<>();
 
  378         "SQLiteViewer.comboBox.noTableEntry=No tables found",
 
  379         "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.",
 
  380         "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.",
 
  381         "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.",
 
  382         "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
 
  383         "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
 
  384         "# {0} - exception message", 
"SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
 
  385     private void processSQLiteFile(
final AbstractFile file) {
 
  387         WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  388         worker = 
new SQLiteViewerWorker(file) {
 
  395                 WorkerResults results;
 
  399                     viewReader = results.getReader();
 
  400                     tablesDropdownList.removeAllItems();
 
  401                     Collection<String> dbTablesMap = results.getDbTablesMap();
 
  402                     if (dbTablesMap.isEmpty()) {
 
  403                         tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
 
  404                         tablesDropdownList.setEnabled(
false);
 
  406                         dbTablesMap.forEach((tableName) -> {
 
  407                             tablesDropdownList.addItem(tableName);
 
  411                     WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  412                 } 
catch (InterruptedException | ExecutionException ex) {
 
  413                     logger.log(Level.SEVERE, String.format(
"Failed to display SQL Viewer for file (%d)", file.getId()), ex);
 
  421     @NbBundle.Messages({
"# {0} - tableName",
 
  422         "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}" 
  424     private void selectTable(String tableName) {
 
  426             numRows = viewReader.getRowCount(tableName);
 
  427             numEntriesField.setText(numRows + 
" entries");
 
  430             currPageLabel.setText(Integer.toString(currPage));
 
  431             numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1));
 
  433             prevPageButton.setEnabled(
false);
 
  436                 exportCsvButton.setEnabled(
true);
 
  437                 nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE)));
 
  438                 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
 
  440                 exportCsvButton.setEnabled(
false);
 
  441                 nextPageButton.setEnabled(
false);
 
  443                 currentTableHeader = 
new ArrayList<>();
 
  444                 viewReader.read(tableName);
 
  445                 Map<String, Object> columnRow = 
new LinkedHashMap<>();
 
  446                 for (
int i = 0; i < currentTableHeader.size(); i++) {
 
  447                     columnRow.put(currentTableHeader.get(i), 
"");
 
  449                 selectedTableView.setupTable(Collections.singletonList(columnRow));
 
  451         } 
catch (SQLiteTableReaderException ex) {
 
  452             logger.log(Level.WARNING, String.format(
"Failed to load table %s "  
  453                     + 
"from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), 
 
  454                     sqliteDbFile.getId()), ex.getMessage());
 
  455             MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
 
  459     @NbBundle.Messages({
"# {0} - tableName",
 
  460         "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
 
  461     private void readTable(String tableName, 
int startRow, 
int numRowsToRead) {
 
  466             if (!tableName.equals(prevTableName)) {
 
  467                 prevTableName = tableName;
 
  469             currentTableHeader = 
new ArrayList<>();
 
  470             viewReader.read(tableName, numRowsToRead, startRow - 1);
 
  471             selectedTableView.setupTable(pageOfTableRows);
 
  472             pageOfTableRows = 
new ArrayList<>();
 
  473         } 
catch (SQLiteTableReaderException ex) {
 
  474             logger.log(Level.WARNING, String.format(
"Failed to read table %s from DB file '%s' "  
  475                     + 
"(objId=%d) starting at row [%d] and limit [%d]", 
 
  476                     tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
 
  477                     startRow - 1, numRowsToRead), ex.getMessage());
 
  478             MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
 
  488     private SQLiteTableReader initReader(AbstractFile sqliteFile) {
 
  489         return new SQLiteTableReader.Builder(sqliteFile)
 
  490                 .forAllColumnNames((columnName) -> {
 
  491                     currentTableHeader.add(columnName);
 
  493                 .forAllTableValues(getForAllStrategy()).build();
 
  508     private Consumer<Object> getForAllStrategy() {
 
  509         return new Consumer<Object>() {
 
  510             private int rowIndex = 0;
 
  513             public void accept(Object t) {
 
  515                 String objectStr = (t instanceof byte[]) ? 
"BLOB Data not shown" 
  516                         : Objects.toString(t, 
"");
 
  518                 row.put(currentTableHeader.get(rowIndex - 1), objectStr);
 
  522                 if (rowIndex == currentTableHeader.size()) {
 
  523                     pageOfTableRows.add(row);
 
  524                     row = 
new LinkedHashMap<>();
 
  526                 rowIndex %= currentTableHeader.size();
 
  532     private int totalColumnCount;
 
  534     @NbBundle.Messages({
"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
 
  535         "SQLiteViewer.exportTableToCsv.FileName=File name: ",
 
  536         "SQLiteViewer.exportTableToCsv.TableName=Table name: " 
  538     private void exportTableToCsv(File file) {
 
  539         final File csvFile = 
new File(file.toString() + 
".csv");
 
  540         final String tableName = (String) this.tablesDropdownList.getSelectedItem();
 
  542         SwingWorker<String, Void> csvWorker = 
new SwingWorker<String, Void>() {
 
  544             protected String doInBackground() throws Exception {
 
  545                 try (FileOutputStream out = 
new FileOutputStream(csvFile, 
false)) {
 
  546                     try (SQLiteTableReader sqliteStream = 
new SQLiteTableReader.Builder(sqliteDbFile)
 
  547                             .forAllColumnNames(getColumnNameCSVStrategy(out))
 
  548                             .forAllTableValues(getForAllCSVStrategy(out)).build()) {
 
  549                         totalColumnCount = sqliteStream.getColumnCount(tableName);
 
  550                         sqliteStream.read(tableName);
 
  552                 } 
catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
 
  553                     logger.log(Level.WARNING, String.format(
"Failed to export table [%s]" 
  554                             + 
" to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
 
  555                             sqliteDbFile.getId()), ex.getMessage()); 
 
  557                     return Bundle.SQLiteViewer_exportTableToCsv_write_errText();
 
  565                     String message = 
get();
 
  566                     if (!message.isEmpty()) {
 
  567                         MessageNotifyUtil.Message.error(message);
 
  569                 } 
catch (InterruptedException | ExecutionException ex) {
 
  570                     logger.log(Level.SEVERE, 
"Failure occurred writing sql csv file.", ex);
 
  593     private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
 
  594         return new Consumer<String>() {
 
  595             private int columnIndex = 0;
 
  598             public void accept(String columnName) {
 
  600                 String csvString = columnName;
 
  602                 if (columnIndex == 1) {
 
  603                     csvString = 
"\"" + csvString + 
"\"";
 
  605                     csvString = 
",\"" + csvString + 
"\"";
 
  607                 if (columnIndex == totalColumnCount) {
 
  612                     out.write(csvString.getBytes());
 
  613                 } 
catch (IOException ex) {
 
  619                     throw new RuntimeException(ex);
 
  639     private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
 
  640         return new Consumer<Object>() {
 
  641             private int rowIndex = 0;
 
  644             public void accept(Object tableValue) {
 
  648                 String objectStr = (tableValue instanceof byte[])
 
  649                         ? 
"BLOB Data not shown" : Objects.toString(tableValue, 
"");
 
  650                 objectStr = 
"\"" + objectStr + 
"\"";
 
  653                     objectStr = 
"," + objectStr;
 
  655                 if (rowIndex == totalColumnCount) {
 
  660                     out.write(objectStr.getBytes());
 
  661                 } 
catch (IOException ex) {
 
  667                     throw new RuntimeException(ex);
 
  669                 rowIndex %= totalColumnCount;
 
  675     public boolean isSupported(AbstractFile file) {
 
  710             this.reader = reader;
 
  711             this.dbTablesMap = dbTablesMap;
 
  718         Collection<String> getDbTablesMap() {
 
List< String > getTableNames()
 
final Collection< String > dbTablesMap
 
final SQLiteTableReader reader
 
WorkerResults doInBackground()