Package org.sleuthkit.autopsy.report

Source Code of org.sleuthkit.autopsy.report.ReportGenerator

/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.report;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.EscapeUtil;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;

/**
* Instances of this class use GeneralReportModules, TableReportModules and
* FileReportModules to generate a report. If desired, displayProgressPanels()
* can be called to show report generation progress using ReportProgressPanel
* objects displayed using a dialog box.
*/
class ReportGenerator {
    private static final Logger logger = Logger.getLogger(ReportGenerator.class.getName());
   
    private Case currentCase = Case.getCurrentCase();
    private SleuthkitCase skCase = currentCase.getSleuthkitCase();
   
    private Map<TableReportModule, ReportProgressPanel> tableProgress;
    private Map<GeneralReportModule, ReportProgressPanel> generalProgress;
    private Map<FileReportModule, ReportProgressPanel> fileProgress;
   
    private String reportPath;
    private ReportGenerationPanel panel = new ReportGenerationPanel();
   
    static final String REPORTS_DIR = "Reports"; //NON-NLS
       
    ReportGenerator(Map<TableReportModule, Boolean> tableModuleStates, Map<GeneralReportModule, Boolean> generalModuleStates, Map<FileReportModule, Boolean> fileListModuleStates) {
        // Create the root reports directory path of the form: <CASE DIRECTORY>/Reports/<Case fileName> <Timestamp>/
        DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss");
        Date date = new Date();
        String dateNoTime = dateFormat.format(date);
        this.reportPath = currentCase.getCaseDirectory() + File.separator + REPORTS_DIR + File.separator + currentCase.getName() + " " + dateNoTime + File.separator;
       
        // Create the root reports directory.
        try {
            FileUtil.createFolder(new File(this.reportPath));
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS
        }
       
        // Initialize the progress panels
        generalProgress = new HashMap<>();
        tableProgress = new HashMap<>();
        fileProgress = new HashMap<>();
        setupProgressPanels(tableModuleStates, generalModuleStates, fileListModuleStates);
    }
   
    /**
     * Create a ReportProgressPanel for each report generation module selected by the user.
     *
     * @param tableModuleStates The enabled/disabled state of each TableReportModule
     * @param generalModuleStates The enabled/disabled state of each GeneralReportModule
     * @param fileListModuleStates The enabled/disabled state of each FileReportModule
     */
    private void setupProgressPanels(Map<TableReportModule, Boolean> tableModuleStates, Map<GeneralReportModule, Boolean> generalModuleStates, Map<FileReportModule, Boolean> fileListModuleStates) {
        if (null != tableModuleStates) {
            for (Entry<TableReportModule, Boolean> entry : tableModuleStates.entrySet()) {
                if (entry.getValue()) {
                    TableReportModule module = entry.getKey();
                    String reportFilePath = module.getRelativeFilePath();
                    if (reportFilePath != null) {
                        tableProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath));
                    }
                    else {
                        tableProgress.put(module, panel.addReport(module.getName(), null));                       
                    }
                }
            }
        }
       
        if (null != generalModuleStates) {
            for (Entry<GeneralReportModule, Boolean> entry : generalModuleStates.entrySet()) {
                if (entry.getValue()) {
                    GeneralReportModule module = entry.getKey();
                    String reportFilePath = module.getRelativeFilePath();
                    if (reportFilePath != null) {
                        generalProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath));
                    }
                    else {
                        generalProgress.put(module, panel.addReport(module.getName(), null));                       
                    }
                }
            }
        }
       
        if (null != fileListModuleStates) {
            for(Entry<FileReportModule, Boolean> entry : fileListModuleStates.entrySet()) {
                if (entry.getValue()) {
                    FileReportModule module = entry.getKey();
                    String reportFilePath = module.getRelativeFilePath();
                    if (reportFilePath != null) {
                        fileProgress.put(module, panel.addReport(module.getName(), reportPath + reportFilePath));
                    }
                    else {
                        fileProgress.put(module, panel.addReport(module.getName(), null));                       
                    }
                }
            }
        }
    }
   
    /**
     * Display the progress panels to the user, and add actions to close the parent dialog.
     */
    public void displayProgressPanels() {
        final JDialog dialog = new JDialog(new JFrame(), true);
        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
        dialog.setTitle(NbBundle.getMessage(this.getClass(), "ReportGenerator.displayProgress.title.text"));
        dialog.add(this.panel);
        dialog.pack();
       
        panel.addCloseAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dialog.dispose();
            }
        });
       
        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                panel.close();
            }
        });
       
        Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
        int w = dialog.getSize().width;
        int h = dialog.getSize().height;

        // set the location of the popUp Window on the center of the screen
        dialog.setLocation((screenDimension.width - w) / 2, (screenDimension.height - h) / 2);
        dialog.setVisible(true);
    }
   
    /**
     * Run the GeneralReportModules using a SwingWorker.
     */
    public void generateGeneralReports() {
        GeneralReportsWorker worker = new GeneralReportsWorker();
        worker.execute();
    }
   
    /**
     * Run the TableReportModules using a SwingWorker.
     *
     * @param artifactTypeSelections the enabled/disabled state of the artifact types to be included in the report
     * @param tagSelections the enabled/disabled state of the tag names to be included in the report
     */
    public void generateTableReports(Map<ARTIFACT_TYPE, Boolean> artifactTypeSelections, Map<String, Boolean> tagNameSelections) {
        if (!tableProgress.isEmpty() && null != artifactTypeSelections) {
            TableReportsWorker worker = new TableReportsWorker(artifactTypeSelections, tagNameSelections);
            worker.execute();
        }
    }
   
    /**
     * Run the FileReportModules using a SwingWorker.
     *
     * @param enabledInfo the Information that should be included about each file
     * in the report.
     */
    public void generateFileListReports(Map<FileReportDataTypes, Boolean> enabledInfo) {
        if (!fileProgress.isEmpty() && null != enabledInfo) {
            List<FileReportDataTypes> enabled = new ArrayList<>();
            for (Entry<FileReportDataTypes, Boolean> e : enabledInfo.entrySet()) {
                if(e.getValue()) {
                    enabled.add(e.getKey());
                }
            }
            FileReportsWorker worker = new FileReportsWorker(enabled);
            worker.execute();
        }
    }
   
    /**
     * SwingWorker to run GeneralReportModules.
     */
    private class GeneralReportsWorker extends SwingWorker<Integer, Integer> {

        @Override
        protected Integer doInBackground() throws Exception {
            for (Entry<GeneralReportModule, ReportProgressPanel> entry : generalProgress.entrySet()) {
                GeneralReportModule module = entry.getKey();
                if (generalProgress.get(module).getStatus() != ReportStatus.CANCELED) {
                    module.generateReport(reportPath, generalProgress.get(module));
                }
            }
            return 0;
        }
       
        @Override
        protected void done() {
            try {
                get();
            } catch (InterruptedException | ExecutionException ex) {
                MessageNotifyUtil.Notify.show(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(),
                        MessageNotifyUtil.MessageType.ERROR);
                logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS
            }
            // catch and ignore if we were cancelled
            catch (java.util.concurrent.CancellationException ex ) { }
        }
       
    }
   
    /**
     * SwingWorker to run FileReportModules.
     */
    private class FileReportsWorker extends SwingWorker<Integer, Integer> {
        private List<FileReportDataTypes> enabledInfo = Arrays.asList(FileReportDataTypes.values());
        private List<FileReportModule> fileModules = new ArrayList<>();
       
        FileReportsWorker(List<FileReportDataTypes> enabled) {
            enabledInfo = enabled;
            for (Entry<FileReportModule, ReportProgressPanel> entry : fileProgress.entrySet()) {
                fileModules.add(entry.getKey());
            }
        }
       
        @Override
        protected Integer doInBackground() throws Exception {
            for (FileReportModule module : fileModules) {
                ReportProgressPanel progress = fileProgress.get(module);
                if (progress.getStatus() != ReportStatus.CANCELED) {
                    progress.start();
                    progress.updateStatusLabel(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.queryingDb.text"));
                }
            }
           
            List<AbstractFile> files = getFiles();
            int numFiles = files.size();
            for (FileReportModule module : fileModules) {
                module.startReport(reportPath);
                module.startTable(enabledInfo);
                fileProgress.get(module).setIndeterminate(false);
                fileProgress.get(module).setMaximumProgress(numFiles);
            }
           
            int i = 0;
            // Add files to report.
            for (AbstractFile file : files) {
                // Check to see if any reports have been cancelled.
                if (fileModules.isEmpty()) {
                    break;
                }
                // Remove cancelled reports, add files to report otherwise.
                Iterator<FileReportModule> iter = fileModules.iterator();
                while (iter.hasNext()) {
                    FileReportModule module = iter.next();
                    ReportProgressPanel progress = fileProgress.get(module);
                    if (progress.getStatus() == ReportStatus.CANCELED) {
                        iter.remove();
                    } else {
                        module.addRow(file, enabledInfo);
                        progress.increment();
                    }
                   
                    if ((i % 100) == 0) {
                        progress.updateStatusLabel(
                                NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingFile.text",
                                                    file.getName()));
                    }
                }
                i++;
            }
           
            for (FileReportModule module : fileModules) {
                module.endTable();
                module.endReport();
                fileProgress.get(module).complete();
            }
           
            return 0;
        }
       
        /**
         * Get all files in the image.
         * @return
         */
        private List<AbstractFile> getFiles() {
            List<AbstractFile> absFiles;
            try {
                SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
                absFiles = skCase.findAllFilesWhere("NOT meta_type = 2"); //NON-NLS
                return absFiles;
            } catch (TskCoreException ex) {
                // TODO
                return Collections.<AbstractFile>emptyList();
            }
        }
       
        @Override
        protected void done() {
            try {
                get();
            } catch (InterruptedException | ExecutionException ex) {
                MessageNotifyUtil.Notify.show(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(),
                        MessageNotifyUtil.MessageType.ERROR);
                logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS
            }
            // catch and ignore if we were cancelled
            catch (java.util.concurrent.CancellationException ex ) { }
        }
    }
   
    /**
     * SwingWorker to run TableReportModules to report on blackboard artifacts,
     * content tags, and blackboard artifact tags.
     */
    private class TableReportsWorker extends SwingWorker<Integer, Integer> {
        private List<TableReportModule> tableModules  = new ArrayList<>();
        private List<ARTIFACT_TYPE> artifactTypes  = new ArrayList<>();
        private HashSet<String> tagNamesFilter = new HashSet<>();
       
        private List<Content> images = new ArrayList<>();
       
        TableReportsWorker(Map<ARTIFACT_TYPE, Boolean> artifactTypeSelections, Map<String, Boolean> tagNameSelections) {
            // Get the report modules selected by the user.
            for (Entry<TableReportModule, ReportProgressPanel> entry : tableProgress.entrySet()) {
                tableModules.add(entry.getKey());
            }
           
            // Get the artifact types selected by the user.
            for (Entry<ARTIFACT_TYPE, Boolean> entry : artifactTypeSelections.entrySet()) {
                if (entry.getValue()) {
                    artifactTypes.add(entry.getKey());
                }
            }
           
            // Get the tag names selected by the user and make a tag names filter.
            if (null != tagNameSelections) {
                for (Entry<String, Boolean> entry : tagNameSelections.entrySet()) {
                    if (entry.getValue() == true) {
                        tagNamesFilter.add(entry.getKey());
                    }
                }
            }
        }

        @Override
        protected Integer doInBackground() throws Exception {
            // Start the progress indicators for each active TableReportModule.
            for (TableReportModule module : tableModules) {
                ReportProgressPanel progress = tableProgress.get(module);
                if (progress.getStatus() != ReportStatus.CANCELED) {
                    module.startReport(reportPath);
                    progress.start();
                    progress.setIndeterminate(false);
                    progress.setMaximumProgress(ARTIFACT_TYPE.values().length + 2); // +2 for content and blackboard artifact tags
                }
            }
                     
            // report on the blackboard results
            makeBlackboardArtifactTables();

            // report on the tagged files and artifacts
            makeContentTagsTables();
            makeBlackboardArtifactTagsTables();

            // report on the tagged images
            makeThumbnailTable();

            // finish progress, wrap up
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).complete();
                module.endReport();
            }
           
            return 0;
        }
       
        /**
         * Generate the tables for the selected blackboard artifacts
         */
        private void makeBlackboardArtifactTables() {
            // Make a comment string describing the tag names filter in effect.
            StringBuilder comment = new StringBuilder();
            if (!tagNamesFilter.isEmpty()) {
                comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text"));
                comment.append(makeCommaSeparatedList(tagNamesFilter));
            }           

            // Add a table to the report for every enabled blackboard artifact type.
            for (ARTIFACT_TYPE type : artifactTypes) {
                // Check for cancellaton.
                removeCancelledTableReportModules();
                if (tableModules.isEmpty()) {
                    return;
                }
                                                       
                for (TableReportModule module : tableModules) {
                    tableProgress.get(module).updateStatusLabel(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing",
                                                type.getDisplayName()));
                }
               
                // Keyword hits and hashset hit artifacts get sepcial handling.               
                if (type.equals(ARTIFACT_TYPE.TSK_KEYWORD_HIT)) {
                    writeKeywordHits(tableModules, comment.toString(), tagNamesFilter);
                    continue;
                } else if (type.equals(ARTIFACT_TYPE.TSK_HASHSET_HIT)) {
                    writeHashsetHits(tableModules, comment.toString(), tagNamesFilter);
                    continue;
                }

                List<ArtifactData> unsortedArtifacts = getFilteredArtifacts(type, tagNamesFilter);
               
                if (unsortedArtifacts.isEmpty()) {
                    continue;
                }

                // The most efficient way to sort all the Artifacts is to add them to a List, and then
                // sort that List based off a Comparator. Adding to a TreeMap/Set/List sorts the list
                // each time an element is added, which adds unnecessary overhead if we only need it sorted once.
                Collections.sort(unsortedArtifacts);

                // Get the column headers appropriate for the artifact type.
                /* @@@ BC: Seems like a better design here would be to have a method that
                 * takes in the artifact as an argument and returns the attributes. We then use that
                 * to make the headers and to make each row afterwards so that we don't have artifact-specific
                 * logic in both getArtifactTableCoumnHeaders and ArtifactData.getRow()
                 */
                List<String> columnHeaders = getArtifactTableColumnHeaders(type.getTypeID());
                if (columnHeaders == null) {
                    // @@@ Hack to prevent system from hanging.  Better solution is to merge all attributes into a single column or analyze the artifacts to find out how many are needed.
                    MessageNotifyUtil.Notify.show(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.msgShow.skippingArtType.title", type),
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.msgShow.skippingArtType.msg"),
                            MessageNotifyUtil.MessageType.ERROR);
                    continue;
                }
               
                for (TableReportModule module : tableModules) {
                    module.startDataType(type.getDisplayName(), comment.toString());                                           
                    module.startTable(columnHeaders);                   
                }
               
                boolean msgSent = false;   
                for(ArtifactData artifactData : unsortedArtifacts) {
                    // Add the row data to all of the reports.
                    for (TableReportModule module : tableModules) {
                       
                        // Get the row data for this type of artifact.
                        List<String> rowData = artifactData.getRow();
                        if (rowData.isEmpty()) {
                            if (msgSent == false) {
                                MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(),
                                                                                  "ReportGenerator.msgShow.skippingArtRow.title",
                                                                                  type),
                                                              NbBundle.getMessage(this.getClass(),
                                                                                  "ReportGenerator.msgShow.skippingArtRow.msg"),
                                                              MessageNotifyUtil.MessageType.ERROR);
                                msgSent = true;
                            }
                            continue;
                        }
                       
                        module.addRow(rowData);
                    }
                }
                // Finish up this data type
                for (TableReportModule module : tableModules) {
                    tableProgress.get(module).increment();
                    module.endTable();
                    module.endDataType();
                }
            }       
        }
       
        /**
         * Make table for tagged files
         */
        @SuppressWarnings("deprecation")
        private void makeContentTagsTables() {
            // Check for cancellaton.
            removeCancelledTableReportModules();
            if (tableModules.isEmpty()) {
                return;
            }
                       
            // Get the content tags.
            List<ContentTag> tags;
            try {
                tags = Case.getCurrentCase().getServices().getTagsManager().getAllContentTags();
            }
            catch (TskCoreException ex) {
                logger.log(Level.SEVERE, "failed to get content tags", ex); //NON-NLS
                return;
            }
                       
            // Tell the modules reporting on content tags is beginning.
            for (TableReportModule module : tableModules) {           
                // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink.
                // @@@ Alos Using the obsolete ARTIFACT_TYPE.TSK_TAG_FILE is also an expedient hack.
                tableProgress.get(module).updateStatusLabel(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing",
                                            ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName()));
                ArrayList<String> columnHeaders = new ArrayList<>(Arrays.asList(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.tag"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.file"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeModified"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeChanged"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeAccessed"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeCreated"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.size"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.hash")));
               
                StringBuilder comment = new StringBuilder();
                if (!tagNamesFilter.isEmpty()) {
                    comment.append(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.makeContTagTab.taggedFiles.msg"));
                    comment.append(makeCommaSeparatedList(tagNamesFilter));
                }           
                if (module instanceof ReportHTML) {
                    ReportHTML htmlReportModule = (ReportHTML)module;
                    htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString());                       
                    htmlReportModule.startContentTagsTable(columnHeaders);
                }
                else {
                    module.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString());                       
                    module.startTable(columnHeaders);
                }               
            }
                       
            // Give the modules the rows for the content tags.
            for (ContentTag tag : tags) {
                // skip tags that we are not reporting on
                if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) {
                    continue;
                }
               
                String fileName;
                try {
                    fileName = tag.getContent().getUniquePath();
                } catch (TskCoreException ex) {
                    fileName = tag.getContent().getName();
                }
               
                ArrayList<String> rowData = new ArrayList<>(Arrays.asList(tag.getName().getDisplayName(), fileName, tag.getComment()));
                for (TableReportModule module : tableModules) {                                                                                      
                    // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink.
                    if (module instanceof ReportHTML) {
                        ReportHTML htmlReportModule = (ReportHTML)module;
                        htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag);
                    }
                    else {     
                        module.addRow(rowData);
                    }                       
                }
               
                // see if it is for an image so that we later report on it
                checkIfTagHasImage(tag);
            }               
               
            // The the modules content tags reporting is ended.
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).increment();
                module.endTable();
                module.endDataType();
            }           
        }
       
        @Override
        protected void done() {
            try {
                get();
            } catch (InterruptedException | ExecutionException ex) {
                MessageNotifyUtil.Notify.show(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorTitle"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.errors.reportErrorText") + ex.getLocalizedMessage(),
                        MessageNotifyUtil.MessageType.ERROR);
                logger.log(Level.SEVERE, "failed to generate reports", ex); //NON-NLS
            }
            // catch and ignore if we were cancelled
            catch (java.util.concurrent.CancellationException ex ) { }
        }
       
        /**
         * Generate the tables for the tagged artifacts
         */
        @SuppressWarnings("deprecation")
        private void makeBlackboardArtifactTagsTables() {
            // Check for cancellaton.
            removeCancelledTableReportModules();
            if (tableModules.isEmpty()) {
                return;
            }
                       
            List<BlackboardArtifactTag> tags;
            try {
                tags = Case.getCurrentCase().getServices().getTagsManager().getAllBlackboardArtifactTags();
            }
            catch (TskCoreException ex) {
                logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); //NON-NLS
                return;
            }

            // Tell the modules reporting on blackboard artifact tags data type is beginning.
            // @@@ Using the obsolete ARTIFACT_TYPE.TSK_TAG_ARTIFACT is an expedient hack.
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).updateStatusLabel(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing",
                                            ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName()));
                StringBuilder comment = new StringBuilder();
                if (!tagNamesFilter.isEmpty()) {
                    comment.append(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.makeBbArtTagTab.taggedRes.msg"));
                    comment.append(makeCommaSeparatedList(tagNamesFilter));
                }                       
                module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString())
                module.startTable(new ArrayList<>(Arrays.asList(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.resultType"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.tag"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.comment"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile"))));
            }
                       
            // Give the modules the rows for the content tags.
            for (BlackboardArtifactTag tag : tags) {
                if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) {
                    continue;
                }
               
                List<String> row;
                for (TableReportModule module : tableModules) {
                    row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName()));
                    module.addRow(row);
                }
               
                // check if the tag is an image that we should later make a thumbnail for
                checkIfTagHasImage(tag);
            }               

            // The the modules blackboard artifact tags reporting is ended.
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).increment();
                module.endTable();
                module.endDataType();
            }           
        }    
       
        /**
         * Test if the user requested that this tag be reported on
         * @param tagName
         * @return true if it should be reported on
         */
        private boolean passesTagNamesFilter(String tagName) {
            return tagNamesFilter.isEmpty() || tagNamesFilter.contains(tagName);
        }
       
        void removeCancelledTableReportModules() {
            Iterator<TableReportModule> iter = tableModules.iterator();
            while (iter.hasNext()) {
                TableReportModule module = iter.next();
                if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) {
                    iter.remove();
                }
            }           
        }

        /**
         * Make a report for the files that were previously found to
         * be images.
         */
        private void makeThumbnailTable() {
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).updateStatusLabel(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.createdThumb.text"));
               
                if (module instanceof ReportHTML) {
                    ReportHTML htmlModule = (ReportHTML) module;
                    htmlModule.startDataType(
                            NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.name"),
                                             NbBundle.getMessage(this.getClass(), "ReportGenerator.thumbnailTable.desc"));
                    List<String> emptyHeaders = new ArrayList<>();
                    for (int i = 0; i < ReportHTML.THUMBNAIL_COLUMNS; i++) {
                        emptyHeaders.add("");
                    }
                    htmlModule.startTable(emptyHeaders);
                   
                    htmlModule.addThumbnailRows(images);
                   
                    htmlModule.endTable();
                    htmlModule.endDataType();
                }
            }
        }
       
        /**
         * Analyze artifact associated with tag and add to internal list if it is associated
         * with an image.  
         * @param artifactTag
         */
        private void checkIfTagHasImage(BlackboardArtifactTag artifactTag) {
            AbstractFile file;
            try {
                file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID());
            } catch (TskCoreException ex) {
                logger.log(Level.WARNING, "Error while getting content from a blackboard artifact to report on.", ex); //NON-NLS
                return;
            }
            checkIfFileIsImage(file);
        }
       
        /**
         * Analyze file that tag is associated with and determine if
         * it is an image and should have a thumbnail reported for it.
         * Images are added to internal list.
         * @param contentTag
         */
        private void checkIfTagHasImage(ContentTag contentTag) {
            Content c = contentTag.getContent();
            if (c instanceof AbstractFile == false) {
                return;
            }
            checkIfFileIsImage((AbstractFile) c);
        }
           
        /**
         * If file is an image file, add it to the internal 'images' list.
         * @param file
         */
        private void checkIfFileIsImage(AbstractFile file) {   
          
            if (file.isDir() ||
                file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS ||
                file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
                return;
            }
           
            if (ImageUtils.thumbnailSupported(file)) {
                images.add(file);
            }
        }
    }
       
    /// @@@ Should move the methods specific to TableReportsWorker into that scope.
    private Boolean failsTagFilter(HashSet<String> tagNames, HashSet<String> tagsNamesFilter)
    {
        if (null == tagsNamesFilter || tagsNamesFilter.isEmpty()) {
            return false;
        }

        HashSet<String> filteredTagNames = new HashSet<>(tagNames);
        filteredTagNames.retainAll(tagsNamesFilter);
        return filteredTagNames.isEmpty();
    }
   
    /**
     * Get a List of the artifacts and data of the given type that pass the given Tag Filter.
     *
     * @param type The artifact type to get
     * @param tagNamesFilter The tag names that should be included.
     * @return a list of the filtered tags.
     */
    private List<ArtifactData> getFilteredArtifacts(ARTIFACT_TYPE type, HashSet<String> tagNamesFilter) {
        List<ArtifactData> artifacts = new ArrayList<>();
        try {
             for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(type)) {
                 List<BlackboardArtifactTag> tags = Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact);
                 HashSet<String> uniqueTagNames = new HashSet<>();
                 for (BlackboardArtifactTag tag : tags) {
                     uniqueTagNames.add(tag.getName().getDisplayName());
                 }
                 if(failsTagFilter(uniqueTagNames, tagNamesFilter)) {
                     continue;
                 }
                 try {
                     artifacts.add(new ArtifactData(artifact, skCase.getBlackboardAttributes(artifact), uniqueTagNames));
                 } catch (TskCoreException ex) {
                     logger.log(Level.SEVERE, "Failed to get Blackboard Attributes when generating report.", ex); //NON-NLS
                 }
             }
         }
         catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Failed to get Blackboard Artifacts when generating report.", ex); //NON-NLS
         }
        return artifacts;
    }
           
    /**
     * Write the keyword hits to the provided TableReportModules.
     * @param tableModules modules to report on
     */
    @SuppressWarnings("deprecation")
    private void writeKeywordHits(List<TableReportModule> tableModules, String comment, HashSet<String> tagNamesFilter) {
        ResultSet listsRs = null;
        try {
            // Query for keyword lists-only so that we can tell modules what lists
            // will exist for their index.
            // @@@ There is a bug in here.  We should use the tags in the below code
            // so that we only report the lists that we will later provide with real
            // hits.  If no keyord hits are tagged, then we make the page for nothing.
            listsRs = skCase.runQuery("SELECT att.value_text AS list " + //NON-NLS
                                                "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + //NON-NLS
                                                "WHERE att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + //NON-NLS
                                                    "AND art.artifact_type_id = " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " " + //NON-NLS
                                                    "AND att.artifact_id = art.artifact_id " //NON-NLS
                                                "GROUP BY list"); //NON-NLS
            List<String> lists = new ArrayList<>();
            while(listsRs.next()) {
                String list = listsRs.getString("list"); //NON-NLS
                if(list.isEmpty()) {
                    list = NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs");
                }
                lists.add(list);
            }
           
            // Make keyword data type and give them set index
            for (TableReportModule module : tableModules) {
                module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment);
                module.addSetIndex(lists);
                tableProgress.get(module).updateStatusLabel(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing",
                                            ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName()));
            }
        }
        catch (SQLException ex) {
            logger.log(Level.SEVERE, "Failed to query keyword lists.", ex); //NON-NLS
        } finally {
            if (listsRs != null) {
                try {
                    skCase.closeRunQuery(listsRs);
                } catch (SQLException ex) {
                }
            }
        }
       
        ResultSet rs = null;
        try {
            // Query for keywords, grouped by list
            rs = skCase.runQuery("SELECT art.artifact_id, art.obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name " + //NON-NLS
                                           "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f " + //NON-NLS
                                           "WHERE (att1.artifact_id = art.artifact_id) " + //NON-NLS
                                                 "AND (att2.artifact_id = art.artifact_id) " //NON-NLS
                                                 "AND (att3.artifact_id = art.artifact_id) " //NON-NLS
                                                 "AND (f.obj_id = art.obj_id) " + //NON-NLS
                                                 "AND (att1.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + //NON-NLS
                                                 "AND (att2.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + //NON-NLS
                                                 "AND (att3.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + //NON-NLS
                                                 "AND (art.artifact_type_id = " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + //NON-NLS
                                           "ORDER BY list, keyword, name"); //NON-NLS
            String currentKeyword = "";
            String currentList = "";
            while (rs.next()) {
                // Check to see if all the TableReportModules have been canceled
                if (tableModules.isEmpty()) {
                    break;
                }
                Iterator<TableReportModule> iter = tableModules.iterator();
                while (iter.hasNext()) {
                    TableReportModule module = iter.next();
                    if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) {
                        iter.remove();
                    }
                }
                // Get any tags that associated with this artifact and apply the tag filter.
                HashSet<String> uniqueTagNames = getUniqueTagNames(rs.getLong("artifact_id")); //NON-NLS
                if(failsTagFilter(uniqueTagNames, tagNamesFilter)) {
                    continue;
                }                   
                String tagsList = makeCommaSeparatedList(uniqueTagNames);
                                                       
                Long objId = rs.getLong("obj_id"); //NON-NLS
                String keyword = rs.getString("keyword"); //NON-NLS
                String preview = rs.getString("preview"); //NON-NLS
                String list = rs.getString("list"); //NON-NLS
                String uniquePath = "";

                 try {
                    uniquePath = skCase.getAbstractFileById(objId).getUniquePath();
                } catch (TskCoreException ex) {
                    logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS
                }

                // If the lists aren't the same, we've started a new list
                if((!list.equals(currentList) && !list.isEmpty()) || (list.isEmpty() && !currentList.equals(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs")))) {
                    if(!currentList.isEmpty()) {
                        for (TableReportModule module : tableModules) {
                            module.endTable();
                            module.endSet();
                        }
                    }
                    currentList = list.isEmpty() ? NbBundle
                            .getMessage(this.getClass(), "ReportGenerator.writeKwHits.userSrchs") : list;
                    currentKeyword = ""; // reset the current keyword because it's a new list
                    for (TableReportModule module : tableModules) {
                        module.startSet(currentList);
                        tableProgress.get(module).updateStatusLabel(
                                NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList",
                                                    ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), currentList));
                    }
                }
                if (!keyword.equals(currentKeyword)) {
                    if(!currentKeyword.equals("")) {
                        for (TableReportModule module : tableModules) {
                            module.endTable();
                        }
                    }
                    currentKeyword = keyword;
                    for (TableReportModule module : tableModules) {
                        module.addSetElement(currentKeyword);
                        module.startTable(getArtifactTableColumnHeaders(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()));
                    }
                }
               
                String previewreplace = EscapeUtil.escapeHtml(preview);
                for (TableReportModule module : tableModules) {
                    module.addRow(Arrays.asList(new String[] {previewreplace.replaceAll("<!", ""), uniquePath, tagsList}));
                }
            }
           
            // Finish the current data type
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).increment();
                module.endDataType();
            }
        } catch (SQLException ex) {
            logger.log(Level.SEVERE, "Failed to query keywords.", ex); //NON-NLS
        } finally {
            if (rs != null) {
                try {
                    skCase.closeRunQuery(rs);
                } catch (SQLException ex) {
                }
            }
        }
    }
   
    /**
     * Write the hash set hits to the provided TableReportModules.
     * @param tableModules modules to report on
     */
    @SuppressWarnings("deprecation")
    private void writeHashsetHits(List<TableReportModule> tableModules,  String comment, HashSet<String> tagNamesFilter) {
        ResultSet listsRs = null;
        try {
            // Query for hashsets
            listsRs = skCase.runQuery("SELECT att.value_text AS list " + //NON-NLS
                                                "FROM blackboard_attributes AS att, blackboard_artifacts AS art " + //NON-NLS
                                                "WHERE att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + //NON-NLS
                                                    "AND art.artifact_type_id = " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + " " + //NON-NLS
                                                    "AND att.artifact_id = art.artifact_id " //NON-NLS
                                                "GROUP BY list"); //NON-NLS
            List<String> lists = new ArrayList<>();
            while(listsRs.next()) {
                lists.add(listsRs.getString("list")); //NON-NLS
            }
           
            for (TableReportModule module : tableModules) {
                module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment);
                module.addSetIndex(lists);
                tableProgress.get(module).updateStatusLabel(
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processing",
                                            ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName()));
            }
        } catch (SQLException ex) {       
            logger.log(Level.SEVERE, "Failed to query hashset lists.", ex); //NON-NLS
        } finally {
            if (listsRs != null) {
                try {
                    skCase.closeRunQuery(listsRs);
                } catch (SQLException ex) {
                }
            }
        }
       
        ResultSet rs = null;
        try {
            // Query for hashset hits
            rs = skCase.runQuery("SELECT art.artifact_id, art.obj_id, att.value_text AS setname, f.name AS name, f.size AS size " + //NON-NLS
                                           "FROM blackboard_artifacts AS art, blackboard_attributes AS att, tsk_files AS f " + //NON-NLS
                                           "WHERE (att.artifact_id = art.artifact_id) " + //NON-NLS
                                                 "AND (f.obj_id = art.obj_id) " + //NON-NLS
                                                 "AND (att.attribute_type_id = " + ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + //NON-NLS
                                                 "AND (art.artifact_type_id = " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() + ") " + //NON-NLS
                                           "ORDER BY setname, name, size"); //NON-NLS
            String currentSet = "";
            while (rs.next()) {
                // Check to see if all the TableReportModules have been canceled
                if (tableModules.isEmpty()) {
                    break;
                }
                Iterator<TableReportModule> iter = tableModules.iterator();
                while (iter.hasNext()) {
                    TableReportModule module = iter.next();
                    if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) {
                        iter.remove();
                    }
                }
               
                // Get any tags that associated with this artifact and apply the tag filter.
                HashSet<String> uniqueTagNames = getUniqueTagNames(rs.getLong("artifact_id")); //NON-NLS
                if(failsTagFilter(uniqueTagNames, tagNamesFilter)) {
                    continue;
                }                   
                String tagsList = makeCommaSeparatedList(uniqueTagNames);
                                                                       
                Long objId = rs.getLong("obj_id"); //NON-NLS
                String set = rs.getString("setname"); //NON-NLS
                String size = rs.getString("size"); //NON-NLS
                String uniquePath = "";
               
                try {
                    uniquePath = skCase.getAbstractFileById(objId).getUniquePath();
                } catch (TskCoreException ex) {
                    logger.log(Level.WARNING, "Failed to get Abstract File from ID.", ex); //NON-NLS
                }

                // If the sets aren't the same, we've started a new set
                if(!set.equals(currentSet)) {
                    if(!currentSet.isEmpty()) {
                        for (TableReportModule module : tableModules) {
                            module.endTable();
                            module.endSet();
                        }
                    }
                    currentSet = set;
                    for (TableReportModule module : tableModules) {
                        module.startSet(currentSet);
                        module.startTable(getArtifactTableColumnHeaders(ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()));
                        tableProgress.get(module).updateStatusLabel(
                                NbBundle.getMessage(this.getClass(), "ReportGenerator.progress.processingList",
                                                    ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), currentSet));
                    }
                }
               
                // Add a row for this hit to every module
                for (TableReportModule module : tableModules) {
                    module.addRow(Arrays.asList(new String[] {uniquePath, size, tagsList}));
                }
            }
           
            // Finish the current data type
            for (TableReportModule module : tableModules) {
                tableProgress.get(module).increment();
                module.endDataType();
            }
        } catch (SQLException ex) {
            logger.log(Level.SEVERE, "Failed to query hashsets hits.", ex); //NON-NLS
        } finally {
            if (rs != null) {
                try {
                    skCase.closeRunQuery(rs);
                } catch (SQLException ex) {
                }
            }
        }
    }
       
    /**
     * For a given artifact type ID, return the list of the row titles we're reporting on.
     *
     * @param artifactTypeId artifact type ID
     * @return List<String> row titles
     */
    private List<String> getArtifactTableColumnHeaders(int artifactTypeId) {
        ArrayList<String> columnHeaders;

        BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactTypeId);       
        switch (type) {
            case TSK_WEB_BOOKMARK:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateCreated"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_WEB_COOKIE:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.value"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_WEB_HISTORY:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.referrer"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.title"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_WEB_DOWNLOAD:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dest"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.sourceUrl"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.program"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_RECENT_OBJECT:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_INSTALLED_PROG:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.instDateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_KEYWORD_HIT:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.preview"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_HASHSET_HIT:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.size")}));
                break;
            case TSK_DEVICE_ATTACHED:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceId"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_WEB_SEARCH_QUERY:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.domain"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateAccessed"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_METADATA_EXIF:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTaken"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devManufacturer"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.devModel"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_CONTACT:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumHome"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumOffice"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumMobile"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.email"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_MESSAGE:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.msgType"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.readStatus"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromPhoneNum"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.fromEmail"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toPhoneNum"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.toEmail"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.subject"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_CALLLOG:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.direction"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_CALENDAR_ENTRY:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.calendarEntryType"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.startDateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.endDateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.location"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_SPEED_DIAL_ENTRY:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.shortCut"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.phoneNumber"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_BLUETOOTH_PAIRING:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.deviceAddress"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_GPS_TRACKPOINT:
                 columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_GPS_BOOKMARK:
                 columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_GPS_LAST_KNOWN_LOCATION:
                 columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_GPS_SEARCH:
                 columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.latitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.longitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.altitude"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.locationAddress"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.dateTime"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")  }));
                break;
            case TSK_SERVICE_ACCOUNT:
                 columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.category"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.userId"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.password"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.personName"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appName"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.url"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.appPath"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.replytoAddress"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mailServer"),
                         NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile") }));
                break;
            case TSK_TOOL_OUTPUT:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.progName"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_ENCRYPTION_DETECTED:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.name"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;
            case TSK_EXT_MISMATCH_DETECTED:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.file"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.extension.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.mimeType.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.path")}));
                break;   
            case TSK_OS_INFO:
                columnHeaders = new ArrayList<>(Arrays.asList(new String[] {
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.processorArchitecture.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osName.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.osInstallDate.text"),
                        NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.srcFile")}));
                break;   
            default:
                return null;
        }
        columnHeaders.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"));
       
        return columnHeaders;
    }
   
    /**
     * Map all BlackboardAttributes' values in a list of BlackboardAttributes to each attribute's attribute
     * type ID, using module's dateToString method for date/time conversions if a module is supplied.
     *
     * @param attList list of BlackboardAttributes to be mapped
     * @param module the TableReportModule the mapping is for
     * @return Map<Integer, String> of the BlackboardAttributes mapped to their attribute type ID
     */
    public Map<Integer, String> getMappedAttributes(List<BlackboardAttribute> attList, TableReportModule... module) {
        Map<Integer, String> attributes = new HashMap<>();
        int size = ATTRIBUTE_TYPE.values().length;
        for (int n = 0; n <= size; n++) {
            attributes.put(n, "");
        }
        for (BlackboardAttribute tempatt : attList) {
            String value = "";
            Integer type = tempatt.getAttributeTypeID();
            if (type.equals(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID()) ||
                type.equals(ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID())
                    ) {
                if (module.length > 0) {
                    value = module[0].dateToString(tempatt.getValueLong());
                } else {
                    SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    value = sdf.format(new java.util.Date((tempatt.getValueLong() * 1000)));
                }
            }
            else {
                value = tempatt.getDisplayString();
            }
           
            if (value == null) {
                value = "";
            }
            value = EscapeUtil.escapeHtml(value);
            attributes.put(type, value);
        }
        return attributes;
    }
   
    /**
     * Converts a collection of strings into a single string of comma-separated items
     *
     * @param items A collection of strings
     * @return A string of comma-separated items
     */
    private String makeCommaSeparatedList(Collection<String> items) {
        String list = "";
        for (Iterator<String> iterator = items.iterator(); iterator.hasNext(); ) {
            list += iterator.next() + (iterator.hasNext() ? ", " : "");
        }
        return list;
    }
   
    /**
     * Given a tsk_file's obj_id, return the unique path of that file.
     *
     * @param objId tsk_file obj_id
     * @return String unique path
     */
    private String getFileUniquePath(long objId) {
        try {
            return skCase.getAbstractFileById(objId).getUniquePath();
        } catch (TskCoreException ex) {
            logger.log(Level.WARNING, "Failed to get Abstract File by ID.", ex); //NON-NLS
        }
        return "";
    }

    /**
     * Container class that holds data about an Artifact to eliminate duplicate
     * calls to the Sleuthkit database.
     */
    private class ArtifactData implements Comparable<ArtifactData> {
        private BlackboardArtifact artifact;
        private List<BlackboardAttribute> attributes;
        private HashSet<String> tags;
        private List<String> rowData = null;
       
        ArtifactData(BlackboardArtifact artifact, List<BlackboardAttribute> attrs, HashSet<String> tags) {
            this.artifact = artifact;
            this.attributes = attrs;
            this.tags = tags;
        }
       
        public BlackboardArtifact getArtifact() { return artifact; }
       
        public List<BlackboardAttribute> getAttributes() { return attributes; }
       
        public HashSet<String> getTags() { return tags; }
       
        public long getArtifactID() { return artifact.getArtifactID(); }
       
        public long getObjectID() { return artifact.getObjectID(); }

       
        /**
         * Compares ArtifactData objects by the first attribute they have in
         * common in their List<BlackboardAttribute>.
         *
         * If all attributes are the same, they are assumed duplicates and are
         * compared by their artifact id. Should only be used with attributes
         * of the same type.
         */
        @Override
        public int compareTo(ArtifactData otherArtifactData) {
            List<String> thisRow = getRow();
            List<String> otherRow = otherArtifactData.getRow();
            for (int i = 0; i < thisRow.size(); i++) {
                int compare = thisRow.get(i).compareTo(otherRow.get(i));
                if (compare != 0) {
                    return compare;
                }
            }
            // If all attributes are the same, they're most likely duplicates so sort by artifact ID
            return ((Long) this.getArtifactID()).compareTo((Long) otherArtifactData.getArtifactID());
        }
       
        /**
         * Get the values for each row in the table report.
         */
        public List<String> getRow() {
            if (rowData == null) {
                try {
                    rowData = getOrderedRowDataAsStrings();
                    // replace null values if attribute was not defined
                    for (int i = 0; i < rowData.size(); i++) {
                        if (rowData.get(i) == null)
                            rowData.set(i, "");
                    }
                } catch (TskCoreException ex) {
                    logger.log(Level.WARNING, "Core exception while generating row data for artifact report.", ex); //NON-NLS
                    rowData = Collections.<String>emptyList();
                }
            }
            return rowData;
        }
       
       /**
        * Get a list of Strings with all the row values for the Artifact in the
        * correct order to be written to the report. 
        *
        * @return List<String> row values.  Values could be null if attribute is not defined in artifact
        * @throws TskCoreException
        */
       private List<String> getOrderedRowDataAsStrings() throws TskCoreException {
            Map<Integer, String> mappedAttributes = getMappedAttributes();           
            List<String> orderedRowData = new ArrayList<>();
            BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID());       
            switch (type) {
                case TSK_WEB_BOOKMARK:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_WEB_COOKIE:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_VALUE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_WEB_HISTORY:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TITLE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_WEB_DOWNLOAD:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_RECENT_OBJECT:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_INSTALLED_PROG:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_DEVICE_ATTACHED:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_WEB_SEARCH_QUERY:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                case TSK_METADATA_EXIF:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                 case TSK_CONTACT:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                 case TSK_MESSAGE:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_READ_STATUS.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_CALLLOG:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_CALENDAR_ENTRY:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_SPEED_DIAL_ENTRY:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SHORTCUT.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_BLUETOOTH_PAIRING:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DEVICE_ID.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_GPS_TRACKPOINT:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_GPS_BOOKMARK:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_GPS_LAST_KNOWN_LOCATION:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_GPS_SEARCH:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                  case TSK_SERVICE_ACCOUNT:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_USER_ID.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PASSWORD.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_URL.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PATH.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_SERVER_NAME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                 case TSK_TOOL_OUTPUT:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_TEXT.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
                 case TSK_ENCRYPTION_DETECTED:
                     orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_NAME.getTypeID()));
                     orderedRowData.add(getFileUniquePath(getObjectID()));
                     break;
                case TSK_EXT_MISMATCH_DETECTED:  
                    AbstractFile file = skCase.getAbstractFileById(getObjectID());
                    orderedRowData.add(file.getName());
                    orderedRowData.add(file.getNameExtension());
                    List<BlackboardAttribute> attrs = file.getGenInfoAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG);
                    if (!attrs.isEmpty()) {
                        orderedRowData.add(attrs.get(0).getValueString());
                    } else {
                        orderedRowData.add("");
                    }                  
                    orderedRowData.add(file.getUniquePath());
                    break;
                 case TSK_OS_INFO:
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()));
                    orderedRowData.add(mappedAttributes.get(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID()));
                    orderedRowData.add(getFileUniquePath(getObjectID()));
                    break;
            }
            orderedRowData.add(makeCommaSeparatedList(getTags()));

            return orderedRowData;
        }
      
        /**
         * Returns a mapping of Attribute Type ID to the String representation
         * of an Attribute Value.
         */
        private Map<Integer,String> getMappedAttributes() {
            return ReportGenerator.this.getMappedAttributes(attributes);
        }
    }
   
    /**
     * Get any tags associated with an artifact
     * @param artifactId
     * @return hash set of tag display names
     * @throws SQLException
     */
     @SuppressWarnings("deprecation")
    private HashSet<String> getUniqueTagNames(long artifactId) throws SQLException {
        HashSet<String> uniqueTagNames = new HashSet<>();
        ResultSet tagNameRows = skCase.runQuery("SELECT display_name, artifact_id FROM tag_names AS tn, blackboard_artifact_tags AS bat " + //NON-NLS
            "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId); //NON-NLS
        while (tagNameRows.next()) {
            uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS
        }
        skCase.closeRunQuery(tagNameRows);
        return uniqueTagNames;
    }   
   
}
TOP

Related Classes of org.sleuthkit.autopsy.report.ReportGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.