diff --git a/.gitignore b/.gitignore
index 9ae14c5cac10ab9bc458f3038f1d5ad98ead39ab..5e4866a207d88b19a01dbb1a0c084874eecb5a28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,3 +76,4 @@ Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png
 /test/script/*/*.xml
 .DS_Store
 .*.swp
+Core/src/org/sleuthkit/autopsy/datamodel/ranges.csv
diff --git a/Core/build.xml b/Core/build.xml
index 291f9258e5e18edcff1c99796b8efd15ddfee7f6..260d06b20b7a0cb508cb89e9df91275de3b7109a 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -26,8 +26,17 @@
         <copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.8.11.jar" tofile="${basedir}/release/modules/ext/sqlite-jdbc-3.8.11.jar"/>
     </target>
 
+    <target name="download-binlist">
+        <get src="https://raw.githubusercontent.com/binlist/data/master/ranges.csv" 
+             dest="src\org\sleuthkit\autopsy\datamodel" 
+             ignoreerrors="true"
+             verbose="true"/>
+    </target>
+
     <target name="init" depends="basic-init,files-init,build-init,-javac-init">
-        <!-- get additional deps -->
+        <antcall target="download-binlist" />   
+
+        <!-- get additional deps -->   
         <antcall target="getTSKJars" />
     </target>
 </project>
diff --git a/Core/manifest.mf b/Core/manifest.mf
index fef8f5793b6773f998b925ce39c27d01d052ad46..2070e22853c5e97f93901e3e4b2251c07cf36413 100644
--- a/Core/manifest.mf
+++ b/Core/manifest.mf
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 OpenIDE-Module: org.sleuthkit.autopsy.core/10
 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
 OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
-OpenIDE-Module-Implementation-Version: 16
+OpenIDE-Module-Implementation-Version: 17
 OpenIDE-Module-Requires: org.openide.windows.WindowManager
 AutoUpdate-Show-In-Client: true
 AutoUpdate-Essential-Module: true
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index bd43512d57f5c39aa8fcf5562bf2ca25445799b1..f3cf2e1903671534e3d01aaef3c9aa8a0eb139a1 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -23,5 +23,5 @@ nbm.homepage=http://www.sleuthkit.org/
 nbm.module.author=Brian Carrier
 nbm.needs.restart=true
 source.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip!/Source/
-spec.version.base=10.5
+spec.version.base=10.6
 
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java
new file mode 100755
index 0000000000000000000000000000000000000000..68a1549399732f654adc4f25bb219682538e3bc4
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddBookmarkTagAction.java
@@ -0,0 +1,67 @@
+/*
+* Autopsy Forensic Browser
+*
+* Copyright 2011-2016 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.actions;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.logging.Level;
+import javax.swing.AbstractAction;
+import javax.swing.KeyStroke;
+import org.openide.util.NbBundle;
+import org.openide.util.Utilities;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.TagName;
+import org.sleuthkit.datamodel.TskCoreException;
+
+public class AddBookmarkTagAction extends AbstractAction {
+
+    public static final KeyStroke BOOKMARK_SHORTCUT = KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_MASK);
+    private static final String NO_COMMENT = "";
+    private static final String BOOKMARK = NbBundle.getMessage(AddBookmarkTagAction.class, "AddBookmarkTagAction.bookmark.text");
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            Map<String, TagName> tagNamesMap = Case.getCurrentCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap();
+            TagName bookmarkTagName = tagNamesMap.get(BOOKMARK);
+
+            /*
+             * Both AddContentTagAction.addTag and
+             * AddBlackboardArtifactTagAction.addTag do their own lookup
+             * If the selection is a BlackboardArtifact wrapped around an
+             * AbstractFile, tag the artifact by default.
+             */
+            final Collection<BlackboardArtifact> artifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
+            if (!artifacts.isEmpty()) {
+                AddBlackboardArtifactTagAction.getInstance().addTag(bookmarkTagName, NO_COMMENT);
+            } else {
+                AddContentTagAction.getInstance().addTag(bookmarkTagName, NO_COMMENT);
+            }
+
+        } catch (TskCoreException ex) {
+            Logger.getLogger(AddBookmarkTagAction.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS
+        }
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
index e32f9df902fdb14913dd433314d96c8367cc4488..e5999fd98ce0634ed4fe599d2bc9a8653c1cbf7e 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java
@@ -29,6 +29,9 @@
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.FsContent;
+import org.sleuthkit.datamodel.LayoutFile;
+import org.sleuthkit.datamodel.LocalFile;
 import org.sleuthkit.datamodel.TagName;
 import org.sleuthkit.datamodel.TskCoreException;
 
@@ -67,6 +70,8 @@ protected void addTag(TagName tagName, String comment) {
          * collection it returns may contain duplicates. Within this invocation
          * of addTag(), we don't want to tag the same AbstractFile more than
          * once, so we dedupe the AbstractFiles by stuffing them into a HashSet.
+         *
+         * We don't want VirtualFile and DerivedFile objects to be tagged.
          */
         final Collection<AbstractFile> selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
 
diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
index 26288a54a2fc85d358ef8ad174744d8593431aae..f665aeb7d5b35e4e6d40dbfbec5796123744d0ac 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
+ *
  * Copyright 2011-2016 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.
@@ -103,10 +103,16 @@ private class TagMenu extends JMenu {
 
             // Each tag name in the current set of tags gets its own menu item in
             // the "Quick Tags" sub-menu. Selecting one of these menu items adds
-            // a tag with the associated tag name. 
+            // a tag with the associated tag name.
             if (null != tagNamesMap && !tagNamesMap.isEmpty()) {
                 for (Map.Entry<String, TagName> entry : tagNamesMap.entrySet()) {
-                    JMenuItem tagNameItem = new JMenuItem(entry.getKey());
+                    String tagDisplayName = entry.getKey();
+                    JMenuItem tagNameItem = new JMenuItem(tagDisplayName);
+                    // for the bookmark tag name only, added shortcut label
+                    if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) {
+                        tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT);
+                    }
+
                     tagNameItem.addActionListener((ActionEvent e) -> {
                         getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT);
                     });
@@ -133,7 +139,7 @@ private class TagMenu extends JMenu {
             quickTagMenu.add(newTagMenuItem);
 
             // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates
-            // a dialog that can be used to create or select a tag name with an 
+            // a dialog that can be used to create or select a tag name with an
             // optional comment and adds a tag with the resulting name.
             JMenuItem tagAndCommentItem = new JMenuItem(
                     NbBundle.getMessage(this.getClass(), "AddTagAction.tagAndComment"));
@@ -163,7 +169,11 @@ private void getAndAddTag(String tagDisplayName, TagName tagName, String comment
                 try {
                     tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName);
                 } catch (TagsManager.TagNameAlreadyExistsException ex) {
-                    Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS
+                    try {
+                        tagName = Case.getCurrentCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName);
+                    } catch (TskCoreException ex1) {
+                        Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database but an error occurred in retrieving it.", ex1); //NON-NLS
+                    }
                 } catch (TskCoreException ex) {
                     Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS
                 }
diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
index d964d91fc7018c7dd782bbff778c2774878b0c6c..6314dc6f405ade962ce50bf62e1b7cd97938229f 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
@@ -25,10 +25,12 @@ AddContentTagAction.cannotApplyTagErr=Cannot Apply Tag
 AddContentTagAction.unableToTag.msg2=Unable to tag {0}.
 AddContentTagAction.taggingErr=Tagging Error
 AddContentTagAction.tagExists={0} has been tagged as {1}. Cannot reapply the same tag.
+AddTagAction.bookmarkFile=Bookmark file
 AddTagAction.quickTag=Quick Tag
 AddTagAction.noTags=No tags
 AddTagAction.newTag=New Tag...
 AddTagAction.tagAndComment=Tag and Comment...
+AddBookmarkTagAction.bookmark.text=Bookmark
 DeleteBlackboardArtifactTagAction.deleteTags=Delete Tag(s)
 DeleteBlackboardArtifactTagAction.unableToDelTag.msg=Unable to delete tag {0}.
 DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error
@@ -48,6 +50,7 @@ GetTagNameDialog.unableToAddTagNameToCase.msg=Unable to add the {0} tag name to
 GetTagNameDialog.taggingErr=Tagging Error
 GetTagNameDialog.tagNameAlreadyDef.msg=A {0} tag name has already been defined.
 GetTagNameDialog.dupTagErr=Duplicate Tag Error
+GetTagNameDialog.tagNameExistsTskCore.msg=The {0} tag name already exists in the database but an error occurred in retrieving it.
 OpenLogFolder.error1=Log File Not Found: {0}
 OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder 
 CTL_OpenLogFolder=Open Log Folder
diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
index 73290602e9c02d186f02a1a7cdc9a93365397fe8..647d8513415771428ac71234d9ac29f940bae96b 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java
@@ -252,7 +252,11 @@ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRS
             try {
                 tagNameFromCombo = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName);
             } catch (TagsManager.TagNameAlreadyExistsException ex) {
-                Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS
+                try {
+                    tagNameFromCombo = Case.getCurrentCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName);
+                } catch (TskCoreException ex1) {
+                    Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database but an error occurred in retrieving it.", ex1); //NON-NLS
+                }
             } catch (TskCoreException ex) {
                 Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS
             }
diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java
index 3d1057a0412bbe5b3ce5344db9376bf3642785d8..4e5720a0fc664bc26324e672fe3daa9634e915db 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java
@@ -53,7 +53,7 @@ public class GetTagNameDialog extends JDialog {
     /**
      * Show the Tag Name Dialog and return the TagName selected by the user. The
      * dialog will be centered with the main autopsy window as its owner. To set
-     * another window as the owner use {@link #doDialog(java.awt.Window) }
+     * another window as the owner use doDialog(Window) instead.
      *
      * @return a TagName instance selected by the user, or null if the user
      *         canceled the dialog.
@@ -317,14 +317,18 @@ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRS
                             JOptionPane.ERROR_MESSAGE);
                     tagName = null;
                 } catch (TagsManager.TagNameAlreadyExistsException ex) {
-                    Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS
-                    JOptionPane.showMessageDialog(null,
-                            NbBundle.getMessage(this.getClass(),
-                                    "GetTagNameDialog.tagNameAlreadyDef.msg",
-                                    tagDisplayName),
-                            NbBundle.getMessage(this.getClass(), "GetTagNameDialog.dupTagErr"),
-                            JOptionPane.ERROR_MESSAGE);
-                    tagName = null;
+                    try {
+                        tagName = Case.getCurrentCase().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName);
+                    } catch (TskCoreException ex1) {
+                        Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " exists in database but an error occurred in retrieving it.", ex1); //NON-NLS
+                        JOptionPane.showMessageDialog(null,
+                                NbBundle.getMessage(this.getClass(),
+                                        "GetTagNameDialog.tagNameExistsTskCore.msg",
+                                        tagDisplayName),
+                                NbBundle.getMessage(this.getClass(), "GetTagNameDialog.dupTagErr"),
+                                JOptionPane.ERROR_MESSAGE);
+                        tagName = null;
+                    }
                 }
             } else {
                 dispose();
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index 229ffe5cdb71bf6093d63c3ee85d098443cf8d05..b58268dce3098ad2f9e840776fa81266c4b69b73 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -158,6 +158,7 @@ CueBannerPanel.title.text=Open Recent Case
 GeneralFilter.rawImageDesc.text=Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin)
 GeneralFilter.encaseImageDesc.text=Encase Images (*.e01)
 GeneralFilter.virtualMachineImageDesc.text=Virtual Machines (*.vmdk, *.vhd)
+GeneralFilter.executableDesc.text=Executables (*.exe)
 ImageDSProcessor.dsType.text=Image or VM File
 ImageDSProcessor.allDesc.text=All Supported Types
 ImageFilePanel.moduleErr=Module Error
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 91bb5c0a95e2c5319e71969947d1acab3f40a657..0b804e1e3b6c072095d7c63ad9793e03e9db9a59 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -1520,8 +1520,8 @@ public static void invokeStartupDialog() {
      * instead.
      */
     @Deprecated
-    public static String convertTimeZone(String timezoneID) {
-        return TimeZoneUtils.convertToAlphaNumericFormat(timezoneID);
+    public static String convertTimeZone(String timeZoneId) {
+        return TimeZoneUtils.convertToAlphaNumericFormat(timeZoneId);
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java
index ffe6f26b72ac3126f24003e4e70a236462595342..0f90bc9faea2826096c9e1e8b0df074645780deb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java
@@ -42,6 +42,9 @@ public class GeneralFilter extends FileFilter {
     public static final String VIRTUAL_MACHINE_DESC = NbBundle.getMessage(GeneralFilter.class,
             "GeneralFilter.virtualMachineImageDesc.text");    
 
+    public static final List<String> EXECUTABLE_EXTS = Arrays.asList(new String[]{".exe"}); //NON-NLS
+    public static final String EXECUTABLE_DESC = NbBundle.getMessage(GeneralFilter.class, "GeneralFilter.executableDesc.text");
+    
     private List<String> extensions;
     private String desc;
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java
index c332fa8722c9ad907150a165ae015863ef24dd41..b7a36fdcbe3b911d5258ab58630a7695e8c59a5f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/AddingDataSourceEvent.java
@@ -31,7 +31,7 @@
 public final class AddingDataSourceEvent extends AutopsyEvent implements Serializable {
 
     private static final long serialVersionUID = 1L;
-    private final UUID eventId;
+    private final UUID dataSourceId;
 
     /**
      * Constructs an event published when a data source is being added to a
@@ -42,9 +42,9 @@ public final class AddingDataSourceEvent extends AutopsyEvent implements Seriali
      *                     DataSourceAddedEvent or a
      *                     AddingDataSourceFailedEvent.
      */
-    public AddingDataSourceEvent(UUID eventId) {
+    public AddingDataSourceEvent(UUID dataSourceId) {
         super(Case.Events.ADDING_DATA_SOURCE.toString(), null, null);
-        this.eventId = eventId;
+        this.dataSourceId = dataSourceId;
     }
 
     /**
@@ -55,7 +55,7 @@ public AddingDataSourceEvent(UUID eventId) {
      * @return The unique event id.
      */
     public UUID getEventId() {
-        return eventId;
+        return dataSourceId;
     }
 
     /**
@@ -68,7 +68,7 @@ public UUID getEventId() {
      */
     @Deprecated
     public UUID getDataSourceId() {
-        return eventId;
+        return dataSourceId;
     }
 
 }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
index 229d26af582ea0475ad821755e3e92fe0598be62..0dd66e8c59d52ed0e0df20b3435e50534bc8cd6e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties
@@ -1,13 +1,11 @@
 OptionsCategory_Name_TagNamesOptions=Tags
 OptionsCategory_TagNames=TagNames
 Blackboard.unableToIndexArtifact.error.msg=Unable to index blackboard artifact {0}
-NewUserTagNameDialog.title.text=New Tag Name
-NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ;
-NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name
-TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.message=The tag name already exists in your settings
-TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.title=Tag name already exists
-NewUserTagNameDialog.JOptionPane.tagNameEmpty.message=The tag name cannot be empty
-NewUserTagNameDialog.JOptionPane.tagNameEmpty.title=Empty tag name
+NewTagNameDialog.title.text=New Tag Name
+NewTagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ;
+NewTagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name
+NewTagNameDialog.JOptionPane.tagNameEmpty.message=The tag name cannot be empty
+NewTagNameDialog.JOptionPane.tagNameEmpty.title=Empty tag name
 TagOptionsPanel.tagTypesListLabel.text=Tag Names:
 TagOptionsPanel.panelDescriptionLabel.text=Autopsy keeps a list of the tag names you have created in the past. Add more or delete them here.
 NewTagNameDialog.okButton.text=OK
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
index 57a67122826c547a4d6c8ec3fa0f77ea9cb02443..eb810e708c1307d2d918b9aa21a2dad90a4c17f5 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
@@ -268,7 +268,6 @@ public synchronized List<AbstractFile> findFiles(Content dataSource, String file
      * components at the volume level and above are removed for the search.
      *
      * @param dataSource The data source.
-     * @param fileName   The full or partial file name.
      * @param filePath   The file path (path components volume at the volume
      *                   level or above will be removed).
      *
@@ -356,7 +355,7 @@ public interface FileAddProgressUpdater {
         /**
          * Called after a file or directory is added to the case database.
          *
-         * @param An AbstractFile represeting the added file or directory.
+         * @param newFile AbstractFile representing the added file or directory.
          */
         void fileAdded(AbstractFile newFile);
     }
@@ -499,8 +498,6 @@ private List<java.io.File> getFilesAndDirectories(List<String> localFilePaths) t
      * @param trans              A case database transaction.
      * @param parentDirectory    The root virtual direcotry of the data source.
      * @param localFile          The local/logical file or directory.
-     * @param addProgressUpdater notifier to receive progress notifications on
-     *                           folders added, or null if not used
      * @param encodingType       Type of encoding used when storing the file
      *
      * @returns File object of file added or new virtualdirectory for the
@@ -614,8 +611,9 @@ public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, lon
      * Adds a collection of carved files to the '$CarvedFiles' virtual directory
      * of a data source, volume or file system.
      *
-     * @param A collection of CarvedFileContainer objects, one per carved file,
-     *          all of which must have the same parent object id.
+     * @param filesToAdd A collection of CarvedFileContainer objects, one per
+     *                   carved file, all of which must have the same parent
+     *                   object id.
      *
      * @return A collection of LayoutFile object representing the carved files.
      *
@@ -660,7 +658,7 @@ public synchronized List<LayoutFile> addCarvedFiles(List<org.sleuthkit.datamodel
      * @throws TskCoreException if there is a problem adding the file to the
      *                          case database.
      *
-     * @Deprecated Use the version with explicit EncodingType instead
+     * @deprecated Use the version with explicit EncodingType instead
      */
     @Deprecated
     public synchronized DerivedFile addDerivedFile(String fileName,
@@ -681,7 +679,7 @@ public synchronized DerivedFile addDerivedFile(String fileName,
      * @param trans              A case database transaction.
      * @param parentDirectory    The root virtual direcotry of the data source.
      * @param localFile          The local/logical file or directory.
-     * @param addProgressUpdater notifier to receive progress notifications on
+     * @param progressUpdater notifier to receive progress notifications on
      *                           folders added, or null if not used
      *
      * @returns File object of file added or new virtualdirectory for the
@@ -694,7 +692,7 @@ public synchronized DerivedFile addDerivedFile(String fileName,
      * @throws TskCoreException If there is a problem completing a database
      *                          operation.
      *
-     * @Deprecated Use the version with explicit EncodingType instead
+     * @deprecated Use the version with explicit EncodingType instead
      */
     @Deprecated
     private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java
index fbb7594c44506819ba3b00c9dc679e076b84b1ac..ff21ac283bad5ac10df47861dad37036ef0e21f1 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java
@@ -43,8 +43,8 @@ enum BUTTON_PRESSED {
      * Creates a new NewUserTagNameDialog dialog.
      */
     NewTagNameDialog() {
-        super(new JFrame(NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.title.text")),
-                NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.title.text"), true);
+        super(new JFrame(NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.title.text")),
+                NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.title.text"), true);
         initComponents();
         this.display();
     }
@@ -112,15 +112,15 @@ private void doButtonAction(boolean okPressed) {
             String newTagDisplayName = tagNameTextField.getText().trim();
             if (newTagDisplayName.isEmpty()) {
                 JOptionPane.showMessageDialog(null,
-                        NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.message"),
-                        NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.title"),
+                        NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.JOptionPane.tagNameEmpty.message"),
+                        NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.JOptionPane.tagNameEmpty.title"),
                         JOptionPane.ERROR_MESSAGE);
                 return;
             }
             if (TagsManager.containsIllegalCharacters(newTagDisplayName)) {
                 JOptionPane.showMessageDialog(null,
-                        NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message"),
-                        NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title"),
+                        NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.JOptionPane.tagNameIllegalCharacters.message"),
+                        NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.JOptionPane.tagNameIllegalCharacters.title"),
                         JOptionPane.ERROR_MESSAGE);
                 return;
             }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form
index 1f81b4d3aa0271ce6d212e05331f562275ebff3f..3f33f848c0bcde117c54e9fac5c0bb7f948941df 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form
@@ -21,10 +21,7 @@
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" alignment="0" attributes="0">
-              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
-              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
-          </Group>
+          <Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
   </Layout>
@@ -39,11 +36,11 @@
       <Layout>
         <DimensionLayout dim="0">
           <Group type="103" groupAlignment="0" attributes="0">
-              <Group type="102" alignment="1" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="1" attributes="0">
-                      <Component id="jSplitPane1" max="32767" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
                       <Component id="panelDescriptionLabel" max="32767" attributes="0"/>
+                      <Component id="jScrollPane2" max="32767" attributes="0"/>
                   </Group>
                   <EmptySpace max="-2" attributes="0"/>
               </Group>
@@ -55,7 +52,7 @@
                   <EmptySpace max="-2" attributes="0"/>
                   <Component id="panelDescriptionLabel" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jSplitPane1" max="32767" attributes="0"/>
+                  <Component id="jScrollPane2" pref="458" max="32767" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
               </Group>
           </Group>
@@ -69,132 +66,138 @@
             </Property>
           </Properties>
         </Component>
-        <Container class="javax.swing.JSplitPane" name="jSplitPane1">
-          <Properties>
-            <Property name="dividerLocation" type="int" value="400"/>
-            <Property name="dividerSize" type="int" value="1"/>
-          </Properties>
+        <Container class="javax.swing.JScrollPane" name="jScrollPane2">
 
-          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
+          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
           <SubComponents>
-            <Container class="javax.swing.JPanel" name="modifyTagTypesListPanel">
-              <Constraints>
-                <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
-                  <JSplitPaneConstraints position="left"/>
-                </Constraint>
-              </Constraints>
+            <Container class="javax.swing.JSplitPane" name="jSplitPane1">
+              <Properties>
+                <Property name="dividerLocation" type="int" value="400"/>
+                <Property name="dividerSize" type="int" value="1"/>
+              </Properties>
 
-              <Layout>
-                <DimensionLayout dim="0">
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <Group type="102" alignment="0" attributes="0">
-                          <EmptySpace max="-2" attributes="0"/>
-                          <Group type="103" groupAlignment="0" attributes="0">
-                              <Component id="tagTypesListLabel" alignment="0" max="32767" attributes="0"/>
-                              <Group type="102" alignment="0" attributes="0">
-                                  <Component id="newTagNameButton" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="deleteTagNameButton" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace min="0" pref="113" max="32767" attributes="0"/>
+              <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
+              <SubComponents>
+                <Container class="javax.swing.JPanel" name="modifyTagTypesListPanel">
+                  <Constraints>
+                    <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
+                      <JSplitPaneConstraints position="left"/>
+                    </Constraint>
+                  </Constraints>
+
+                  <Layout>
+                    <DimensionLayout dim="0">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="0" attributes="0">
+                                  <Component id="jScrollPane1" alignment="1" max="32767" attributes="0"/>
+                                  <Component id="tagTypesListLabel" alignment="0" max="32767" attributes="0"/>
+                                  <Group type="102" attributes="0">
+                                      <Component id="newTagNameButton" min="-2" max="-2" attributes="0"/>
+                                      <EmptySpace max="-2" attributes="0"/>
+                                      <Component id="deleteTagNameButton" min="-2" max="-2" attributes="0"/>
+                                      <EmptySpace min="0" pref="113" max="32767" attributes="0"/>
+                                  </Group>
                               </Group>
-                              <Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
                           </Group>
-                          <EmptySpace max="-2" attributes="0"/>
                       </Group>
-                  </Group>
-                </DimensionLayout>
-                <DimensionLayout dim="1">
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <Group type="102" alignment="0" attributes="0">
-                          <EmptySpace max="-2" attributes="0"/>
-                          <Component id="tagTypesListLabel" min="-2" max="-2" attributes="0"/>
-                          <EmptySpace max="-2" attributes="0"/>
-                          <Component id="jScrollPane1" pref="383" max="32767" attributes="0"/>
-                          <EmptySpace max="-2" attributes="0"/>
-                          <Group type="103" groupAlignment="3" attributes="0">
-                              <Component id="newTagNameButton" alignment="3" min="-2" max="-2" attributes="0"/>
-                              <Component id="deleteTagNameButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                    </DimensionLayout>
+                    <DimensionLayout dim="1">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="tagTypesListLabel" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="jScrollPane1" pref="381" max="32767" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="3" attributes="0">
+                                  <Component id="newTagNameButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                                  <Component id="deleteTagNameButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                              </Group>
+                              <EmptySpace max="-2" attributes="0"/>
                           </Group>
-                          <EmptySpace max="-2" attributes="0"/>
                       </Group>
-                  </Group>
-                </DimensionLayout>
-              </Layout>
-              <SubComponents>
-                <Component class="javax.swing.JLabel" name="tagTypesListLabel">
-                  <Properties>
-                    <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-                      <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.tagTypesListLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-                    </Property>
-                  </Properties>
-                </Component>
-                <Container class="javax.swing.JScrollPane" name="jScrollPane1">
-                  <AuxValues>
-                    <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
-                  </AuxValues>
-
-                  <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+                    </DimensionLayout>
+                  </Layout>
                   <SubComponents>
-                    <Component class="javax.swing.JList" name="tagNamesList">
+                    <Component class="javax.swing.JLabel" name="tagTypesListLabel">
                       <Properties>
-                        <Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
-                          <StringArray count="0"/>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.tagTypesListLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
                         </Property>
-                        <Property name="selectionMode" type="int" value="0"/>
                       </Properties>
+                    </Component>
+                    <Container class="javax.swing.JScrollPane" name="jScrollPane1">
                       <AuxValues>
-                        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;org.sleuthkit.autopsy.casemodule.services.TagNameDefiniton&gt;"/>
+                        <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
                       </AuxValues>
+
+                      <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+                      <SubComponents>
+                        <Component class="javax.swing.JList" name="tagNamesList">
+                          <Properties>
+                            <Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
+                              <StringArray count="0"/>
+                            </Property>
+                            <Property name="selectionMode" type="int" value="0"/>
+                          </Properties>
+                          <AuxValues>
+                            <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;org.sleuthkit.autopsy.casemodule.services.TagNameDefiniton&gt;"/>
+                          </AuxValues>
+                        </Component>
+                      </SubComponents>
+                    </Container>
+                    <Component class="javax.swing.JButton" name="newTagNameButton">
+                      <Properties>
+                        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+                          <Image iconType="3" name="/org/sleuthkit/autopsy/images/add-tag.png"/>
+                        </Property>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.newTagNameButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                      <Events>
+                        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newTagNameButtonActionPerformed"/>
+                      </Events>
+                    </Component>
+                    <Component class="javax.swing.JButton" name="deleteTagNameButton">
+                      <Properties>
+                        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+                          <Image iconType="3" name="/org/sleuthkit/autopsy/images/delete-tag.png"/>
+                        </Property>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.deleteTagNameButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                      <Events>
+                        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteTagNameButtonActionPerformed"/>
+                      </Events>
                     </Component>
                   </SubComponents>
                 </Container>
-                <Component class="javax.swing.JButton" name="newTagNameButton">
-                  <Properties>
-                    <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
-                      <Image iconType="3" name="/org/sleuthkit/autopsy/images/add-tag.png"/>
-                    </Property>
-                    <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-                      <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.newTagNameButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-                    </Property>
-                  </Properties>
-                  <Events>
-                    <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newTagNameButtonActionPerformed"/>
-                  </Events>
-                </Component>
-                <Component class="javax.swing.JButton" name="deleteTagNameButton">
-                  <Properties>
-                    <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
-                      <Image iconType="3" name="/org/sleuthkit/autopsy/images/delete-tag.png"/>
-                    </Property>
-                    <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-                      <ResourceString bundle="org/sleuthkit/autopsy/casemodule/services/Bundle.properties" key="TagOptionsPanel.deleteTagNameButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-                    </Property>
-                  </Properties>
-                  <Events>
-                    <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteTagNameButtonActionPerformed"/>
-                  </Events>
-                </Component>
-              </SubComponents>
-            </Container>
-            <Container class="javax.swing.JPanel" name="tagTypesAdditionalPanel">
-              <Constraints>
-                <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
-                  <JSplitPaneConstraints position="right"/>
-                </Constraint>
-              </Constraints>
+                <Container class="javax.swing.JPanel" name="tagTypesAdditionalPanel">
+                  <Constraints>
+                    <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
+                      <JSplitPaneConstraints position="right"/>
+                    </Constraint>
+                  </Constraints>
 
-              <Layout>
-                <DimensionLayout dim="0">
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <EmptySpace min="0" pref="356" max="32767" attributes="0"/>
-                  </Group>
-                </DimensionLayout>
-                <DimensionLayout dim="1">
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <EmptySpace min="0" pref="456" max="32767" attributes="0"/>
-                  </Group>
-                </DimensionLayout>
-              </Layout>
+                  <Layout>
+                    <DimensionLayout dim="0">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <EmptySpace min="0" pref="354" max="32767" attributes="0"/>
+                      </Group>
+                    </DimensionLayout>
+                    <DimensionLayout dim="1">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <EmptySpace min="0" pref="454" max="32767" attributes="0"/>
+                      </Group>
+                    </DimensionLayout>
+                  </Layout>
+                </Container>
+              </SubComponents>
             </Container>
           </SubComponents>
         </Container>
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java
index 4f761062e867fcb113c58874d6c7736514e0634c..c1c9beb5b2dd4bf6a33706673a58adcd33724855 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java
@@ -67,6 +67,7 @@ private void initComponents() {
 
         jPanel1 = new javax.swing.JPanel();
         panelDescriptionLabel = new javax.swing.JLabel();
+        jScrollPane2 = new javax.swing.JScrollPane();
         jSplitPane1 = new javax.swing.JSplitPane();
         modifyTagTypesListPanel = new javax.swing.JPanel();
         tagTypesListLabel = new javax.swing.JLabel();
@@ -111,13 +112,13 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
             .addGroup(modifyTagTypesListPanelLayout.createSequentialGroup()
                 .addContainerGap()
                 .addGroup(modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING)
                     .addComponent(tagTypesListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                     .addGroup(modifyTagTypesListPanelLayout.createSequentialGroup()
                         .addComponent(newTagNameButton)
                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                         .addComponent(deleteTagNameButton)
-                        .addGap(0, 113, Short.MAX_VALUE))
-                    .addComponent(jScrollPane1))
+                        .addGap(0, 113, Short.MAX_VALUE)))
                 .addContainerGap())
         );
         modifyTagTypesListPanelLayout.setVerticalGroup(
@@ -126,7 +127,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                 .addContainerGap()
                 .addComponent(tagTypesListLabel)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 381, Short.MAX_VALUE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(newTagNameButton)
@@ -140,24 +141,26 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         tagTypesAdditionalPanel.setLayout(tagTypesAdditionalPanelLayout);
         tagTypesAdditionalPanelLayout.setHorizontalGroup(
             tagTypesAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 356, Short.MAX_VALUE)
+            .addGap(0, 354, Short.MAX_VALUE)
         );
         tagTypesAdditionalPanelLayout.setVerticalGroup(
             tagTypesAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 456, Short.MAX_VALUE)
+            .addGap(0, 454, Short.MAX_VALUE)
         );
 
         jSplitPane1.setRightComponent(tagTypesAdditionalPanel);
 
+        jScrollPane2.setViewportView(jSplitPane1);
+
         javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
         jPanel1.setLayout(jPanel1Layout);
         jPanel1Layout.setHorizontalGroup(
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
+            .addGroup(jPanel1Layout.createSequentialGroup()
                 .addContainerGap()
-                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
-                    .addComponent(jSplitPane1)
-                    .addComponent(panelDescriptionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(panelDescriptionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addComponent(jScrollPane2))
                 .addContainerGap())
         );
         jPanel1Layout.setVerticalGroup(
@@ -166,7 +169,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                 .addContainerGap()
                 .addComponent(panelDescriptionLabel)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jSplitPane1)
+                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 458, Short.MAX_VALUE)
                 .addContainerGap())
         );
 
@@ -178,9 +181,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(layout.createSequentialGroup()
-                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addGap(0, 0, Short.MAX_VALUE))
+            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
         );
     }// </editor-fold>//GEN-END:initComponents
 
@@ -220,6 +221,7 @@ private void deleteTagNameButtonActionPerformed(java.awt.event.ActionEvent evt)
     private javax.swing.JButton deleteTagNameButton;
     private javax.swing.JPanel jPanel1;
     private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JScrollPane jScrollPane2;
     private javax.swing.JSplitPane jSplitPane1;
     private javax.swing.JPanel modifyTagTypesListPanel;
     private javax.swing.JButton newTagNameButton;
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
index 46982397f8bca7e6d11ce7d6140f487f0e7dd612..9d4d0eb112d52d64128f5ad2f4df93767de21996 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java
@@ -97,11 +97,11 @@ public List<TagName> getTagNamesInUse() throws TskCoreException {
      */
     public synchronized Map<String, TagName> getDisplayNamesToTagNamesMap() throws TskCoreException {
         /**
-         * Order is important here. The keys (display names) for the standard
-         * tag types and current user's custom tag types are added to the map
-         * first, with null TagName values. If tag name entries exist for those
-         * keys, loading of the tag names from the database supplies the missing
-         * values.
+         * Order is important here. The keys (display names) for the current
+         * user's custom tag types are added to the map first, with null TagName
+         * values. If tag name entries exist for those keys, loading of the tag
+         * names from the database supplies the missing values. Standard tag
+         * names are added during the initialization of the case database.
          *
          * Note that creating the map on demand increases the probability that
          * the display names of newly added custom tag types and the display
@@ -109,7 +109,6 @@ public synchronized Map<String, TagName> getDisplayNamesToTagNamesMap() throws T
          * map.
          */
         Map<String, TagName> tagNames = new HashMap<>();
-        tagNames.put(NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"), null);
         Set<TagNameDefiniton> customTypes = TagNameDefiniton.getTagNameDefinitions();
         for (TagNameDefiniton tagType : customTypes) {
             tagNames.put(tagType.getDisplayName(), null);
@@ -536,5 +535,4 @@ public synchronized boolean tagNameExists(String tagDisplayName) {
     @Deprecated
     public synchronized void close() throws IOException {
     }
-
 }
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/package.dox b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/package.dox
index 51cbc93198ae21e05856d80b00939556c31f5fce..57c5f417de8b0d1ee373f773d057036ecfae7771 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/package.dox
+++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/package.dox
@@ -1,8 +1,6 @@
 /**
 \package org.sleuthkit.autopsy.corecomponentinterfaces
 
-This package contains the interface classes that define the core components in Autopsy.  These components are used in the different zones of the GUI.
-
-See \ref design_data_flow for examples of the modules and such that it defines. 
+This package contains the interface classes that define the core components in Autopsy.  These components are used in the different zones of the GUI. 
 
 */
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
index 65349282c506d8b0c51c700a716a459bedfa5189..7761afe04d412dd6a04eeef0bd979d557e9184ce 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java
@@ -23,6 +23,7 @@
 import java.util.List;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataResult;
 import java.util.logging.Level;
+import javax.swing.JComponent;
 import org.openide.explorer.ExplorerManager;
 import org.openide.explorer.ExplorerUtils;
 import org.openide.util.NbBundle;
@@ -30,6 +31,7 @@
 import org.openide.nodes.Node;
 import org.openide.windows.Mode;
 import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.AddBookmarkTagAction;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
 import org.sleuthkit.autopsy.coreutils.Logger;
@@ -104,6 +106,8 @@ private void customizeComponent(boolean isMain, String title) {
 
         setTitle(title); // set the title
         setName(title);
+        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
+        getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
 
         putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI
         putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true);
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/History.java b/Core/src/org/sleuthkit/autopsy/coreutils/History.java
index 409cfe0006c98ec74cb40ad09910b6981558d298..746ac9f710851d713bf13d2cc0195fe6ed755e8f 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/History.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/History.java
@@ -32,12 +32,12 @@
 
 /**
  * A basic history implementation. Keeps a history (and forward) stack of state
- * objects of type <T>. exposes current state and availability of
- * advance/retreat operations via methods and JFX {@link  Property}s. Null is not
+ * objects of type T. exposes current state and availability of
+ * advance/retreat operations via methods and JFX Property objects. Null is not
  * a valid state, and will only be the current state before the first call to
  * advance.
  *
- * @param <T> the type of objects used to represent the
+ * @param T the type of objects used to represent the
  *            current/historical/future states
  */
 @ThreadSafe
@@ -168,7 +168,7 @@ synchronized public void clear() {
      *
      * TODO: this really should not extend SimpleListProperty but should
      * delegate to an appropriate observable implementation while implementing
-     * the {@link Deque} interface
+     * the Deque interface
      */
     private static class ObservableStack<T> extends SimpleListProperty<T> {
 
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java
index 9f5d9e6cbc459662b567e5cd731a49459e3af9c4..6fd18280fe41baf9ccabe84a8f7e755bb446051e 100755
--- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java
@@ -1,18 +1,18 @@
 /*
  * Autopsy Forensic Browser
- * 
+ *
  * Copyright 2012-16 Basis Technology Corp.
- * 
+ *
  * Copyright 2012 42six Solutions.
  * Contact: aebadirad <at> 42six <dot> com
  * Project Contact/Architect: 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.
@@ -323,7 +323,7 @@ public static File getCachedThumbnailFile(Content content, int iconSize) {
 
     /**
      * Get the location of the cached thumbnail for a file with the given fileID
-     * as a java {@link File}. The returned File may not exist on disk yet.
+     * as a java File. The returned File may not exist on disk yet.
      *
      * @param fileID the fileID to get the cached thumbnail location for
      *
@@ -510,7 +510,6 @@ private static interface PropertyExtractor<T> {
      * public methods that pull particular (usually meta-)data out of a image
      * file.
      *
-     * @param <T>               the type of the property to be retrieved.
      * @param file              the file to extract the data from
      * @param errorTemplate     a message template used to log errors. Should
      *                          take one parameter: the file's unique path or
@@ -560,10 +559,10 @@ private static <T> T getImageProperty(AbstractFile file, final String errorTempl
     }
 
     /**
-     * Create a new {@link Task} that will get a thumbnail for the given image
-     * of the specified size. If a cached thumbnail is available it will be
-     * returned as the result of the task, otherwise a new thumbnail will be
-     * created and cached.
+     * Create a new Task that will get a thumbnail for the given image of the
+     * specified size. If a cached thumbnail is available it will be returned as
+     * the result of the task, otherwise a new thumbnail will be created and
+     * cached.
      *
      * Note: the returned task is suitable for running in a background thread,
      * but is not started automatically. Clients are responsible for running the
@@ -712,8 +711,8 @@ private void saveThumbnail(BufferedImage thumbnail) {
     }
 
     /**
-     * Create a new {@link Task} that will read the file into memory as an
-     * {@link javafx.scene.image.Image}
+     * Create a new Task that will read the file into memory as an
+     * javafx.scene.image.Image.
      *
      * Note: the returned task is suitable for running in a background thread,
      * but is not started automatically. Clients are responsible for running the
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/JLnkParserException.java b/Core/src/org/sleuthkit/autopsy/coreutils/JLnkParserException.java
index de215a56c22ed21ef7c8460238f342b8198e3176..b4cfc0631811db2d7791e0fc7a03b41e8ea17851 100755
--- a/Core/src/org/sleuthkit/autopsy/coreutils/JLnkParserException.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/JLnkParserException.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2012 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,18 +18,8 @@
  */
 package org.sleuthkit.autopsy.coreutils;
 
-/**
- *
- * @author jwallace
- */
 public class JLnkParserException extends Exception {
 
-    /**
-     * Constructs an instance of <code>JLnkParserException</code> caused by the
-     * given exception.
-     *
-     * @param msg the detail message.
-     */
     public JLnkParserException(Exception cause) {
         super(cause);
     }
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java b/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
index 2d16c51cbb8780b015f9b4aee91046c0e668765c..b00f4cc513488edc3b68bfb36dba5da3e31da376 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/UNCPathUtilities.java
@@ -104,7 +104,8 @@ synchronized public String mappedDriveToUNC(String inputPath) {
     /**
      * This method converts a passed in path to UNC if it is not already UNC.
      * The UNC path will end up in one of the following two forms:
-     * \\hostname\somefolder\otherfolder or \\IP_ADDRESS\somefolder\otherfolder
+     * \\\\hostname\\somefolder\\otherfolder or
+     * \\\\IP_ADDRESS\\somefolder\\otherfolder
      *
      * This is accomplished by checking the mapped drives list the operating
      * system maintains and substituting where required. If the drive of the
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java
index d8951baebc67de9952169f7a343cbc2a9da37c20..966eb919e77086f900269704e0cf1bd9559c188b 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java
@@ -72,8 +72,6 @@ public static Document createDocument() throws ParserConfigurationException {
      * Loads an XML document into a WC3 DOM and validates it using a schema
      * packaged as a class resource.
      *
-     * @param <T>                The name of the class associated with the
-     *                           resource.
      * @param docPath            The full path to the XML document.
      * @param clazz              The class associated with the schema resource.
      * @param schemaResourceName The name of the schema resource.
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
index 3e999f1e469d696f5d680037ce54b2e0f3a469b7..f2babbc88e23e961c58a57472415ff92c43beb38 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
@@ -22,6 +22,10 @@
 import org.openide.nodes.Children.Keys;
 import org.openide.nodes.Node;
 import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters;
+import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles;
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts.AccountsRootNode;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.DerivedFile;
 import org.sleuthkit.datamodel.Directory;
@@ -190,6 +194,11 @@ public AbstractNode visit(Reports reportsItem) {
             return new Reports.ReportsListNode();
         }
 
+        @Override
+        public AbstractNode visit(Accounts accountsItem) {
+            return accountsItem.new AccountsRootNode();
+        }
+
         @Override
         protected AbstractNode defaultVisit(AutopsyVisitableItem di) {
             throw new UnsupportedOperationException(
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
index d25fe6e20378b05efb8cc74fce3b140acad66c1d..d673acb71e065f35026a1d62d6a567db53f4dcda 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011-2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2016 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.
@@ -18,12 +18,16 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
+import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters;
+import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles;
+
 /**
  * This visitor goes over the AutopsyVisitableItems, which are currently the
  * nodes in the tree that are structural and not nodes that are from
  * Sleuthkit-based data model objects.
  */
-interface AutopsyItemVisitor<T> {
+public interface AutopsyItemVisitor<T> {
 
     T visit(DataSources i);
 
@@ -65,6 +69,8 @@ interface AutopsyItemVisitor<T> {
 
     T visit(Reports reportsItem);
 
+    T visit(Accounts accountsItem);
+
     static abstract public class Default<T> implements AutopsyItemVisitor<T> {
 
         protected abstract T defaultVisit(AutopsyVisitableItem ec);
@@ -168,5 +174,10 @@ public T visit(Results r) {
         public T visit(Reports reportsItem) {
             return defaultVisit(reportsItem);
         }
+
+        @Override
+        public T visit(Accounts accountsItem) {
+            return defaultVisit(accountsItem);
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java
index a8efccb494982c8660e0b23bdff158f5bd102a55..90359bf0554fe311c101ac7d0fa596ddb6ee9f81 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java
@@ -17,12 +17,13 @@
  * limitations under the License.
  */
 package org.sleuthkit.autopsy.datamodel;
+;
 
 /**
  * AutopsyVisitableItems are the nodes in the directory tree that are for
  * structure only. They are not associated with content objects.
  */
-interface AutopsyVisitableItem {
+public interface AutopsyVisitableItem {
 
     /**
      * visitor pattern support
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
index a1ca0a55c5d4d8b393ea4cba58d4392a7f674ea6..788d6969ecfcf047bbcacf752d66ec7a0498d92e 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
@@ -140,7 +140,6 @@ public Action[] getActions(boolean context) {
         //if this artifact has associated content, add the action to view the content in the timeline
         AbstractFile file = getLookup().lookup(AbstractFile.class);
         if (null != file) {
-            
             actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file));
         }
 
@@ -186,13 +185,13 @@ protected Sheet createSheet() {
             ss = Sheet.createPropertiesSet();
             s.put(ss);
         }
-        final String NO_DESCR = NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.noDesc.text");
+        final String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text");
 
         Map<String, Object> map = new LinkedHashMap<>();
         fillPropertyMap(map, artifact);
 
-        ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.name"),
-                NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.displayName"),
+        ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"),
+                NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"),
                 NO_DESCR,
                 this.getDisplayName()));
 
@@ -223,13 +222,13 @@ protected Sheet createSheet() {
                     actualMimeType = ""; //NON-NLS
                 }
             }
-            ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.name"),
-                    NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.displayName"),
+            ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"),
+                    NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"),
                     NO_DESCR,
                     ext));
             ss.put(new NodeProperty<>(
-                    NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.name"),
-                    NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.displayName"),
+                    NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"),
+                    NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"),
                     NO_DESCR,
                     actualMimeType));
         }
@@ -244,32 +243,32 @@ protected Sheet createSheet() {
 
             if (sourcePath.isEmpty() == false) {
                 ss.put(new NodeProperty<>(
-                        NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.name"),
-                        NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.displayName"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"),
                         NO_DESCR,
                         sourcePath));
             }
 
             if (Arrays.asList(SHOW_FILE_METADATA).contains(artifactTypeId)) {
                 AbstractFile file = associated instanceof AbstractFile ? (AbstractFile) associated : null;
-                ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"),
-                        NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"),
+                ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"),
                         "",
                         file != null ? ContentUtils.getStringTime(file.getMtime(), file) : ""));
-                ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"),
-                        NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"),
+                ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"),
                         "",
                         file != null ? ContentUtils.getStringTime(file.getCtime(), file) : ""));
-                ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"),
-                        NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"),
+                ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"),
                         "",
                         file != null ? ContentUtils.getStringTime(file.getAtime(), file) : ""));
-                ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"),
-                        NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"),
+                ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"),
                         "",
                         file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : ""));
-                ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"),
-                        NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"),
+                ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"),
                         "",
                         associated.getSize()));
             }
@@ -288,8 +287,8 @@ protected Sheet createSheet() {
 
             if (dataSourceStr.isEmpty() == false) {
                 ss.put(new NodeProperty<>(
-                        NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.name"),
-                        NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.displayName"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"),
+                        NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"),
                         NO_DESCR,
                         dataSourceStr));
             }
@@ -410,8 +409,6 @@ private static Content getAssociatedContent(BlackboardArtifact artifact) {
                 NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.getAssocCont.exception.msg"));
     }
 
-    
-
     private static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact, Content content) {
         if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
             return null;
@@ -437,12 +434,7 @@ private static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact,
             }
             if (keyword != null) {
                 boolean isRegexp = StringUtils.isNotBlank(regexp);
-                String origQuery;
-                if (isRegexp) {
-                    origQuery = regexp;
-                } else {
-                    origQuery = keyword;
-                }
+                String origQuery = isRegexp ? regexp : keyword;
                 return highlightFactory.createInstance(objectId, keyword, isRegexp, origQuery);
             }
         } catch (TskCoreException ex) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
index ffe07974100af2b1630e93de9137ec61958e1f35..3c24e022839d24ec9877abdf76488d38644c2776 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
@@ -118,17 +118,6 @@ FileSize.createSheet.filterType.displayName=Filter Type
 FileSize.createSheet.filterType.desc=no description
 FileSize.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0}
 FileTypeChildren.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0}
-FileTypeExtensionFilters.tskImgFilter.text=Images
-FileTypeExtensionFilters.tskVideoFilter.text=Videos
-FileTypeExtensionFilters.tskAudioFilter.text=Audio
-FileTypeExtensionFilters.tskArchiveFilter.text=Archives
-FileTypeExtensionFilters.tskDocumentFilter.text=Documents
-FileTypeExtensionFilters.tskExecFilter.text=Executable
-FileTypeExtensionFilters.autDocHtmlFilter.text=HTML
-FileTypeExtensionFilters.autDocOfficeFilter.text=Office
-FileTypeExtensionFilters.autoDocPdfFilter.text=PDF
-FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text
-FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text
 FileTypeNode.createSheet.filterType.name=Filter Type
 FileTypeNode.createSheet.filterType.displayName=Filter Type
 FileTypeNode.createSheet.filterType.desc=no description
@@ -233,10 +222,6 @@ ReportNode.reportNameProperty.name=Report Name
 ReportNode.reportNameProperty.displayName=Report Name
 ReportNode.reportNameProperty.desc=Name of the report
 ReportsListNode.displayName=Reports
-ResultsNode.name.text=Results
-ResultsNode.createSheet.name.name=Name
-ResultsNode.createSheet.name.displayName=Name
-ResultsNode.createSheet.name.desc=no description
 TagNameNode.namePlusTags.text={0} Tags
 TagNameNode.contentTagTypeNodeKey.text=Content Tags
 TagNameNode.bbArtTagTypeNodeKey.text=Result Tags
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java
new file mode 100644
index 0000000000000000000000000000000000000000..300eb118dc586ad77bd3961188c719e3bd253f56
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java
@@ -0,0 +1,171 @@
+package org.sleuthkit.autopsy.datamodel;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeMap;
+import com.google.common.collect.TreeRangeMap;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.autopsy.datamodel.accounts.BINRange;
+
+public class CreditCards {
+
+    //Interface for objects that provide details about one or more BINs.
+    static public interface BankIdentificationNumber {
+
+        /**
+         * Get the city of the issuer.
+         *
+         * @return the city of the issuer.
+         */
+        Optional<String> getBankCity();
+
+        /**
+         * Get the name of the issuer.
+         *
+         * @return the name of the issuer.
+         */
+        Optional<String> getBankName();
+
+        /**
+         * Get the phone number of the issuer.
+         *
+         * @return the phone number of the issuer.
+         */
+        Optional<String> getBankPhoneNumber();
+
+        /**
+         * Get the URL of the issuer.
+         *
+         * @return the URL of the issuer.
+         */
+        Optional<String> getBankURL();
+
+        /**
+         * Get the brand of this BIN range.
+         *
+         * @return the brand of this BIN range.
+         */
+        Optional<String> getBrand();
+
+        /**
+         * Get the type of card (credit vs debit) for this BIN range.
+         *
+         * @return the type of cards in this BIN range.
+         */
+        Optional<String> getCardType();
+
+        /**
+         * Get the country of the issuer.
+         *
+         * @return the country of the issuer.
+         */
+        Optional<String> getCountry();
+
+        /**
+         * Get the length of account numbers in this BIN range.
+         *
+         * NOTE: the length is currently unused, and not in the data file for
+         * any ranges. It could be quite helpfull for validation...
+         *
+         * @return the length of account numbers in this BIN range. Or an empty
+         *         Optional if the length is unknown.
+         *
+         */
+        Optional<Integer> getNumberLength();
+
+        /**
+         * Get the scheme this BIN range uses to amex,visa,mastercard, etc
+         *
+         * @return the scheme this BIN range uses.
+         */
+        Optional<String> getScheme();
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(CreditCards.class.getName());
+    /**
+     * Range Map from a (ranges of) BINs to data model object with details of
+     * the BIN, ie, bank name, phone, url, visa/amex/mastercard/...,
+     */
+    @GuardedBy("CreditCards.class")
+    private final static RangeMap<Integer, BINRange> binRanges = TreeRangeMap.create();
+
+    /**
+     * Flag for if we have loaded the BINs from the file already.
+     */
+    @GuardedBy("CreditCards.class")
+    private static boolean binsLoaded = false;
+
+    /**
+     * Load the BIN range information from disk. If the map has already been
+     * initialized, don't load again.
+     */
+    synchronized private static void loadBINRanges() {
+        if (binsLoaded == false) {
+            try {
+                InputStreamReader in = new InputStreamReader(CreditCards.class.getResourceAsStream("ranges.csv")); //NON-NLS
+                CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
+
+                //parse each row and add to range map
+                for (CSVRecord record : rangesParser) {
+
+                    /**
+                     * Because ranges.csv allows both 6 and (the newer) 8 digit
+                     * BINs, but we need a consistent length for the range map,
+                     * we pad all the numbers out to 8 digits
+                     */
+                    String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS
+
+                    //if there is no end listed, use start, since ranges will be closed.
+                    String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS
+                    end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS
+
+                    final String numberLength = record.get("number_length"); //NON-NLS
+
+                    try {
+                        BINRange binRange = new BINRange(Integer.parseInt(start),
+                                Integer.parseInt(end),
+                                StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength),
+                                record.get("scheme"), //NON-NLS
+                                record.get("brand"), //NON-NLS
+                                record.get("type"), //NON-NLS
+                                record.get("country"), //NON-NLS
+                                record.get("bank_name"), //NON-NLS
+                                record.get("bank_url"), //NON-NLS
+                                record.get("bank_phone"), //NON-NLS
+                                record.get("bank_city")); //NON-NLS
+
+                        binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), binRange);
+
+                    } catch (NumberFormatException numberFormatException) {
+                        LOGGER.log(Level.WARNING, "Failed to parse BIN range: " + record.toString(), numberFormatException); //NON-NLS
+                    }
+                    binsLoaded = true;
+                }
+            } catch (IOException ex) {
+                LOGGER.log(Level.WARNING, "Failed to load BIN ranges form ranges.csv", ex); //NON-NLS
+                MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information.  Accounts will not have their BINs identified.");
+            }
+        }
+    }
+
+    /**
+     * Get an BINInfo object with details about the given BIN
+     *
+     * @param bin the BIN to get details of.
+     *
+     * @return
+     */
+    synchronized static public BankIdentificationNumber getBINInfo(int bin) {
+        loadBINRanges();
+        return binRanges.get(bin);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
index fb496b5be30aa058db0f29df67ed4fb41c286f7a..e300ace13434dfd6e51b682f75a61ee687c2ea1f 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011 - 2015 Basis Technology Corp.
+ *
+ * Copyright 2011 - 2016 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.
@@ -22,6 +22,7 @@
 import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode;
 import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode;
 import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode;
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
 
 /**
  * Visitor pattern that goes over all nodes in the directory tree. This includes
@@ -122,6 +123,23 @@ public interface DisplayableItemNodeVisitor<T> {
 
     T visit(Reports.ReportNode reportNode);
 
+    /*
+     * Accounts
+     */
+    T visit(Accounts.AccountsRootNode accountRootNode);
+
+    T visit(Accounts.CreditCardNumberAccountTypeNode accountTypeNode);
+
+    T visit(Accounts.ByBINNode byArtifactNode);
+
+    T visit(Accounts.ByFileNode byFileNode);
+
+    T visit(Accounts.FileWithCCNNode byFileNode);
+
+    T visit(Accounts.BINNode binNode);
+
+    T visit(Accounts.DefaultAccountTypeNode node);
+
     /**
      * Visitor with an implementable default behavior for all types. Override
      * specific visit types to not use the default behavior.
@@ -333,5 +351,39 @@ public T visit(Reports.ReportsListNode node) {
         public T visit(Reports.ReportNode node) {
             return defaultVisit(node);
         }
+
+        @Override
+        public T visit(Accounts.CreditCardNumberAccountTypeNode node) {
+            return defaultVisit(node);
+        }
+
+        @Override
+        public T visit(Accounts.AccountsRootNode node) {
+            return defaultVisit(node);
+        }
+
+        @Override
+        public T visit(Accounts.ByBINNode node) {
+            return defaultVisit(node);
+        }
+
+        @Override
+        public T visit(Accounts.ByFileNode node) {
+            return defaultVisit(node);
+        }
+
+        @Override
+        public T visit(Accounts.FileWithCCNNode node) {
+            return defaultVisit(node);
+        }
+
+        @Override
+        public T visit(Accounts.BINNode node) {
+            return defaultVisit(node);
+        }
+        @Override
+        public T visit(Accounts.DefaultAccountTypeNode node) {
+            return defaultVisit(node);
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
index 6aac72300a53f82d83b14840e874989529ba0bb2..4a420f926d9b5685a22ca658e1c0e1f71ab8cf95 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011-2015 Basis Technology Corp.
+ *
+ * Copyright 2011-2016 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.
@@ -37,27 +37,13 @@
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
 import org.sleuthkit.datamodel.BlackboardArtifact;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CALENDAR_ENTRY;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
-import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
 import org.sleuthkit.datamodel.SleuthkitCase;
 import org.sleuthkit.datamodel.TskCoreException;
 import org.sleuthkit.datamodel.TskException;
@@ -209,18 +195,13 @@ public TypeFactory() {
             super();
 
             // these are shown in other parts of the UI tree
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO));
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG));
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT));
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT));
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT));
-            doNotShow.add(new BlackboardArtifact.Type(
-                    BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_GEN_INFO));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_EMAIL_MSG));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_HASHSET_HIT));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_KEYWORD_HIT));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT));
+            doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT));
         }
 
         private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
@@ -295,11 +276,11 @@ protected boolean createKeys(List<BlackboardArtifact.Type> list) {
                     types.removeAll(doNotShow);
                     Collections.sort(types,
                             new Comparator<BlackboardArtifact.Type>() {
-                                @Override
-                                public int compare(BlackboardArtifact.Type a, BlackboardArtifact.Type b) {
-                                    return a.getDisplayName().compareTo(b.getDisplayName());
-                                }
-                            });
+                        @Override
+                        public int compare(BlackboardArtifact.Type a, BlackboardArtifact.Type b) {
+                            return a.getDisplayName().compareTo(b.getDisplayName());
+                        }
+                    });
                     list.addAll(types);
 
                     // the create node method will get called only for new types
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java
index e1b7553abac29314e44c56a782577c95ed5a1d13..5fcbdd478b0186ef38bfa5376cca8cdb2c1b7b2b 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters;
 import java.util.List;
 import java.util.Observable;
 import java.util.Observer;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java
index 3bbbf0b63ce60828096c01e896c221eb7b298eea..1e199334b543d0370f3a619927ad99e790fc82ca 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.Arrays;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java
index 391a49f757b861ba8ed777f8a5bc84b18554660b..6c39d582dd35d66c1e4efb9b93bbf14171a3a18c 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java
index c6c9c72471488a54224d1119fb328bfce32abd82..21141660746cdd08fc1d8dc317d26fcde6a40b69 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java
@@ -28,7 +28,7 @@
 import org.openide.nodes.AbstractNode;
 import org.openide.nodes.ChildFactory;
 import org.openide.nodes.Node;
-import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter;
+import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles.RecentFilesFilter;
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.ContentVisitor;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java
index e9d8fe6b6334281061659bcfcae1e6b845ebb7ba..e5eea61c96c91b778e36caf007cfe77d1795ff02 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java
@@ -25,7 +25,7 @@
 import org.openide.nodes.Children;
 import org.openide.nodes.Sheet;
 import org.openide.util.lookup.Lookups;
-import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter;
+import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles.RecentFilesFilter;
 import org.sleuthkit.datamodel.SleuthkitCase;
 
 /**
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java
index 737a93109746e042a71679da1b8a36f5dddf03bb..ebf71a872d95ed0dad8ef6fa71c67293fbb229c0 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011-2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2016 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.
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
 import java.util.Arrays;
 import org.openide.nodes.Sheet;
 import org.openide.util.NbBundle;
@@ -25,18 +26,21 @@
 import org.sleuthkit.datamodel.SleuthkitCase;
 
 /**
- * Node for the results view
+ * Node for the results section of the tree.
  */
 public class ResultsNode extends DisplayableItemNode {
 
-    public static final String NAME = NbBundle.getMessage(ResultsNode.class, "ResultsNode.name.text");
+    @NbBundle.Messages("ResultsNode.name.text=Results")
+    public static final String NAME = Bundle.ResultsNode_name_text();
 
     public ResultsNode(SleuthkitCase sleuthkitCase) {
-        super(new RootContentChildren(Arrays.asList(new ExtractedContent(sleuthkitCase),
+        super(new RootContentChildren(Arrays.asList(
+                new ExtractedContent(sleuthkitCase),
                 new KeywordHits(sleuthkitCase),
                 new HashsetHits(sleuthkitCase),
                 new EmailExtracted(sleuthkitCase),
-                new InterestingHits(sleuthkitCase)
+                new InterestingHits(sleuthkitCase),
+                new Accounts(sleuthkitCase)
         )), Lookups.singleton(NAME));
         setName(NAME);
         setDisplayName(NAME);
@@ -54,6 +58,10 @@ public <T> T accept(DisplayableItemNodeVisitor<T> v) {
     }
 
     @Override
+    @NbBundle.Messages({
+        "ResultsNode.createSheet.name.name=Name",
+        "ResultsNode.createSheet.name.displayName=Name",
+        "ResultsNode.createSheet.name.desc=no description"})
     protected Sheet createSheet() {
         Sheet s = super.createSheet();
         Sheet.Set ss = s.get(Sheet.PROPERTIES);
@@ -62,10 +70,11 @@ protected Sheet createSheet() {
             s.put(ss);
         }
 
-        ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ResultsNode.createSheet.name.name"),
-                NbBundle.getMessage(this.getClass(), "ResultsNode.createSheet.name.displayName"),
-                NbBundle.getMessage(this.getClass(), "ResultsNode.createSheet.name.desc"),
-                NAME));
+        ss.put(new NodeProperty<>(Bundle.ResultsNode_createSheet_name_name(),
+                Bundle.ResultsNode_createSheet_name_displayName(),
+                Bundle.ResultsNode_createSheet_name_desc(),
+                NAME
+        ));
         return s;
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java
index 491959437817ec7b49c0bfd9f7adf6ee2c75645e..89a6ac170fccb627b58b25b9489a009a565beea0 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java
@@ -18,6 +18,7 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters;
 import java.util.Arrays;
 import org.openide.nodes.Sheet;
 import org.openide.util.NbBundle;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java
new file mode 100644
index 0000000000000000000000000000000000000000..64c60e542dac59aac955c81a0949af09b0fb1b0d
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java
@@ -0,0 +1,1427 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2016 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.datamodel.accounts;
+
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeMap;
+import com.google.common.collect.TreeRangeMap;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import java.awt.event.ActionEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.nodes.NodeNotFoundException;
+import org.openide.nodes.NodeOp;
+import org.openide.nodes.Sheet;
+import org.openide.util.NbBundle;
+import org.openide.util.Utilities;
+import org.openide.util.lookup.Lookups;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
+import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor;
+import org.sleuthkit.autopsy.datamodel.AutopsyVisitableItem;
+import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
+import org.sleuthkit.autopsy.datamodel.CreditCards;
+import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
+import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
+import org.sleuthkit.autopsy.datamodel.NodeProperty;
+import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.TskData.DbType;
+
+/**
+ * AutopsyVisitableItem for the Accounts section of the tree. All nodes,
+ * factories, and custom key class related to accounts are inner classes.
+ */
+final public class Accounts implements AutopsyVisitableItem {
+
+    private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName());
+
+    @NbBundle.Messages("AccountsRootNode.name=Accounts")
+    final public static String NAME = Bundle.AccountsRootNode_name();
+
+    private SleuthkitCase skCase;
+    private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus");
+
+    /**
+     * Should rejected accounts be shown in the accounts section of the tree.
+     */
+    private boolean showRejected = false;
+
+    private final RejectAccounts rejectActionInstance;
+    private final ApproveAccounts approveActionInstance;
+
+    /**
+     * Constructor
+     *
+     * @param skCase The SleuthkitCase object to use for db queries.
+     */
+    public Accounts(SleuthkitCase skCase) {
+        this.skCase = skCase;
+
+        this.rejectActionInstance = new RejectAccounts();
+        this.approveActionInstance = new ApproveAccounts();
+    }
+
+    @Override
+    public <T> T accept(AutopsyItemVisitor<T> v) {
+        return v.visit(this);
+    }
+
+    /**
+     * Get the clause that should be used in order to (not) filter out rejected
+     * results from db queries.
+     *
+     * @return A clause that will or will not filter out rejected artifacts
+     *         based on the state of showRejected.
+     */
+    private String getRejectedArtifactFilterClause() {
+        return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
+    }
+
+    /**
+     * Gets a new Action that when invoked toggles showing rejected artifacts on
+     * or off.
+     *
+     * @return An Action that will toggle whether rejected artifacts are shown
+     *         in the tree rooted by this Accounts instance.
+     */
+    public Action newToggleShowRejectedAction() {
+        return new ToggleShowRejected();
+    }
+
+    /**
+     * Base class for children that are also observers of the reviewStatusBus.
+     * It
+     *
+     * @param <X> The type of keys used by this factory.
+     */
+    private abstract class ObservingChildren<X> extends Children.Keys<X> {
+
+        /**
+         * Create of keys used by this Children object to represent the child
+         * nodes.
+         */
+        abstract protected Collection<X> createKeys();
+
+        /**
+         * Refresh the keys for this Children
+         */
+        void refreshKeys() {
+            setKeys(createKeys());
+        }
+
+        /**
+         * Handle a ReviewStatusChangeEvent
+         *
+         * @param event the ReviewStatusChangeEvent to handle.
+         */
+        @Subscribe
+        abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
+
+        @Subscribe
+        abstract void handleDataAdded(ModuleDataEvent event);
+
+        @Override
+        protected void removeNotify() {
+            super.removeNotify();
+            reviewStatusBus.unregister(ObservingChildren.this);
+        }
+
+        @Override
+        protected void addNotify() {
+            super.addNotify();
+            refreshKeys();
+            reviewStatusBus.register(ObservingChildren.this);
+        }
+    }
+
+    /**
+     * Top-level node for the accounts tree
+     */
+    @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"})
+    final public class AccountsRootNode extends DisplayableItemNode {
+
+        /**
+         * Creates child nodes for each account type in the db.
+         */
+        final private class AccountTypeFactory extends ObservingChildren<String> {
+
+            /*
+             * The pcl is in this class because it has the easiest mechanisms to
+             * add and remove itself during its life cycles.
+             */
+            private final PropertyChangeListener pcl = new PropertyChangeListener() {
+                @Override
+                public void propertyChange(PropertyChangeEvent evt) {
+                    String eventType = evt.getPropertyName();
+                    if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
+                        /**
+                         * Checking for a current case is a stop gap measure
+                         * until a different way of handling the closing of
+                         * cases is worked out. Currently, remote events may be
+                         * received for a case that is already closed.
+                         */
+                        try {
+                            Case.getCurrentCase();
+                            /**
+                             * Even with the check above, it is still possible
+                             * that the case will be closed in a different
+                             * thread before this code executes. If that
+                             * happens, it is possible for the event to have a
+                             * null oldValue.
+                             */
+                            ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
+                            if (null != eventData
+                                    && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
+                                reviewStatusBus.post(eventData);
+                            }
+                        } catch (IllegalStateException notUsed) {
+                            // Case is closed, do nothing.
+                        }
+                    } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
+                            || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
+                        /**
+                         * Checking for a current case is a stop gap measure
+                         * until a different way of handling the closing of
+                         * cases is worked out. Currently, remote events may be
+                         * received for a case that is already closed.
+                         */
+                        try {
+                            Case.getCurrentCase();
+                            refreshKeys();
+                        } catch (IllegalStateException notUsed) {
+                            // Case is closed, do nothing.
+                        }
+                    } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
+                        // case was closed. Remove listeners so that we don't get called with a stale case handle
+                        if (evt.getNewValue() == null) {
+                            removeNotify();
+                            skCase = null;
+                        }
+                    }
+                }
+            };
+
+            @Subscribe
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+                refreshKeys();
+            }
+
+            @Subscribe
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+                refreshKeys();
+            }
+
+            @Override
+            protected List<String> createKeys() {
+                List<String> list = new ArrayList<>();
+                try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(
+                        "SELECT DISTINCT blackboard_attributes.value_text as account_type "
+                        + " FROM blackboard_attributes "
+                        + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID());
+                        ResultSet resultSet = executeQuery.getResultSet()) {
+                    while (resultSet.next()) {
+                        String accountType = resultSet.getString("account_type");
+                        list.add(accountType);
+                    }
+                } catch (TskCoreException | SQLException ex) {
+                    LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
+                }
+                return list;
+            }
+
+            @Override
+            protected Node[] createNodes(String key) {
+                try {
+                    Account.Type accountType = Account.Type.valueOf(key);
+                    switch (accountType) {
+                        case CREDIT_CARD:
+                            return new Node[]{new CreditCardNumberAccountTypeNode()};
+                        default:
+                            return new Node[]{new DefaultAccountTypeNode(key)};
+                    }
+                } catch (IllegalArgumentException ex) {
+                    LOGGER.log(Level.WARNING, "Unknown account type: {0}", key);
+                    //Flesh out what happens with other account types here.
+                    return new Node[]{new DefaultAccountTypeNode(key)};
+                }
+            }
+
+            @Override
+            protected void removeNotify() {
+                IngestManager.getInstance().removeIngestJobEventListener(pcl);
+                IngestManager.getInstance().removeIngestModuleEventListener(pcl);
+                Case.removePropertyChangeListener(pcl);
+                super.removeNotify();
+            }
+
+            @Override
+            protected void addNotify() {
+                IngestManager.getInstance().addIngestJobEventListener(pcl);
+                IngestManager.getInstance().addIngestModuleEventListener(pcl);
+                Case.addPropertyChangeListener(pcl);
+                super.addNotify();
+                refreshKeys();
+            }
+
+        }
+
+        public AccountsRootNode() {
+            super(Children.LEAF, Lookups.singleton(Accounts.this));
+            setChildren(Children.createLazy(AccountTypeFactory::new));
+            setName(Accounts.NAME);
+            setDisplayName(Bundle.Accounts_RootNode_displayName());
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png");    //NON-NLS
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return false;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+    }
+
+    /**
+     * Default Node class for unknown account types and account types that have
+     * no special behavior.
+     */
+    final public class DefaultAccountTypeNode extends DisplayableItemNode {
+
+        private final String accountTypeName;
+
+        final private class DefaultAccountFactory extends ObservingChildren<Long> {
+
+            private DefaultAccountFactory() {
+            }
+
+            @Override
+            protected Collection<Long> createKeys() {
+                List<Long> list = new ArrayList<>();
+                String query
+                        = "SELECT blackboard_artifacts.artifact_id " //NON-NLS
+                        + " FROM blackboard_artifacts " //NON-NLS
+                        + "      JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
+                        + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                        + "     AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
+                        + "     AND blackboard_attributes.value_text = '" + accountTypeName + "'" //NON-NLS
+                        + getRejectedArtifactFilterClause(); //NON-NLS
+                try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
+                        ResultSet rs = results.getResultSet();) {
+                    while (rs.next()) {
+                        list.add(rs.getLong("artifact_id")); //NON-NLS
+                    }
+                } catch (TskCoreException | SQLException ex) {
+                    LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
+                }
+                return list;
+            }
+
+            @Override
+            protected Node[] createNodes(Long t) {
+                try {
+                    return new Node[]{new BlackboardArtifactNode(skCase.getBlackboardArtifact(t))};
+                } catch (TskCoreException ex) {
+                    LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
+                    return new Node[0];
+                }
+            }
+
+            @Subscribe
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+                refreshKeys();
+            }
+
+            @Subscribe
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+                refreshKeys();
+            }
+        }
+
+        private DefaultAccountTypeNode(String accountTypeName) {
+            super(Children.LEAF);
+            this.accountTypeName = accountTypeName;
+            setChildren(Children.createLazy(DefaultAccountFactory::new));
+            setName(accountTypeName);
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png");   //NON-NLS
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return true;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+    }
+
+    /**
+     * Enum for the children under the credit card AccountTypeNode.
+     */
+    private enum CreditCardViewMode {
+        BY_FILE,
+        BY_BIN;
+    }
+
+    /**
+     * Node for the Credit Card account type. *
+     */
+    final public class CreditCardNumberAccountTypeNode extends DisplayableItemNode {
+
+        /**
+         * ChildFactory that makes nodes for the different account organizations
+         * (by file, by BIN)
+         */
+        final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
+
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+            }
+
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+            }
+
+            /**
+             *
+             */
+            @Override
+            protected List<CreditCardViewMode> createKeys() {
+                return Arrays.asList(CreditCardViewMode.values());
+
+            }
+
+            @Override
+            protected Node[] createNodes(CreditCardViewMode key) {
+                switch (key) {
+                    case BY_BIN:
+                        return new Node[]{new ByBINNode()};
+                    case BY_FILE:
+                        return new Node[]{new ByFileNode()};
+                    default:
+                        return new Node[0];
+                }
+            }
+        }
+
+        private CreditCardNumberAccountTypeNode() {
+            super(Children.LEAF);
+            setChildren(new ViewModeFactory());
+            setName(Account.Type.CREDIT_CARD.getDisplayName());
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png");   //NON-NLS
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return false;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+    }
+
+    /**
+     * Node that is the root of the "by file" accounts tree. Its children are
+     * FileWithCCNNodes.
+     */
+    final public class ByFileNode extends DisplayableItemNode {
+
+        /**
+         * Factory for the children of the ByFiles Node.
+         */
+        final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
+
+            @Subscribe
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+                refreshKeys();
+            }
+
+            @Subscribe
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+                refreshKeys();
+            }
+
+            @Override
+            protected List<FileWithCCN> createKeys() {
+                List<FileWithCCN> list = new ArrayList<>();
+                String query
+                        = "SELECT blackboard_artifacts.obj_id," //NON-NLS
+                        + "      solr_attribute.value_text AS solr_document_id, "; //NON-NLS
+                if(skCase.getDatabaseType().equals(DbType.POSTGRESQL)){
+                    query += "      string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
+                           + "      string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
+                } else {
+                    query += "      GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
+                           + "      GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
+                }
+                query +=  "      COUNT( blackboard_artifacts.artifact_id) AS hits  " //NON-NLS
+                        + " FROM blackboard_artifacts " //NON-NLS
+                        + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
+                        + "                                AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
+                        + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
+                        + "                                AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
+                        + "                                AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS
+                        + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                        + getRejectedArtifactFilterClause()
+                        + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
+                        + " ORDER BY hits DESC ";  //NON-NLS
+                try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
+                        ResultSet rs = results.getResultSet();) {
+                    while (rs.next()) {
+                        list.add(new FileWithCCN(
+                                rs.getLong("obj_id"), //NON-NLS
+                                rs.getString("solr_document_id"), //NON-NLS
+                                unGroupConcat(rs.getString("artifact_IDs"), Long::valueOf), //NON-NLS
+                                rs.getLong("hits"), //NON-NLS
+                                new HashSet<>(unGroupConcat(rs.getString("review_status_ids"), id -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(id))))));  //NON-NLS
+                    }
+                } catch (TskCoreException | SQLException ex) {
+                    LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
+
+                }
+                return list;
+            }
+
+            @Override
+            protected Node[] createNodes(FileWithCCN key) {
+                //add all account artifacts for the file and the file itself to the lookup
+                try {
+                    List<Object> lookupContents = new ArrayList<>();
+                    for (long artId : key.artifactIDs) {
+                        lookupContents.add(skCase.getBlackboardArtifact(artId));
+                    }
+                    AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID());
+                    lookupContents.add(abstractFileById);
+                    return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
+                } catch (TskCoreException ex) {
+                    LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
+                    return new Node[0];
+                }
+            }
+        }
+
+        private ByFileNode() {
+            super(Children.LEAF);
+            setChildren(Children.createLazy(FileWithCCNFactory::new));
+            setName("By File");   //NON-NLS
+            updateDisplayName();
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png");   //NON-NLS
+            reviewStatusBus.register(this);
+        }
+
+        @NbBundle.Messages({
+            "# {0} - number of children",
+            "Accounts.ByFileNode.displayName=By File ({0})"})
+        private void updateDisplayName() {
+            String query
+                    = "SELECT count(*) FROM ( SELECT count(*) AS documents "
+                    + " FROM blackboard_artifacts " //NON-NLS
+                    + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
+                    + "                                AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
+                    + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
+                    + "                                AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
+                    + "                                AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS
+                    + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                    + getRejectedArtifactFilterClause()
+                    + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
+            try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
+                    ResultSet rs = results.getResultSet();) {
+                while (rs.next()) {
+                    if(skCase.getDatabaseType().equals(DbType.POSTGRESQL)){
+                        setDisplayName(Bundle.Accounts_ByFileNode_displayName(rs.getLong("count")));
+                    } else {
+                        setDisplayName(Bundle.Accounts_ByFileNode_displayName(rs.getLong("count(*)")));
+                    }
+                }
+            } catch (TskCoreException | SQLException ex) {
+                LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
+
+            }
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return true;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+
+        @Subscribe
+        void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+            updateDisplayName();
+        }
+
+        @Subscribe
+        void handleDataAdded(ModuleDataEvent event) {
+            updateDisplayName();
+        }
+    }
+
+    /**
+     * Node that is the root of the "By BIN" accounts tree. Its children are
+     * BINNodes.
+     */
+    final public class ByBINNode extends DisplayableItemNode {
+
+        /**
+         * Factory that generates the children of the ByBin node.
+         */
+        final private class BINFactory extends ObservingChildren<BinResult> {
+
+            @Subscribe
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+                refreshKeys();
+            }
+
+            @Subscribe
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+                refreshKeys();
+            }
+
+            @Override
+            protected List<BinResult> createKeys() {
+                List<BinResult> list = new ArrayList<>();
+
+                RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
+
+                String query
+                        = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
+                        + "     COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
+                        + " FROM blackboard_artifacts " //NON-NLS
+                        + "      JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
+                        + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                        + "     AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
+                        + getRejectedArtifactFilterClause()
+                        + " GROUP BY BIN " //NON-NLS
+                        + " ORDER BY BIN "; //NON-NLS
+                try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) {
+                    ResultSet resultSet = results.getResultSet();
+                    //sort all te individual bins in to the ranges
+                    while (resultSet.next()) {
+                        final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
+                        long count = resultSet.getLong("count");
+
+                        BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
+                        BinResult previousResult = binRanges.get(bin);
+
+                        if (previousResult != null) {
+                            binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
+                            count += previousResult.getCount();
+                        }
+
+                        if (binRange != null) {
+                            binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
+                        } else {
+                            binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
+                        }
+                    }
+                    binRanges.asMapOfRanges().values().forEach(list::add);
+                } catch (TskCoreException | SQLException ex) {
+                    LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
+
+                }
+                return list;
+            }
+
+            @Override
+            protected Node[] createNodes(BinResult key) {
+                return new Node[]{new BINNode(key)};
+            }
+        }
+
+        private ByBINNode() {
+            super(Children.LEAF);
+            setChildren(Children.createLazy(BINFactory::new));
+            setName("By BIN");  //NON-NLS
+            updateDisplayName();
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png");   //NON-NLS
+            reviewStatusBus.register(this);
+        }
+
+        @NbBundle.Messages({
+            "# {0} - number of children",
+            "Accounts.ByBINNode.displayName=By BIN ({0})"})
+        private void updateDisplayName() {
+            String query
+                    = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
+                    + " FROM blackboard_artifacts " //NON-NLS
+                    + "      JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
+                    + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                    + "     AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
+                    + getRejectedArtifactFilterClause(); //NON-NLS
+            try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) {
+                ResultSet resultSet = results.getResultSet();
+                while (resultSet.next()) {
+                    setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
+                }
+            } catch (TskCoreException | SQLException ex) {
+                LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
+            }
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return false;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+
+        @Subscribe
+        void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+            updateDisplayName();
+        }
+
+        @Subscribe
+        void handleDataAdded(ModuleDataEvent event) {
+            updateDisplayName();
+        }
+    }
+
+    /**
+     * DataModel for a child of the ByFileNode. Represents a file(chunk) and its
+     * associated accounts.
+     */
+    @Immutable
+    final private static class FileWithCCN {
+
+        @Override
+        public int hashCode() {
+            int hash = 5;
+            hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
+            hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
+            hash = 79 * hash + Objects.hashCode(this.artifactIDs);
+            hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
+            hash = 79 * hash + Objects.hashCode(this.statuses);
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final FileWithCCN other = (FileWithCCN) obj;
+            if (this.objID != other.objID) {
+                return false;
+            }
+            if (this.hits != other.hits) {
+                return false;
+            }
+            if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
+                return false;
+            }
+            if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
+                return false;
+            }
+            if (!Objects.equals(this.statuses, other.statuses)) {
+                return false;
+            }
+            return true;
+        }
+
+        private final long objID;
+        private final String keywordSearchDocID;
+        private final List<Long> artifactIDs;
+        private final long hits;
+        private final Set<BlackboardArtifact.ReviewStatus> statuses;
+
+        private FileWithCCN(long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
+            this.objID = objID;
+            this.keywordSearchDocID = solrDocID;
+            this.artifactIDs = artifactIDs;
+            this.hits = hits;
+            this.statuses = statuses;
+        }
+
+        /**
+         * Get the object ID of the file.
+         *
+         * @return the object ID of the file.
+         */
+        public long getObjID() {
+            return objID;
+        }
+
+        /**
+         * Get the keyword search docuement id. This is used for unnalocated
+         * files to limit results to one chunk/page
+         *
+         * @return the keyword search document id.
+         */
+        public String getkeywordSearchDocID() {
+            return keywordSearchDocID;
+        }
+
+        /**
+         * Get the artifact ids of the account artifacts from this file.
+         *
+         * @return the artifact ids of the account artifacts from this file.
+         */
+        public List<Long> getArtifactIDs() {
+            return artifactIDs;
+        }
+
+        /**
+         * Get the number of account artifacts from this file.
+         *
+         * @return the number of account artifacts from this file.
+         */
+        public long getHits() {
+            return hits;
+        }
+
+        /**
+         * Get the status(s) of the account artifacts from this file.
+         *
+         * @return the status(s) of the account artifacts from this file.
+         */
+        public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
+            return statuses;
+        }
+    }
+
+    /**
+     * TODO: this was copy-pasted from timeline. Is there a single accessible
+     * place it should go?
+     *
+     *
+     * take the result of a group_concat SQLite operation and split it into a
+     * set of X using the mapper to to convert from string to X
+     *
+     * @param <X>         the type of elements to return
+     * @param groupConcat a string containing the group_concat result ( a comma
+     *                    separated list)
+     * @param mapper      a function from String to X
+     *
+     * @return a Set of X, each element mapped from one element of the original
+     *         comma delimited string
+     */
+    static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
+        return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
+                : Stream.of(groupConcat.split(",")) //NON-NLS
+                .map(mapper::apply)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Node that represents a file or chunk of an unallocated space file.
+     */
+    final public class FileWithCCNNode extends DisplayableItemNode {
+
+        private final FileWithCCN fileKey;
+        private final String fileName;
+
+        /**
+         * Constructor
+         *
+         * @param key            The FileWithCCN that backs this node.
+         * @param content        The Content object the key represents.
+         * @param lookupContents The contents of this Node's lookup. It should
+         *                       contain the content object and the account
+         *                       artifacts.
+         */
+        @NbBundle.Messages({
+            "# {0} - raw file name",
+            "# {1} - solr chunk id",
+            "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
+        private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
+            super(Children.LEAF, Lookups.fixed(lookupContents));
+            this.fileKey = key;
+            this.fileName = (key.getkeywordSearchDocID() == null)
+                    ? content.getName()
+                    : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
+            setName(fileName + key.getObjID());
+            setDisplayName(fileName);
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return true;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+
+        @Override
+        @NbBundle.Messages({
+            "Accounts.FileWithCCNNode.nameProperty.displayName=File",
+            "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
+            "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
+            "Accounts.FileWithCCNNode.noDescription=no description"})
+        protected Sheet createSheet() {
+            Sheet s = super.createSheet();
+            Sheet.Set ss = s.get(Sheet.PROPERTIES);
+            if (ss == null) {
+                ss = Sheet.createPropertiesSet();
+                s.put(ss);
+            }
+
+            ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_noDescription(),
+                    fileName));
+            ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_noDescription(),
+                    fileKey.getHits()));
+            ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_noDescription(),
+                    fileKey.getStatuses().stream()
+                    .map(BlackboardArtifact.ReviewStatus::getDisplayName)
+                    .collect(Collectors.joining(", ")))); //NON-NLS
+
+            return s;
+        }
+
+        @Override
+        public Action[] getActions(boolean context) {
+            Action[] actions = super.getActions(context);
+            ArrayList<Action> arrayList = new ArrayList<>();
+            arrayList.addAll(Arrays.asList(actions));
+            try {
+                arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false));
+            } catch (TskCoreException ex) {
+                LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
+            }
+
+            arrayList.add(approveActionInstance);
+            arrayList.add(rejectActionInstance);
+
+            return arrayList.toArray(new Action[arrayList.size()]);
+        }
+    }
+
+    final public class BINNode extends DisplayableItemNode {
+
+        /**
+         * Creates the nodes for the credit card numbers
+         */
+        final private class CreditCardNumberFactory extends ObservingChildren<Long> {
+
+            @Subscribe
+            @Override
+            void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+                refreshKeys();
+                //make sure to refresh the nodes for artifacts that changed statuses.
+                event.artifacts.stream().map(BlackboardArtifact::getArtifactID).forEach(this::refreshKey);
+            }
+
+            @Subscribe
+            @Override
+            void handleDataAdded(ModuleDataEvent event) {
+                refreshKeys();
+            }
+
+            @Override
+            protected List<Long> createKeys() {
+                List<Long> list = new ArrayList<>();
+
+                String query
+                        = "SELECT blackboard_artifacts.artifact_id " //NON-NLS
+                        + " FROM blackboard_artifacts " //NON-NLS
+                        + "      JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
+                        + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                        + "     AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
+                        + "     AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND  blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
+                        + getRejectedArtifactFilterClause()
+                        + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
+                try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
+                        ResultSet rs = results.getResultSet();) {
+                    while (rs.next()) {
+                        list.add(rs.getLong("artifact_id")); //NON-NLS
+                    }
+                } catch (TskCoreException | SQLException ex) {
+                    LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
+
+                }
+                return list;
+            }
+
+            @Override
+            protected Node[] createNodes(Long artifactID) {
+                if (skCase == null) {
+                    return new Node[0];
+                }
+
+                try {
+                    BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID);
+                    return new Node[]{new AccountArtifactNode(art)};
+                } catch (TskCoreException ex) {
+                    LOGGER.log(Level.WARNING, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex);   //NON-NLS
+                    return new Node[0];
+                }
+            }
+        }
+        private final BinResult bin;
+
+        private BINNode(BinResult bin) {
+            super(Children.LEAF);
+            setChildren(Children.createLazy(CreditCardNumberFactory::new));
+            this.bin = bin;
+            setName(getBinRangeString());
+            updateDisplayName();
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png");   //NON-NLS
+            reviewStatusBus.register(this);
+        }
+
+        @Subscribe
+        void handleReviewStatusChange(ReviewStatusChangeEvent event) {
+            updateDisplayName();
+        }
+
+        @Subscribe
+        void handleDataAdded(ModuleDataEvent event) {
+            updateDisplayName();
+        }
+
+        private void updateDisplayName() {
+            String query
+                    = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
+                    + " FROM blackboard_artifacts " //NON-NLS
+                    + "      JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
+                    + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
+                    + "     AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
+                    + "     AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND  blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
+                    + getRejectedArtifactFilterClause();
+            try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
+                    ResultSet rs = results.getResultSet();) {
+                while (rs.next()) {
+                    setDisplayName(getBinRangeString() + " (" + rs.getLong("count") + ")"); //NON-NLS
+                }
+            } catch (TskCoreException | SQLException ex) {
+                LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
+
+            }
+
+        }
+
+        private String getBinRangeString() {
+            if (bin.getBINStart() == bin.getBINEnd()) {
+                return Integer.toString(bin.getBINStart());
+            } else {
+                return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
+            }
+        }
+
+        @Override
+        public boolean isLeafTypeNode() {
+            return true;
+        }
+
+        @Override
+        public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+            return v.visit(this);
+        }
+
+        private Sheet.Set getPropertySet(Sheet s) {
+            Sheet.Set ss = s.get(Sheet.PROPERTIES);
+            if (ss == null) {
+                ss = Sheet.createPropertiesSet();
+                s.put(ss);
+            }
+            return ss;
+        }
+
+        @Override
+        @NbBundle.Messages({
+            "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
+            "Accounts.BINNode.accountsProperty.displayName=Accounts",
+            "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
+            "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
+            "Accounts.BINNode.brandProperty.displayName=Brand",
+            "Accounts.BINNode.bankProperty.displayName=Bank",
+            "Accounts.BINNode.bankCityProperty.displayName=Bank City",
+            "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
+            "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
+            "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
+            "Accounts.BINNode.noDescription=no description"})
+        protected Sheet createSheet() {
+            Sheet sheet = super.createSheet();
+            Sheet.Set properties = getPropertySet(sheet);
+
+            properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
+                    Bundle.Accounts_BINNode_binProperty_displayName(),
+                    Bundle.Accounts_BINNode_noDescription(),
+                    getBinRangeString()));
+            properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
+                    Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                    bin.getCount()));
+
+            //add optional properties if they are available
+            if (bin.hasDetails()) {
+                bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
+                        Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        cardType)));
+                bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
+                        Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        scheme)));
+                bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
+                        Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        brand)));
+                bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
+                        Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        bankName)));
+                bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
+                        Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        bankCity)));
+                bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
+                        Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        country)));
+                bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
+                        Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        phoneNumber)));
+                bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
+                        Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
+                        url)));
+            }
+            return sheet;
+        }
+    }
+
+    /**
+     * Data model item to back the BINNodes in the tree. Has the number of
+     * accounts found with the BIN.
+     */
+    @Immutable
+    final static private class BinResult implements CreditCards.BankIdentificationNumber {
+
+        @Override
+        public int hashCode() {
+            int hash = 3;
+            hash = 97 * hash + this.binEnd;
+            hash = 97 * hash + this.binStart;
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final BinResult other = (BinResult) obj;
+            if (this.binEnd != other.binEnd) {
+                return false;
+            }
+            if (this.binStart != other.binStart) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * The number of accounts with this BIN
+         */
+        private final long count;
+
+        private final BINRange binRange;
+        private final int binEnd;
+        private final int binStart;
+
+        private BinResult(long count, @Nonnull BINRange binRange) {
+            this.count = count;
+            this.binRange = binRange;
+            binStart = binRange.getBINstart();
+            binEnd = binRange.getBINend();
+        }
+
+        private BinResult(long count, int start, int end) {
+            this.count = count;
+            this.binRange = null;
+            binStart = start;
+            binEnd = end;
+        }
+
+        int getBINStart() {
+            return binStart;
+        }
+
+        int getBINEnd() {
+            return binEnd;
+        }
+
+        long getCount() {
+            return count;
+        }
+
+        boolean hasDetails() {
+            return binRange != null;
+        }
+
+        @Override
+        public Optional<Integer> getNumberLength() {
+            return binRange.getNumberLength();
+        }
+
+        @Override
+        public Optional<String> getBankCity() {
+            return binRange.getBankCity();
+        }
+
+        @Override
+        public Optional<String> getBankName() {
+            return binRange.getBankName();
+        }
+
+        @Override
+        public Optional<String> getBankPhoneNumber() {
+            return binRange.getBankPhoneNumber();
+        }
+
+        @Override
+        public Optional<String> getBankURL() {
+            return binRange.getBankURL();
+        }
+
+        @Override
+        public Optional<String> getBrand() {
+            return binRange.getBrand();
+        }
+
+        @Override
+        public Optional<String> getCardType() {
+            return binRange.getCardType();
+        }
+
+        @Override
+        public Optional<String> getCountry() {
+            return binRange.getCountry();
+        }
+
+        @Override
+        public Optional<String> getScheme() {
+            return binRange.getScheme();
+        }
+    }
+
+    final private class AccountArtifactNode extends BlackboardArtifactNode {
+
+        private final BlackboardArtifact artifact;
+
+        private AccountArtifactNode(BlackboardArtifact artifact) {
+            super(artifact, "org/sleuthkit/autopsy/images/credit-card.png");   //NON-NLS
+            this.artifact = artifact;
+            setName("" + this.artifact.getArtifactID());
+        }
+
+        @Override
+        public Action[] getActions(boolean context) {
+            List<Action> actionsList = new ArrayList<>();
+            actionsList.addAll(Arrays.asList(super.getActions(context)));
+
+            actionsList.add(approveActionInstance);
+            actionsList.add(rejectActionInstance);
+
+            return actionsList.toArray(new Action[actionsList.size()]);
+        }
+
+        @Override
+        protected Sheet createSheet() {
+            Sheet sheet = super.createSheet();
+            Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
+            if (properties == null) {
+                properties = Sheet.createPropertiesSet();
+                sheet.put(properties);
+            }
+            properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
+                    Bundle.Accounts_FileWithCCNNode_noDescription(),
+                    artifact.getReviewStatus().getDisplayName()));
+
+            return sheet;
+        }
+    }
+
+    private final class ToggleShowRejected extends AbstractAction {
+
+        @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
+        ToggleShowRejected() {
+            super(Bundle.ToggleShowRejected_name());
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            showRejected = !showRejected;
+            reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
+        }
+    }
+
+    private abstract class ReviewStatusAction extends AbstractAction {
+
+        private final BlackboardArtifact.ReviewStatus newStatus;
+
+        private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
+            super(displayName);
+            this.newStatus = newStatus;
+
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+
+            /* get paths for selected nodes to reselect after applying review
+             * status change */
+            List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
+                    .map(node -> {
+                        String[] createPath;
+                        /*
+                         * If the we are rejecting and not showing rejected
+                         * results, then the selected node, won't exist any
+                         * more, so we select the previous one in stead.
+                         */
+                        if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
+                            List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
+                            int indexOf = siblings.indexOf(node);
+                            //there is no previous for the first node, so instead we select the next one
+                            Node sibling = indexOf > 0
+                                    ? siblings.get(indexOf - 1)
+                                    : siblings.get(indexOf + 1);
+                            createPath = NodeOp.createPath(sibling, null);
+                        } else {
+                            createPath = NodeOp.createPath(node, null);
+                        }
+                        //for the reselect to work we need to strip off the first part of the path.
+                        return Arrays.copyOfRange(createPath, 1, createPath.length);
+                    }).collect(Collectors.toList());
+
+            //change status of selected artifacts
+            final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
+            artifacts.forEach(artifact -> {
+                try {
+                    skCase.setReviewStatus(artifact, newStatus);
+                } catch (TskCoreException ex) {
+                    LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
+                }
+            });
+            //post event
+            reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
+
+            final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
+            final Node rootNode = directoryListing.getRootNode();
+
+            //convert paths back to nodes
+            List<Node> toArray = new ArrayList<>();
+            selectedPaths.forEach(path -> {
+                try {
+                    toArray.add(NodeOp.findPath(rootNode, path));
+                } catch (NodeNotFoundException ex) {
+                    //just ingnore paths taht don't exist.  this is expected since we are rejecting
+                }
+            });
+            //select nodes
+            directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
+        }
+    }
+
+    final private class ApproveAccounts extends ReviewStatusAction {
+
+        @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
+        private ApproveAccounts() {
+            super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
+        }
+    }
+
+    final private class RejectAccounts extends ReviewStatusAction {
+
+        @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
+        private RejectAccounts() {
+            super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
+        }
+    }
+
+    private class ReviewStatusChangeEvent {
+
+        Collection<? extends BlackboardArtifact> artifacts;
+        BlackboardArtifact.ReviewStatus newReviewStatus;
+
+        public ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
+            this.artifacts = artifacts;
+            this.newReviewStatus = newReviewStatus;
+        }
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/BINRange.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/BINRange.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f1a33b761ffffd464085221fcab615eaee8c25c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/BINRange.java
@@ -0,0 +1,146 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2016 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.datamodel.accounts;
+
+import java.util.Optional;
+import javax.annotation.concurrent.Immutable;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.datamodel.CreditCards;
+
+/**
+ * Details of a range of Bank Identification Number(s) (BIN) used by a bank.
+ */
+@Immutable
+public class BINRange implements CreditCards.BankIdentificationNumber {
+
+    private final int BINStart; //start of BIN range, 8 digits
+    private final int BINEnd; // end (incluse ) of BIN rnage, 8 digits
+
+    private final Integer numberLength; // the length of accounts numbers with this BIN, currently unused
+
+    /**
+     * AMEX, VISA, MASTERCARD, DINERS, DISCOVER, UNIONPAY
+     */
+    private final String scheme;
+    private final String brand;
+
+    /**
+     * DEBIT, CREDIT
+     */
+    private final String cardType;
+    private final String country;
+    private final String bankName;
+    private final String bankCity;
+    private final String bankURL;
+    private final String bankPhoneNumber;
+
+    /**
+     * Constructor
+     *
+     * @param BIN_start     the first BIN in the range, must be 8 digits
+     * @param BIN_end       the last(inclusive) BIN in the range, must be 8
+     *                      digits
+     * @param number_length the length of account numbers in this BIN range
+     * @param scheme        amex/visa/mastercard/etc
+     * @param brand         the brand of this BIN range
+     * @param type          credit vs debit
+     * @param country       the country of the issuer
+     * @param bank_name     the name of the issuer
+     * @param bank_url      the url of the issuer
+     * @param bank_phone    the phone number of the issuer
+     * @param bank_city     the city of the issuer
+     */
+    public BINRange(int BIN_start, int BIN_end, Integer number_length, String scheme, String brand, String type, String country, String bank_name, String bank_url, String bank_phone, String bank_city) {
+        this.BINStart = BIN_start;
+        this.BINEnd = BIN_end;
+
+        this.numberLength = number_length;
+        this.scheme = StringUtils.defaultIfBlank(scheme, null);
+        this.brand = StringUtils.defaultIfBlank(brand, null);
+        this.cardType = StringUtils.defaultIfBlank(type, null);
+        this.country = StringUtils.defaultIfBlank(country, null);
+        this.bankName = StringUtils.defaultIfBlank(bank_name, null);
+        this.bankURL = StringUtils.defaultIfBlank(bank_url, null);
+        this.bankPhoneNumber = StringUtils.defaultIfBlank(bank_phone, null);
+        this.bankCity = StringUtils.defaultIfBlank(bank_city, null);
+    }
+
+    /**
+     * Get the first BIN in this range
+     *
+     * @return the first BIN in this range.
+     */
+    public int getBINstart() {
+        return BINStart;
+    }
+
+    /**
+     * Get the last (inclusive) BIN in this range.
+     *
+     * @return the last (inclusive) BIN in this range.
+     */
+    public int getBINend() {
+        return BINEnd;
+    }
+
+    @Override
+    public Optional<Integer> getNumberLength() {
+        return Optional.ofNullable(numberLength);
+    }
+
+    @Override
+    public Optional<String> getScheme() {
+        return Optional.ofNullable(scheme);
+    }
+
+    @Override
+    public Optional<String> getBrand() {
+        return Optional.ofNullable(brand);
+    }
+
+    @Override
+    public Optional<String> getCardType() {
+        return Optional.ofNullable(cardType);
+    }
+
+    @Override
+    public Optional<String> getCountry() {
+        return Optional.ofNullable(country);
+    }
+
+    @Override
+    public Optional<String> getBankName() {
+        return Optional.ofNullable(bankName);
+    }
+
+    @Override
+    public Optional<String> getBankURL() {
+        return Optional.ofNullable(bankURL);
+    }
+
+    @Override
+    public Optional<String> getBankPhoneNumber() {
+        return Optional.ofNullable(bankPhoneNumber);
+    }
+
+    @Override
+    public Optional<String> getBankCity() {
+        return Optional.ofNullable(bankCity);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..dc778857c514d05af4e912063c8821cb0776ccc1
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Bundle.properties
@@ -0,0 +1,12 @@
+FileTypeExtensionFilters.tskImgFilter.text=Images
+FileTypeExtensionFilters.tskVideoFilter.text=Videos
+FileTypeExtensionFilters.tskAudioFilter.text=Audio
+FileTypeExtensionFilters.tskArchiveFilter.text=Archives
+FileTypeExtensionFilters.tskDocumentFilter.text=Documents
+FileTypeExtensionFilters.tskExecFilter.text=Executable
+FileTypeExtensionFilters.autDocHtmlFilter.text=HTML
+FileTypeExtensionFilters.autDocOfficeFilter.text=Office
+FileTypeExtensionFilters.autoDocPdfFilter.text=PDF
+FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text
+FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text
+
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/FileTypeExtensionFilters.java
similarity index 89%
rename from Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java
rename to Core/src/org/sleuthkit/autopsy/datamodel/accounts/FileTypeExtensionFilters.java
index 1e2dd473dde7e047a1b9f5e490c0f817398a2b5f..80680e97cc60dcd94b38abe94db5c3531ebb0858 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/FileTypeExtensionFilters.java
@@ -16,20 +16,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.datamodel;
+package org.sleuthkit.autopsy.datamodel.accounts;
 
+import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor;
+import org.sleuthkit.autopsy.datamodel.AutopsyVisitableItem;
 import java.util.Arrays;
 import java.util.List;
-
 import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.datamodel.FileTypeExtensions;
 import org.sleuthkit.datamodel.SleuthkitCase;
 
 /**
  * Filters database results by file extension.
  */
-class FileTypeExtensionFilters implements AutopsyVisitableItem {
+public class FileTypeExtensionFilters implements AutopsyVisitableItem {
 
-    private SleuthkitCase skCase;
+    private final SleuthkitCase skCase;
 
     // root node filters
     public enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface {
@@ -53,10 +55,10 @@ public enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface {
                 NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.tskExecFilter.text"),
                 Arrays.asList(".exe", ".dll", ".bat", ".cmd", ".com")); //NON-NLS
 
-        private int id;
-        private String name;
-        private String displayName;
-        private List<String> filter;
+        private final int id;
+        private final String name;
+        private final String displayName;
+        private final List<String> filter;
 
         private RootFilter(int id, String name, String displayName, List<String> filter) {
             this.id = id;
@@ -110,10 +112,10 @@ public enum DocumentFilter implements AutopsyVisitableItem, SearchFilterInterfac
                 NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.autDocRtfFilter.text"),
                 Arrays.asList(".rtf")); //NON-NLS
 
-        private int id;
-        private String name;
-        private String displayName;
-        private List<String> filter;
+        private final int id;
+        private final String name;
+        private final String displayName;
+        private final List<String> filter;
 
         private DocumentFilter(int id, String name, String displayName, List<String> filter) {
             this.id = id;
@@ -157,10 +159,10 @@ public enum ExecutableFilter implements AutopsyVisitableItem, SearchFilterInterf
         ExecutableFilter_CMD(3, "ExecutableFilter_CMD", ".cmd", Arrays.asList(".cmd")), //NON-NLS
         ExecutableFilter_COM(4, "ExecutableFilter_COM", ".com", Arrays.asList(".com")); //NON-NLS
 
-        private int id;
-        private String name;
-        private String displayName;
-        private List<String> filter;
+        private final int id;
+        private final String name;
+        private final String displayName;
+        private final List<String> filter;
 
         private ExecutableFilter(int id, String name, String displayName, List<String> filter) {
             this.id = id;
@@ -208,7 +210,7 @@ public SleuthkitCase getSleuthkitCase() {
         return this.skCase;
     }
 
-    interface SearchFilterInterface {
+    public interface SearchFilterInterface {
 
         public String getName();
 
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/RecentFiles.java
similarity index 93%
rename from Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java
rename to Core/src/org/sleuthkit/autopsy/datamodel/accounts/RecentFiles.java
index cc8b8424cbefeabb0737736763358398ef42c976..d1ad7cb47b281c7eaf96e42e675318f97464bdbc 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/RecentFiles.java
@@ -16,8 +16,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.sleuthkit.autopsy.datamodel;
+package org.sleuthkit.autopsy.datamodel.accounts;
 
+import org.sleuthkit.autopsy.datamodel.AutopsyVisitableItem;
+import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor;
 import org.openide.util.NbBundle;
 import org.sleuthkit.datamodel.SleuthkitCase;
 
@@ -25,7 +27,7 @@
  * Recent files node support NOTE: As of june '15 we do not display this in the
  * tree. It can be added back when we have filtering in the results area.
  */
-class RecentFiles implements AutopsyVisitableItem {
+public class RecentFiles implements AutopsyVisitableItem {
 
     SleuthkitCase skCase;
 
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java
new file mode 100755
index 0000000000000000000000000000000000000000..108c7fc96308f96f256f1c83b4156b7dcb2a907f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java
@@ -0,0 +1,186 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import org.openide.util.NbBundle;
+
+/**
+ * A dialog for adding or editing an external viewer rule
+ */
+class AddExternalViewerRuleDialog extends JDialog {
+
+    private ExternalViewerRule rule;
+    private final AddExternalViewerRulePanel addRulePanel;
+    private BUTTON_PRESSED result;
+    private JButton saveButton;
+    private JButton closeButton;
+
+    enum BUTTON_PRESSED {
+        OK, CANCEL;
+    }
+
+    /**
+     * Creates a dialog for creating an external viewer rule
+     */
+    AddExternalViewerRuleDialog() {
+        super(new JFrame(NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.title")),
+                NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.title"), true);
+        addRulePanel = new AddExternalViewerRulePanel();
+        this.display();
+    }
+
+    /**
+     * Creates a dialog for editing an external viewer rule
+     *
+     * @param rule ExternalViewerRule to be edited
+     */
+    AddExternalViewerRuleDialog(ExternalViewerRule rule) {
+        super(new JFrame(NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.title")),
+                NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.title"), true);
+        addRulePanel = new AddExternalViewerRulePanel(rule);
+        this.display();
+    }
+
+    /**
+     * Displays the add external viewer rule dialog.
+     */
+    private void display() {
+        setLayout(new BorderLayout());
+
+        /**
+         * Center the dialog.
+         */
+        Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
+        int width = this.getSize().width;
+        int height = this.getSize().height;
+        setLocation((screenDimension.width - width) / 2, (screenDimension.height - height) / 2);
+
+        add(this.addRulePanel, BorderLayout.PAGE_START);
+
+        // Add a save button.
+        saveButton = new JButton(NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.saveButton.title"));
+        saveButton.addActionListener((ActionEvent e) -> {
+            doButtonAction(true);
+        });
+
+        // Add a close button.
+        closeButton = new JButton(NbBundle.getMessage(AddExternalViewerRuleDialog.class, "AddExternalViewerRuleDialog.cancelButton.title"));
+        closeButton.addActionListener((ActionEvent e) -> {
+            doButtonAction(false);
+        });
+
+        // Put the buttons in their own panel, under the settings panel.
+        JPanel buttonPanel = new JPanel();
+        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
+        buttonPanel.add(new javax.swing.Box.Filler(new Dimension(10, 10), new Dimension(10, 10), new Dimension(10, 10)));
+        buttonPanel.add(saveButton);
+        buttonPanel.add(new javax.swing.Box.Filler(new Dimension(10, 10), new Dimension(10, 10), new Dimension(10, 10)));
+        buttonPanel.add(closeButton);
+        add(buttonPanel, BorderLayout.LINE_START);
+
+        /**
+         * Add a handler for when the dialog window is closed directly,
+         * bypassing the buttons.
+         */
+        this.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                doButtonAction(false);
+            }
+        });
+
+        /**
+         * Add a listener to enable the save button when a text field in the
+         * AddRulePanel is changed or modified.
+         */
+        this.addRulePanel.addPropertyChangeListener((PropertyChangeEvent evt) -> {
+            if (evt.getPropertyName().equals(AddExternalViewerRulePanel.EVENT.CHANGED.toString())) {
+                enableSaveButton();
+            }
+        });
+
+        enableSaveButton();
+
+        /**
+         * Show the dialog.
+         */
+        pack();
+        setResizable(false);
+        setVisible(true);
+    }
+
+    /**
+     * Performs actions based on whether the save button was pressed or not.
+     *
+     * @param savePressed Whether save was pressed.
+     */
+    private void doButtonAction(boolean savePressed) {
+        if (savePressed) {
+            ExternalViewerRule ruleFromPanel = addRulePanel.getRule();
+            if (null != ruleFromPanel) {
+                this.rule = ruleFromPanel;
+                this.result = BUTTON_PRESSED.OK;
+                setVisible(false);
+            }
+        } else {
+            this.rule = null;
+            this.result = BUTTON_PRESSED.CANCEL;
+            setVisible(false);
+        }
+    }
+
+    /**
+     * Gets the external viewer rule of this dialog
+     *
+     * @return The external viewer rule
+     */
+    ExternalViewerRule getRule() {
+        return rule;
+    }
+
+    /**
+     * Gets the button pressed on this dialog
+     *
+     * @return The button pressed to close the dialog
+     */
+    BUTTON_PRESSED getResult() {
+        return result;
+    }
+
+    /**
+     * Enables save button once addRulePanel's fields have text in them. Maps
+     * enter key to the save button.
+     */
+    private void enableSaveButton() {
+        this.saveButton.setEnabled(addRulePanel.hasFields());
+        getRootPane().setDefaultButton(saveButton);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.form b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.form
new file mode 100755
index 0000000000000000000000000000000000000000..8ad8af3aa4b6c205a15f7955902eba5abbde39a1
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.form
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <NonVisualComponents>
+    <Component class="javax.swing.ButtonGroup" name="buttonGroup">
+    </Component>
+  </NonVisualComponents>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="nameTextField" alignment="0" max="32767" attributes="0"/>
+                  <Group type="102" alignment="0" attributes="0">
+                      <Component id="exePathTextField" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="browseButton" min="-2" max="-2" attributes="0"/>
+                  </Group>
+                  <Group type="102" attributes="0">
+                      <Component id="exePathLabel" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace min="0" pref="80" max="32767" attributes="0"/>
+                  </Group>
+                  <Group type="102" alignment="0" attributes="0">
+                      <Component id="nameLabel" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="32767" attributes="0"/>
+                      <Component id="mimeRadioButton" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="extRadioButton" min="-2" max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="0" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="nameLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="mimeRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="extRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
+              <Component id="nameTextField" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="exePathLabel" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="exePathTextField" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="browseButton" alignment="3" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace max="32767" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Component class="javax.swing.JLabel" name="nameLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.nameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JTextField" name="nameTextField">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.nameTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JRadioButton" name="mimeRadioButton">
+      <Properties>
+        <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+          <ComponentRef name="buttonGroup"/>
+        </Property>
+        <Property name="selected" type="boolean" value="true"/>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.mimeRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JRadioButton" name="extRadioButton">
+      <Properties>
+        <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+          <ComponentRef name="buttonGroup"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.extRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JLabel" name="exePathLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.exePathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JTextField" name="exePathTextField">
+      <Properties>
+        <Property name="editable" type="boolean" value="false"/>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.exePathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JButton" name="browseButton">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="AddExternalViewerRulePanel.browseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="browseButtonActionPerformed"/>
+      </Events>
+    </Component>
+  </SubComponents>
+</Form>
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java
new file mode 100755
index 0000000000000000000000000000000000000000..2481be77742c5e0929d3fe79fb56312175e76753
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java
@@ -0,0 +1,280 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.util.logging.Level;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.GeneralFilter;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
+
+/**
+ * Panel found in an AddRuleDialog
+ */
+class AddExternalViewerRulePanel extends javax.swing.JPanel {
+
+    private static final Logger logger = Logger.getLogger(AddExternalViewerRulePanel.class.getName());
+    private final JFileChooser fc = new JFileChooser();
+    private static final GeneralFilter exeFilter = new GeneralFilter(GeneralFilter.EXECUTABLE_EXTS, GeneralFilter.EXECUTABLE_DESC);
+
+    enum EVENT {
+        CHANGED
+    }
+
+    /**
+     * Creates new form AddRulePanel
+     */
+    AddExternalViewerRulePanel() {
+        initComponents();
+        fc.setDragEnabled(false);
+        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        fc.setMultiSelectionEnabled(false);
+        fc.setFileFilter(exeFilter);
+        customize();
+    }
+
+    /**
+     * Creates new form AddRulePanel if the user is editing a rule. Loads
+     * information of the rule being edited.
+     *
+     * @param rule to be edited
+     */
+    AddExternalViewerRulePanel(ExternalViewerRule rule) {
+        this();
+        nameTextField.setText(rule.getName());
+        exePathTextField.setText(rule.getExePath());
+        if (rule.getRuleType() == ExternalViewerRule.RuleType.EXT) {
+            extRadioButton.setSelected(true);
+        }
+        customize();
+    }
+
+    /**
+     * Allows listeners for when the name or exePath text fields are modified.
+     * Set action commands for the radio buttons.
+     */
+    private void customize() {
+        mimeRadioButton.setActionCommand("mime");
+        extRadioButton.setActionCommand("ext");
+        nameTextField.getDocument().addDocumentListener(new DocumentListener() {
+            @Override
+            public void changedUpdate(DocumentEvent e) {
+                fire();
+            }
+            @Override
+            public void removeUpdate(DocumentEvent e) {
+                fire();
+            }
+            @Override
+            public void insertUpdate(DocumentEvent e) {
+                fire();
+            }
+            private void fire() {
+                firePropertyChange(EVENT.CHANGED.toString(), null, null);
+            }
+        });
+        exePathTextField.getDocument().addDocumentListener(new DocumentListener() {
+            @Override
+            public void changedUpdate(DocumentEvent e) {
+                fire();
+            }
+            @Override
+            public void removeUpdate(DocumentEvent e) {
+                fire();
+            }
+            @Override
+            public void insertUpdate(DocumentEvent e) {
+                fire();
+            }
+            private void fire() {
+                firePropertyChange(EVENT.CHANGED.toString(), null, null);
+            }
+        });
+    }
+
+    /**
+     * Check if the text fields are filled and if a radio button is selected.
+     *
+     * @return true if neither of the text fields are empty and a radio button
+     *         is selected
+     */
+    boolean hasFields() {
+        return !exePathTextField.getText().isEmpty() && !nameTextField.getText().isEmpty() &&
+                (mimeRadioButton.isSelected() || extRadioButton.isSelected());
+    }
+
+    /**
+     * Returns the ExternalViewerRule created from input text. Returns null if
+     * the name is not a valid MIME type (as defined by both autopsy and the
+     * user, checked through FileTypeDetector) or in the form of a valid
+     * extension.
+     *
+     * @return ExternalViewerRule or null
+     */
+    ExternalViewerRule getRule() {
+        String exePath = exePathTextField.getText();
+        if (exePath.isEmpty()) {
+            JOptionPane.showMessageDialog(null,
+                    NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.message"),
+                    NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.title"),
+                    JOptionPane.ERROR_MESSAGE);
+            return null;
+        }
+
+        String name = nameTextField.getText();
+        if (mimeRadioButton.isSelected()) {
+            FileTypeDetector detector;
+            try {
+                detector = new FileTypeDetector();
+            } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
+                logger.log(Level.WARNING, "Couldn't create file type detector for file ext mismatch settings.", ex);
+                return null;
+            }
+            if (name.isEmpty() || !detector.isDetectable(name)) {
+                JOptionPane.showMessageDialog(null,
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidMime.message"),
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidMime.title"),
+                        JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            return new ExternalViewerRule(name, exePath, ExternalViewerRule.RuleType.MIME);
+        } else if (extRadioButton.isSelected()) {
+            if (name.isEmpty() || !name.matches("^\\.?\\w+$")) {
+                JOptionPane.showMessageDialog(null,
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.message"),
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.title"),
+                        JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (name.charAt(0) != '.') {
+                name = "." + name;
+            }
+            return new ExternalViewerRule(name.toLowerCase(), exePath, ExternalViewerRule.RuleType.EXT);
+        }
+        return null;
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        buttonGroup = new javax.swing.ButtonGroup();
+        nameLabel = new javax.swing.JLabel();
+        nameTextField = new javax.swing.JTextField();
+        mimeRadioButton = new javax.swing.JRadioButton();
+        extRadioButton = new javax.swing.JRadioButton();
+        exePathLabel = new javax.swing.JLabel();
+        exePathTextField = new javax.swing.JTextField();
+        browseButton = new javax.swing.JButton();
+
+        org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.nameLabel.text")); // NOI18N
+
+        nameTextField.setText(org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.nameTextField.text")); // NOI18N
+
+        buttonGroup.add(mimeRadioButton);
+        mimeRadioButton.setSelected(true);
+        org.openide.awt.Mnemonics.setLocalizedText(mimeRadioButton, org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.mimeRadioButton.text")); // NOI18N
+
+        buttonGroup.add(extRadioButton);
+        org.openide.awt.Mnemonics.setLocalizedText(extRadioButton, org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.extRadioButton.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(exePathLabel, org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.exePathLabel.text")); // NOI18N
+
+        exePathTextField.setEditable(false);
+        exePathTextField.setText(org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.exePathTextField.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(AddExternalViewerRulePanel.class, "AddExternalViewerRulePanel.browseButton.text")); // NOI18N
+        browseButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                browseButtonActionPerformed(evt);
+            }
+        });
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+        this.setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(nameTextField)
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(exePathTextField)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(browseButton))
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(exePathLabel)
+                        .addGap(0, 80, Short.MAX_VALUE))
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(nameLabel)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addComponent(mimeRadioButton)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(extRadioButton)))
+                .addContainerGap())
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(nameLabel)
+                    .addComponent(mimeRadioButton)
+                    .addComponent(extRadioButton))
+                .addGap(2, 2, 2)
+                .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(exePathLabel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(exePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(browseButton))
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
+        int returnState = fc.showOpenDialog(this);
+        if (returnState == JFileChooser.APPROVE_OPTION) {
+            String path = fc.getSelectedFile().getPath();
+            exePathTextField.setText(path);
+        }
+    }//GEN-LAST:event_browseButtonActionPerformed
+
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JButton browseButton;
+    private javax.swing.ButtonGroup buttonGroup;
+    private javax.swing.JLabel exePathLabel;
+    private javax.swing.JTextField exePathTextField;
+    private javax.swing.JRadioButton extRadioButton;
+    private javax.swing.JRadioButton mimeRadioButton;
+    private javax.swing.JLabel nameLabel;
+    private javax.swing.JTextField nameTextField;
+    // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
index aae76281731f7bab4e6f0e4426af4de98217ac33..ffb99d0f59670221e51ac89d7ab6d4e58a0ca147 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties
@@ -64,6 +64,7 @@ DirectoryTreeTopComponent.title.text=Directory Listing
 DirectoryTreeTopComponent.action.viewArtContent.text=View Artifact Content
 DirectoryTreeTopComponent.moduleErr=Module Error
 DirectoryTreeTopComponent.moduleErr.msg=A module caused an error listening to DirectoryTreeTopComponent updates. See log to determine which module. Some data could be incomplete.
+DirectoryTreeTopComponent.showRejectedCheckBox.text=Show Rejected Results
 ExplorerNodeActionVisitor.action.imgDetails.title=Image Details
 ExplorerNodeActionVisitor.action.extUnallocToSingleFiles=Extract Unallocated Space to Single Files
 ExplorerNodeActionVisitor.action.fileSystemDetails.title=File System Details
@@ -93,3 +94,33 @@ ExtractUnallocAction.done.notifyMsg.completedExtract.msg=Files were extracted to
 ExtractUnallocAction.done.errMsg.title=Error Extracting
 ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space\: {0}
 ExtractAction.done.notifyMsg.extractErr=Error extracting files\: {0}
+OptionsCategory_Name_ExternalViewer=External Viewer
+OptionsCategory_Keywords_ExternalViewer=ExternalViewer
+ExternalViewerGlobalSettingsPanel.newRuleButton.text=New Rule
+ExternalViewerGlobalSettingsPanel.editRuleButton.text=Edit Rule
+ExternalViewerGlobalSettingsPanel.deleteRuleButton.text=Delete Rule
+ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text=Add your custom rules for external viewers
+ExternalViewerGlobalSettingsPanel.ruleListLabel.text=MIME type and extensions
+ExternalViewerGlobalSettingsPanel.exePathLabel.text=Program associated with this MIME type or extension
+ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text=Program associated with this MIME type
+ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text=Program associated with this extension
+ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text=No MIME type or extension selected
+AddExternalViewerRuleDialog.saveButton.title=Save
+AddExternalViewerRuleDialog.cancelButton.title=Cancel
+AddExternalViewerRuleDialog.title=External Viewer Rule
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidMime.message=The MIME type is invalid. Add your custom types in the File Types options panel.
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidMime.title=Invalid MIME type
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.message=The extension is invalid.
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExt.title=Invalid extension
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.message=The path to the program executable is invalid
+ExternalViewerGlobalSettingsPanel.JOptionPane.invalidExePath.title=Invalid Path
+ExternalViewerGlobalSettingsPanel.exePathNameLabel.text=No MIME type or extension currently selected
+ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message=A rule already exists with this MIME type or extension. Please edit that one.
+ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title=Rule not added
+AddExternalViewerRulePanel.mimeRadioButton.text=MIME type
+AddExternalViewerRulePanel.nameTextField.text=
+AddExternalViewerRulePanel.nameLabel.text=MIME type or extension
+AddExternalViewerRulePanel.browseButton.text=Browse
+AddExternalViewerRulePanel.exePathTextField.text=
+AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension
+AddExternalViewerRulePanel.extRadioButton.text=Extension
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index 52b1954188a2d25ec42450f46490321d7f18f7b2..ec66a5d4c745b6803c132099d4a33766a1a5a581 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2013 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -38,36 +38,14 @@
 import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
 import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
 import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
-import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode;
-import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode;
 import org.sleuthkit.autopsy.datamodel.DirectoryNode;
 import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
 import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
-import org.sleuthkit.autopsy.datamodel.EmailExtracted;
-import org.sleuthkit.autopsy.datamodel.EmailExtracted.AccountNode;
-import org.sleuthkit.autopsy.datamodel.EmailExtracted.FolderNode;
-import org.sleuthkit.autopsy.datamodel.ExtractedContent;
-import org.sleuthkit.autopsy.datamodel.ExtractedContent.TypeNode;
 import org.sleuthkit.autopsy.datamodel.FileNode;
-import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode;
-import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode;
-import org.sleuthkit.autopsy.datamodel.FileTypeNode;
-import org.sleuthkit.autopsy.datamodel.FileTypesNode;
-import org.sleuthkit.autopsy.datamodel.HashsetHits;
-import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetNameNode;
-import org.sleuthkit.autopsy.datamodel.ImageNode;
-import org.sleuthkit.autopsy.datamodel.InterestingHits;
-import org.sleuthkit.autopsy.datamodel.KeywordHits;
-import org.sleuthkit.autopsy.datamodel.KeywordHits.ListNode;
-import org.sleuthkit.autopsy.datamodel.KeywordHits.TermNode;
 import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
 import org.sleuthkit.autopsy.datamodel.LocalFileNode;
-import org.sleuthkit.autopsy.datamodel.RecentFilesFilterNode;
-import org.sleuthkit.autopsy.datamodel.RecentFilesNode;
 import org.sleuthkit.autopsy.datamodel.Reports;
-import org.sleuthkit.autopsy.datamodel.Tags;
 import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
-import org.sleuthkit.autopsy.datamodel.VolumeNode;
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -300,112 +278,12 @@ private Content findLinked(BlackboardArtifactNode ba) {
      */
     private class GetPreferredActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default<AbstractAction> {
 
-        @Override
-        public AbstractAction visit(ImageNode in) {
-            return openChild(in);
-        }
-
-        @Override
-        public AbstractAction visit(VolumeNode vn) {
-            return openChild(vn);
-        }
-
-        @Override
-        public AbstractAction visit(ExtractedContent.RootNode ecn) {
-            return openChild(ecn);
-        }
-
-        @Override
-        public AbstractAction visit(KeywordHits.RootNode khrn) {
-            return openChild(khrn);
-        }
-
-        @Override
-        public AbstractAction visit(HashsetHits.RootNode hhrn) {
-            return openChild(hhrn);
-        }
-
-        @Override
-        public AbstractAction visit(HashsetNameNode hhsn) {
-            return openChild(hhsn);
-        }
-
-        @Override
-        public AbstractAction visit(InterestingHits.RootNode iarn) {
-            return openChild(iarn);
-        }
-
-        @Override
-        public AbstractAction visit(InterestingHits.SetNameNode iasn) {
-            return openChild(iasn);
-        }
-
-        @Override
-        public AbstractAction visit(EmailExtracted.RootNode eern) {
-            return openChild(eern);
-        }
-
-        @Override
-        public AbstractAction visit(AccountNode eean) {
-            return openChild(eean);
-        }
-
-        @Override
-        public AbstractAction visit(FolderNode eefn) {
-            return openChild(eefn);
-        }
-
-        @Override
-        public AbstractAction visit(RecentFilesNode rfn) {
-            return openChild(rfn);
-        }
-
-        @Override
-        public AbstractAction visit(DeletedContentsNode dcn) {
-            return openChild(dcn);
-        }
-
-        @Override
-        public AbstractAction visit(DeletedContentNode dcn) {
-            return openChild(dcn);
-        }
-
-        @Override
-        public AbstractAction visit(FileSizeRootNode fsrn) {
-            return openChild(fsrn);
-        }
-
-        @Override
-        public AbstractAction visit(FileSizeNode fsn) {
-            return openChild(fsn);
-        }
-
         @Override
         public AbstractAction visit(BlackboardArtifactNode ban) {
             return new ViewContextAction(
                     NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban);
         }
 
-        @Override
-        public AbstractAction visit(TypeNode atn) {
-            return openChild(atn);
-        }
-
-        @Override
-        public AbstractAction visit(Tags.TagNameNode node) {
-            return openChild(node);
-        }
-
-        @Override
-        public AbstractAction visit(Tags.ContentTagTypeNode node) {
-            return openChild(node);
-        }
-
-        @Override
-        public AbstractAction visit(Tags.BlackboardArtifactTagTypeNode node) {
-            return openChild(node);
-        }
-
         @Override
         public AbstractAction visit(DirectoryNode dn) {
             if (dn.getDisplayName().equals(DirectoryNode.DOTDOTDIR)) {
@@ -417,11 +295,6 @@ public AbstractAction visit(DirectoryNode dn) {
             }
         }
 
-        @Override
-        public AbstractAction visit(VirtualDirectoryNode ldn) {
-            return openChild(ldn);
-        }
-
         @Override
         public AbstractAction visit(FileNode fn) {
             if (fn.hasContentChildren()) {
@@ -440,31 +313,6 @@ public AbstractAction visit(LocalFileNode dfn) {
             }
         }
 
-        @Override
-        public AbstractAction visit(FileTypeNode fsfn) {
-            return openChild(fsfn);
-        }
-
-        @Override
-        public AbstractAction visit(FileTypesNode sfn) {
-            return openChild(sfn);
-        }
-
-        @Override
-        public AbstractAction visit(RecentFilesFilterNode rffn) {
-            return openChild(rffn);
-        }
-
-        @Override
-        public AbstractAction visit(ListNode khsn) {
-            return openChild(khsn);
-        }
-
-        @Override
-        public AbstractAction visit(TermNode khmln) {
-            return openChild(khmln);
-        }
-
         @Override
         public AbstractAction visit(Reports.ReportNode reportNode) {
             return reportNode.getPreferredAction();
@@ -472,7 +320,7 @@ public AbstractAction visit(Reports.ReportNode reportNode) {
 
         @Override
         protected AbstractAction defaultVisit(DisplayableItemNode c) {
-            return null;
+            return openChild(c);
         }
 
         /**
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
index e4821adcb30b3b5414944cfe23708f0b79eb7a33..9504548823b93f446194e93668ca7dab021419a7 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form
@@ -16,28 +16,29 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" alignment="0" attributes="0">
-              <EmptySpace max="-2" attributes="0"/>
+          <Component id="jScrollPane1" alignment="0" pref="262" max="32767" attributes="0"/>
+          <Group type="102" attributes="0">
+              <EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
               <Component id="backButton" min="-2" pref="23" max="-2" attributes="0"/>
               <EmptySpace min="0" pref="0" max="-2" attributes="0"/>
               <Component id="forwardButton" min="-2" pref="23" max="-2" attributes="0"/>
-              <EmptySpace pref="206" max="32767" attributes="0"/>
+              <EmptySpace pref="46" max="32767" attributes="0"/>
+              <Component id="showRejectedCheckBox" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
           </Group>
-          <Component id="jSeparator1" alignment="0" pref="262" max="32767" attributes="0"/>
-          <Component id="jScrollPane1" alignment="0" pref="262" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" alignment="0" attributes="0">
-              <Group type="103" groupAlignment="0" attributes="0">
-                  <Component id="backButton" min="-2" pref="26" max="-2" attributes="1"/>
+              <EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="1" attributes="0">
                   <Component id="forwardButton" min="-2" pref="26" max="-2" attributes="1"/>
+                  <Component id="backButton" min="-2" pref="26" max="-2" attributes="1"/>
+                  <Component id="showRejectedCheckBox" min="-2" max="-2" attributes="0"/>
               </Group>
-              <EmptySpace min="0" pref="0" max="-2" attributes="0"/>
-              <Component id="jSeparator1" min="-2" pref="1" max="-2" attributes="0"/>
-              <EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
-              <Component id="jScrollPane1" pref="860" max="32767" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="jScrollPane1" min="-2" pref="838" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
           </Group>
       </Group>
@@ -122,7 +123,12 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="forwardButtonActionPerformed"/>
       </Events>
     </Component>
-    <Component class="javax.swing.JSeparator" name="jSeparator1">
+    <Component class="javax.swing.JCheckBox" name="showRejectedCheckBox">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="DirectoryTreeTopComponent.showRejectedCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
     </Component>
   </SubComponents>
 </Form>
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
index 9d7f7c5df8c48aef5001c33c0ad1a5a1028b57b4..739d3fd90ca3e649e3485d3a3bc6a8818b81f4d5 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2015 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -60,7 +60,6 @@
 import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
 import org.sleuthkit.autopsy.datamodel.DataSources;
 import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
-import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
 import org.sleuthkit.autopsy.datamodel.ExtractedContent;
 import org.sleuthkit.autopsy.datamodel.KeywordHits;
 import org.sleuthkit.autopsy.datamodel.KnownFileFilterNode;
@@ -71,6 +70,7 @@
 import org.sleuthkit.autopsy.datamodel.Tags;
 import org.sleuthkit.autopsy.datamodel.Views;
 import org.sleuthkit.autopsy.datamodel.ViewsNode;
+import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -164,65 +164,68 @@ private void initComponents() {
         jScrollPane1 = new BeanTreeView();
         backButton = new javax.swing.JButton();
         forwardButton = new javax.swing.JButton();
-        jSeparator1 = new javax.swing.JSeparator();
+        showRejectedCheckBox = new javax.swing.JCheckBox();
 
         jScrollPane1.setBorder(null);
 
-        backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N NON-NLS
+        backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N
         org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N
         backButton.setBorderPainted(false);
         backButton.setContentAreaFilled(false);
-        backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N NON-NLS
+        backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N
         backButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
         backButton.setMaximumSize(new java.awt.Dimension(55, 100));
         backButton.setMinimumSize(new java.awt.Dimension(5, 5));
         backButton.setPreferredSize(new java.awt.Dimension(23, 23));
-        backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N NON-NLS
+        backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N
         backButton.addActionListener(new java.awt.event.ActionListener() {
             public void actionPerformed(java.awt.event.ActionEvent evt) {
                 backButtonActionPerformed(evt);
             }
         });
 
-        forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N NON-NLS
+        forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N
         org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N
         forwardButton.setBorderPainted(false);
         forwardButton.setContentAreaFilled(false);
-        forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N NON-NLS
+        forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N
         forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
         forwardButton.setMaximumSize(new java.awt.Dimension(55, 100));
         forwardButton.setMinimumSize(new java.awt.Dimension(5, 5));
         forwardButton.setPreferredSize(new java.awt.Dimension(23, 23));
-        forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N NON-NLS
+        forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N
         forwardButton.addActionListener(new java.awt.event.ActionListener() {
             public void actionPerformed(java.awt.event.ActionEvent evt) {
                 forwardButtonActionPerformed(evt);
             }
         });
 
+        org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N
+
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
         layout.setHorizontalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
             .addGroup(layout.createSequentialGroup()
-                .addContainerGap()
+                .addGap(5, 5, 5)
                 .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addGap(0, 0, 0)
                 .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addContainerGap(206, Short.MAX_VALUE))
-            .addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
-            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE)
+                .addComponent(showRejectedCheckBox)
+                .addContainerGap())
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
-                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                .addGap(5, 5, 5)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+                    .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
                     .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
-                    .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
-                .addGap(0, 0, 0)
-                .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 1, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addGap(0, 0, 0)
-                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 860, Short.MAX_VALUE)
+                    .addComponent(showRejectedCheckBox))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 838, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addContainerGap())
         );
     }// </editor-fold>//GEN-END:initComponents
@@ -275,11 +278,12 @@ private void forwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN
 
         this.setCursor(null);
     }//GEN-LAST:event_forwardButtonActionPerformed
+
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JButton backButton;
     private javax.swing.JButton forwardButton;
     private javax.swing.JScrollPane jScrollPane1;
-    private javax.swing.JSeparator jSeparator1;
+    private javax.swing.JCheckBox showRejectedCheckBox;
     // End of variables declaration//GEN-END:variables
 
     /**
@@ -356,6 +360,7 @@ public void componentOpened() {
                     items.add(new Tags());
                     items.add(new Reports());
                     contentChildren = new RootContentChildren(items);
+                 
                     Node root = new AbstractNode(contentChildren) {
                         /**
                          * to override the right click action in the white blank
@@ -399,6 +404,10 @@ public Node getNode() throws IOException {
                     tree.expandNode(resultsChilds.findChild(KeywordHits.NAME));
                     tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME));
 
+                    Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class);
+                    showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction());
+                    showRejectedCheckBox.setSelected(false);
+
                     Node views = childNodes.findChild(ViewsNode.NAME);
                     Children viewsChilds = views.getChildren();
                     for (Node n : viewsChilds.getNodes()) {
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java
index a52dd2cbf5d7f4f9d745684ad31b66aef6b8efc2..716ac430052fdb3b383af4049e542366cd01906d 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- *
- * Copyright 2011-2014 Basis Technology Corp.
+ * 
+ * Copyright 2011-2016 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.
@@ -33,13 +33,14 @@
 
 /**
  * Extracts a File object to a temporary file in the case directory, and then
- * tries to open it in the user's system with the default associated
- * application.
+ * tries to open it in the user's system with the default or user specified
+ * associated application.
  */
 public class ExternalViewerAction extends AbstractAction {
 
     private final static Logger logger = Logger.getLogger(ExternalViewerAction.class.getName());
     private org.sleuthkit.datamodel.AbstractFile fileObject;
+    private String fileObjectExt;
     final static String[] EXECUTABLE_EXT = {".exe", ".dll", ".com", ".bat", ".msi", ".reg", ".scr", ".cmd"}; //NON-NLS
 
     public ExternalViewerAction(String title, Node fileNode) {
@@ -53,12 +54,15 @@ public ExternalViewerAction(String title, Node fileNode) {
         boolean isExecutable = false;
         if (extPos != -1) {
             String extension = fileName.substring(extPos, fileName.length()).toLowerCase();
+            fileObjectExt = extension;
             for (int i = 0; i < EXECUTABLE_EXT.length; ++i) {
                 if (EXECUTABLE_EXT[i].equals(extension)) {
                     isExecutable = true;
                     break;
                 }
             }
+        } else {
+            fileObjectExt = "";
         }
 
         // no point opening a file if it's empty, and java doesn't know how to
@@ -89,13 +93,31 @@ public void actionPerformed(ActionEvent e) {
             logger.log(Level.WARNING, "Can't save to temporary file.", ex); //NON-NLS
         }
 
-        try {
-            Desktop.getDesktop().open(tempFile);
-        } catch (IOException ex) {
-            logger.log(Level.WARNING, "Could not find a viewer for the given file: " + tempFile.getName(), ex); //NON-NLS
-            JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_message(), Bundle.ExternalViewerAction_actionPerformed_failure_title(), JOptionPane.ERROR_MESSAGE);
+        /**
+         * Check if the file MIME type or extension exists in the user defined
+         * settings. Otherwise open with the default associated application.
+         */
+        String exePath = ExternalViewerRulesManager.getInstance().getExePathForName(fileObject.getMIMEType());
+        if (exePath.equals("")) {
+            exePath = ExternalViewerRulesManager.getInstance().getExePathForName(fileObjectExt);
+        }
+        if (!exePath.equals("")) {
+            Runtime runtime = Runtime.getRuntime();
+            String[] s = new String[]{exePath, tempFile.getAbsolutePath()};
+            try {
+                runtime.exec(s);
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Could not open the specified viewer for the given file: " + tempFile.getName(), ex); //NON-NLS
+                JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_message(), Bundle.ExternalViewerAction_actionPerformed_failure_title(), JOptionPane.ERROR_MESSAGE);
+            }
+        } else {
+            try {
+                Desktop.getDesktop().open(tempFile);
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Could not find a viewer for the given file: " + tempFile.getName(), ex); //NON-NLS
+                JOptionPane.showMessageDialog(null, Bundle.ExternalViewerAction_actionPerformed_failure_message(), Bundle.ExternalViewerAction_actionPerformed_failure_title(), JOptionPane.ERROR_MESSAGE);
+            }
         }
-
         // delete the file on exit
         tempFile.deleteOnExit();
     }
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form
new file mode 100755
index 0000000000000000000000000000000000000000..e00c4a966fa09eba13e87f47223a6d2d1e0a5988
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.form
@@ -0,0 +1,267 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <Properties>
+    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+      <Dimension value="[750, 500]"/>
+    </Property>
+  </Properties>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Container class="javax.swing.JPanel" name="jPanel1">
+
+      <Layout>
+        <DimensionLayout dim="0">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="externalViewerTitleLabel" pref="777" max="32767" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+              </Group>
+              <Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="jScrollPane1" pref="777" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+          </Group>
+        </DimensionLayout>
+        <DimensionLayout dim="1">
+          <Group type="103" groupAlignment="0" attributes="0">
+              <Group type="102" alignment="0" attributes="0">
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="externalViewerTitleLabel" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace pref="475" max="32767" attributes="0"/>
+              </Group>
+              <Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
+                  <Group type="102" attributes="0">
+                      <EmptySpace min="-2" pref="32" max="-2" attributes="0"/>
+                      <Component id="jScrollPane1" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                  </Group>
+              </Group>
+          </Group>
+        </DimensionLayout>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JLabel" name="externalViewerTitleLabel">
+          <Properties>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+
+          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+          <SubComponents>
+            <Container class="javax.swing.JSplitPane" name="jSplitPane1">
+              <Properties>
+                <Property name="dividerLocation" type="int" value="350"/>
+                <Property name="dividerSize" type="int" value="1"/>
+              </Properties>
+
+              <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
+              <SubComponents>
+                <Container class="javax.swing.JPanel" name="exePanel">
+                  <Constraints>
+                    <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
+                      <JSplitPaneConstraints position="right"/>
+                    </Constraint>
+                  </Constraints>
+
+                  <Layout>
+                    <DimensionLayout dim="0">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="0" attributes="0">
+                                  <Component id="exePathLabel" alignment="0" min="-2" max="-2" attributes="0"/>
+                                  <Component id="exePathNameLabel" alignment="0" min="-2" max="-2" attributes="0"/>
+                              </Group>
+                              <EmptySpace pref="159" max="32767" attributes="0"/>
+                          </Group>
+                      </Group>
+                    </DimensionLayout>
+                    <DimensionLayout dim="1">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" alignment="0" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="exePathLabel" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="exePathNameLabel" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace pref="408" max="32767" attributes="0"/>
+                          </Group>
+                      </Group>
+                    </DimensionLayout>
+                  </Layout>
+                  <SubComponents>
+                    <Component class="javax.swing.JLabel" name="exePathLabel">
+                      <Properties>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.exePathLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                    </Component>
+                    <Component class="javax.swing.JLabel" name="exePathNameLabel">
+                      <Properties>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.exePathNameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                    </Component>
+                  </SubComponents>
+                </Container>
+                <Container class="javax.swing.JPanel" name="rulesPanel">
+                  <Constraints>
+                    <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
+                      <JSplitPaneConstraints position="left"/>
+                    </Constraint>
+                  </Constraints>
+
+                  <Layout>
+                    <DimensionLayout dim="0">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="0" attributes="0">
+                                  <Group type="102" attributes="0">
+                                      <Group type="103" groupAlignment="0" attributes="0">
+                                          <Component id="ruleListLabel" alignment="1" max="32767" attributes="0"/>
+                                          <Group type="102" attributes="0">
+                                              <Component id="rulesScrollPane" min="-2" pref="311" max="-2" attributes="0"/>
+                                              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+                                          </Group>
+                                      </Group>
+                                      <EmptySpace max="-2" attributes="0"/>
+                                  </Group>
+                                  <Group type="102" attributes="0">
+                                      <Component id="newRuleButton" min="-2" max="-2" attributes="0"/>
+                                      <EmptySpace max="-2" attributes="0"/>
+                                      <Component id="editRuleButton" min="-2" max="-2" attributes="0"/>
+                                      <EmptySpace max="-2" attributes="0"/>
+                                      <Component id="deleteRuleButton" min="-2" max="-2" attributes="0"/>
+                                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+                                  </Group>
+                              </Group>
+                          </Group>
+                      </Group>
+                    </DimensionLayout>
+                    <DimensionLayout dim="1">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Group type="102" alignment="0" attributes="0">
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="ruleListLabel" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Component id="rulesScrollPane" pref="380" max="32767" attributes="0"/>
+                              <EmptySpace max="-2" attributes="0"/>
+                              <Group type="103" groupAlignment="3" attributes="0">
+                                  <Component id="newRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                                  <Component id="editRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                                  <Component id="deleteRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
+                              </Group>
+                              <EmptySpace max="-2" attributes="0"/>
+                          </Group>
+                      </Group>
+                    </DimensionLayout>
+                  </Layout>
+                  <SubComponents>
+                    <Component class="javax.swing.JLabel" name="ruleListLabel">
+                      <Properties>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.ruleListLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                    </Component>
+                    <Container class="javax.swing.JScrollPane" name="rulesScrollPane">
+                      <AuxValues>
+                        <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+                      </AuxValues>
+
+                      <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+                      <SubComponents>
+                        <Component class="javax.swing.JList" name="rulesList">
+                          <Properties>
+                            <Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
+                              <StringArray count="0"/>
+                            </Property>
+                          </Properties>
+                          <AuxValues>
+                            <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;ExternalViewerRule&gt;"/>
+                          </AuxValues>
+                        </Component>
+                      </SubComponents>
+                    </Container>
+                    <Component class="javax.swing.JButton" name="newRuleButton">
+                      <Properties>
+                        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+                          <Image iconType="3" name="/org/sleuthkit/autopsy/images/add16.png"/>
+                        </Property>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.newRuleButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                      <Events>
+                        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newRuleButtonActionPerformed"/>
+                      </Events>
+                    </Component>
+                    <Component class="javax.swing.JButton" name="editRuleButton">
+                      <Properties>
+                        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+                          <Image iconType="3" name="/org/sleuthkit/autopsy/images/edit16.png"/>
+                        </Property>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.editRuleButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                      <Events>
+                        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="editRuleButtonActionPerformed"/>
+                      </Events>
+                    </Component>
+                    <Component class="javax.swing.JButton" name="deleteRuleButton">
+                      <Properties>
+                        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+                          <Image iconType="3" name="/org/sleuthkit/autopsy/images/delete16.png"/>
+                        </Property>
+                        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                          <ResourceString bundle="org/sleuthkit/autopsy/directorytree/Bundle.properties" key="ExternalViewerGlobalSettingsPanel.deleteRuleButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                        </Property>
+                      </Properties>
+                      <Events>
+                        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deleteRuleButtonActionPerformed"/>
+                      </Events>
+                    </Component>
+                  </SubComponents>
+                </Container>
+              </SubComponents>
+            </Container>
+          </SubComponents>
+        </Container>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java
new file mode 100755
index 0000000000000000000000000000000000000000..b2497baed86e51f73231eb200e99c7396a67ef19
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java
@@ -0,0 +1,369 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.swing.DefaultListModel;
+import javax.swing.JOptionPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
+import org.sleuthkit.autopsy.coreutils.Logger;
+
+/**
+ * An options panel for the user to create, edit, and delete associations for
+ * opening files in external viewers. Users can associate a file by either MIME
+ * type or by extension to an executable file.
+ */
+final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel implements OptionsPanel {
+
+    private static final Logger LOGGER = Logger.getLogger(ExternalViewerGlobalSettingsPanel.class.getName());
+    private DefaultListModel<ExternalViewerRule> rulesListModel;
+    private java.util.List<ExternalViewerRule> rules;
+
+    /**
+     * Creates new form ExternalViewerGlobalSettingsPanel
+     */
+    public ExternalViewerGlobalSettingsPanel() {
+        initComponents();
+        customizeComponents();
+    }
+
+    /**
+     * Initializes field variables. Adds a listener to the list of rules.
+     */
+    private void customizeComponents() {
+        rulesListModel = new DefaultListModel<>();
+        rules = new ArrayList<>();
+        rulesList.setModel(rulesListModel);
+        rulesList.addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                if (e.getValueIsAdjusting() == false) {
+                    if (rulesList.getSelectedIndex() == -1) {
+                        clearExePath();
+                    } else {
+                        populateExePath();
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        jPanel1 = new javax.swing.JPanel();
+        externalViewerTitleLabel = new javax.swing.JLabel();
+        jScrollPane1 = new javax.swing.JScrollPane();
+        jSplitPane1 = new javax.swing.JSplitPane();
+        exePanel = new javax.swing.JPanel();
+        exePathLabel = new javax.swing.JLabel();
+        exePathNameLabel = new javax.swing.JLabel();
+        rulesPanel = new javax.swing.JPanel();
+        ruleListLabel = new javax.swing.JLabel();
+        rulesScrollPane = new javax.swing.JScrollPane();
+        rulesList = new javax.swing.JList<>();
+        newRuleButton = new javax.swing.JButton();
+        editRuleButton = new javax.swing.JButton();
+        deleteRuleButton = new javax.swing.JButton();
+
+        setPreferredSize(new java.awt.Dimension(750, 500));
+
+        org.openide.awt.Mnemonics.setLocalizedText(externalViewerTitleLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.externalViewerTitleLabel.text")); // NOI18N
+
+        jSplitPane1.setDividerLocation(350);
+        jSplitPane1.setDividerSize(1);
+
+        org.openide.awt.Mnemonics.setLocalizedText(exePathLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathLabel.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(exePathNameLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.exePathNameLabel.text")); // NOI18N
+
+        javax.swing.GroupLayout exePanelLayout = new javax.swing.GroupLayout(exePanel);
+        exePanel.setLayout(exePanelLayout);
+        exePanelLayout.setHorizontalGroup(
+            exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(exePanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(exePathLabel)
+                    .addComponent(exePathNameLabel))
+                .addContainerGap(159, Short.MAX_VALUE))
+        );
+        exePanelLayout.setVerticalGroup(
+            exePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(exePanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(exePathLabel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(exePathNameLabel)
+                .addContainerGap(408, Short.MAX_VALUE))
+        );
+
+        jSplitPane1.setRightComponent(exePanel);
+
+        org.openide.awt.Mnemonics.setLocalizedText(ruleListLabel, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.ruleListLabel.text")); // NOI18N
+
+        rulesScrollPane.setViewportView(rulesList);
+
+        newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.newRuleButton.text")); // NOI18N
+        newRuleButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                newRuleButtonActionPerformed(evt);
+            }
+        });
+
+        editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.editRuleButton.text")); // NOI18N
+        editRuleButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                editRuleButtonActionPerformed(evt);
+            }
+        });
+
+        deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.deleteRuleButton.text")); // NOI18N
+        deleteRuleButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                deleteRuleButtonActionPerformed(evt);
+            }
+        });
+
+        javax.swing.GroupLayout rulesPanelLayout = new javax.swing.GroupLayout(rulesPanel);
+        rulesPanel.setLayout(rulesPanelLayout);
+        rulesPanelLayout.setHorizontalGroup(
+            rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(rulesPanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(rulesPanelLayout.createSequentialGroup()
+                        .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(ruleListLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                            .addGroup(rulesPanelLayout.createSequentialGroup()
+                                .addComponent(rulesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 311, javax.swing.GroupLayout.PREFERRED_SIZE)
+                                .addGap(0, 0, Short.MAX_VALUE)))
+                        .addContainerGap())
+                    .addGroup(rulesPanelLayout.createSequentialGroup()
+                        .addComponent(newRuleButton)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(editRuleButton)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(deleteRuleButton)
+                        .addGap(0, 0, Short.MAX_VALUE))))
+        );
+        rulesPanelLayout.setVerticalGroup(
+            rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(rulesPanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(ruleListLabel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(rulesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(rulesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(newRuleButton)
+                    .addComponent(editRuleButton)
+                    .addComponent(deleteRuleButton))
+                .addContainerGap())
+        );
+
+        jSplitPane1.setLeftComponent(rulesPanel);
+
+        jScrollPane1.setViewportView(jSplitPane1);
+
+        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+        jPanel1.setLayout(jPanel1Layout);
+        jPanel1Layout.setHorizontalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(externalViewerTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 777, Short.MAX_VALUE)
+                .addContainerGap())
+            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                .addGroup(jPanel1Layout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 777, Short.MAX_VALUE)
+                    .addContainerGap()))
+        );
+        jPanel1Layout.setVerticalGroup(
+            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(externalViewerTitleLabel)
+                .addContainerGap(475, Short.MAX_VALUE))
+            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                .addGroup(jPanel1Layout.createSequentialGroup()
+                    .addGap(32, 32, 32)
+                    .addComponent(jScrollPane1)
+                    .addContainerGap()))
+        );
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+        this.setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed
+        AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog();
+        AddExternalViewerRuleDialog.BUTTON_PRESSED result = dialog.getResult();
+        if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
+            ExternalViewerRule newRule = dialog.getRule();
+            // Only allow one association for each MIME type or extension.
+            if (rules.contains(newRule)) {
+                JOptionPane.showMessageDialog(null,
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
+                        JOptionPane.ERROR_MESSAGE);
+            } else {
+                rules.add(newRule);
+                updateRulesListModel();
+                int index = rules.indexOf(newRule);
+                rulesList.setSelectedIndex(index);
+                enableButtons();
+                firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+            }
+        }
+    }//GEN-LAST:event_newRuleButtonActionPerformed
+
+    private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed
+        int selected = rulesList.getSelectedIndex();
+        AddExternalViewerRuleDialog dialog = new AddExternalViewerRuleDialog(rulesListModel.get(rulesList.getSelectedIndex()));
+        AddExternalViewerRuleDialog.BUTTON_PRESSED result = dialog.getResult();
+        if (result == AddExternalViewerRuleDialog.BUTTON_PRESSED.OK) {
+            rules.remove(selected);
+            ExternalViewerRule newRule = dialog.getRule();
+            // Only allow one association for each MIME type or extension.
+            if (rules.contains(newRule)) {
+                JOptionPane.showMessageDialog(null,
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.message"),
+                        NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class, "ExternalViewerGlobalSettingsPanel.JOptionPane.ruleAlreadyExists.title"),
+                        JOptionPane.ERROR_MESSAGE);
+            } else {
+                rules.add(selected, dialog.getRule());
+                updateRulesListModel();
+                firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+            }
+        }
+        rulesList.setSelectedIndex(selected);
+        enableButtons();
+    }//GEN-LAST:event_editRuleButtonActionPerformed
+
+    private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed
+        ExternalViewerRule rule = rulesList.getSelectedValue();
+        rules.remove(rule);
+        updateRulesListModel();
+        firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+    }//GEN-LAST:event_deleteRuleButtonActionPerformed
+
+    @Override
+    public void store() {
+        ExternalViewerRulesManager.getInstance().setUserRules(rules);
+    }
+
+    @Override
+    public void load() {
+        rules = ExternalViewerRulesManager.getInstance().getUserRules();
+        updateRulesListModel();
+        enableButtons();
+    }
+
+    /**
+     * Enable edit and delete buttons if there is a rule selected.
+     */
+    private void enableButtons() {
+        boolean ruleIsSelected = rulesList.getSelectedIndex() != -1;
+        editRuleButton.setEnabled(ruleIsSelected);
+        deleteRuleButton.setEnabled(ruleIsSelected);
+    }
+
+    /**
+     * Sets the list model for the rules list component, sorted by the MIME
+     * type or extension alphabetically.
+     */
+    private void updateRulesListModel() {
+        rulesListModel.clear();
+        Collections.sort(rules);
+        for (ExternalViewerRule rule : rules) {
+            rulesListModel.addElement(rule);
+        }
+    }
+
+    /**
+     * Fills in the .exe file path label if a rule is selected.
+     */
+    private void populateExePath() {
+        ExternalViewerRule rule = rulesList.getSelectedValue();
+        if (rule != null) {
+            if (rule.getRuleType() == ExternalViewerRule.RuleType.MIME) {
+                exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
+                        "ExternalViewerGlobalSettingsPanel.exePathLabel.MIME.text"));
+            } else {
+                exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
+                        "ExternalViewerGlobalSettingsPanel.exePathLabel.EXT.text"));
+            }
+            exePathNameLabel.setText(rule.getExePath());
+        }
+        enableButtons();
+    }
+
+    /**
+     * Clears the .exe file path label.
+     */
+    private void clearExePath() {
+        rulesList.clearSelection();
+        exePathLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
+                "ExternalViewerGlobalSettingsPanel.exePathLabel.text"));
+        exePathNameLabel.setText(NbBundle.getMessage(ExternalViewerGlobalSettingsPanel.class,
+                "ExternalViewerGlobalSettingsPanel.exePathLabel.empty.text"));
+        enableButtons();
+    }
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JButton deleteRuleButton;
+    private javax.swing.JButton editRuleButton;
+    private javax.swing.JPanel exePanel;
+    private javax.swing.JLabel exePathLabel;
+    private javax.swing.JLabel exePathNameLabel;
+    private javax.swing.JLabel externalViewerTitleLabel;
+    private javax.swing.JPanel jPanel1;
+    private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JSplitPane jSplitPane1;
+    private javax.swing.JButton newRuleButton;
+    private javax.swing.JLabel ruleListLabel;
+    private javax.swing.JList<ExternalViewerRule> rulesList;
+    private javax.swing.JPanel rulesPanel;
+    private javax.swing.JScrollPane rulesScrollPane;
+    // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerOptionsPanelController.java
new file mode 100755
index 0000000000000000000000000000000000000000..b351192af3f87cf048ca29675da356f4ee19ede5
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerOptionsPanelController.java
@@ -0,0 +1,112 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import javax.swing.JComponent;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
+
+/**
+ * Controller for the ExternalViewerGlobalSettingsPanel
+ */
+@OptionsPanelController.TopLevelRegistration(
+        categoryName = "#OptionsCategory_Name_ExternalViewer",
+        iconBase = "org/sleuthkit/autopsy/directorytree/external-viewer-rules-32x32.png",
+        keywords = "#OptionsCategory_Keywords_ExternalViewer",
+        keywordsCategory = "ExternalViewer",
+        position = 9
+)
+public final class ExternalViewerOptionsPanelController extends OptionsPanelController {
+
+    private ExternalViewerGlobalSettingsPanel panel;
+    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+    private boolean changed;
+
+    @Override
+    public void update() {
+        getPanel().load();
+        changed = false;
+    }
+
+    @Override
+    public void applyChanges() {
+        if (changed) {
+            getPanel().store();
+            changed = false;
+        }
+    }
+
+    @Override
+    public void cancel() {
+    }
+
+    @Override
+    public boolean isValid() {
+        return true;
+    }
+
+    @Override
+    public boolean isChanged() {
+        return changed;
+    }
+
+    @Override
+    public JComponent getComponent(Lookup lkp) {
+        return getPanel();
+    }
+
+    @Override
+    public HelpCtx getHelpCtx() {
+        return null;
+    }
+
+    @Override
+    public void addPropertyChangeListener(PropertyChangeListener l) {
+        pcs.addPropertyChangeListener(l);
+    }
+
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener l) {
+        pcs.removePropertyChangeListener(l);
+    }
+
+    private ExternalViewerGlobalSettingsPanel getPanel() {
+        if (panel == null) {
+            panel = new ExternalViewerGlobalSettingsPanel();
+            panel.addPropertyChangeListener((PropertyChangeEvent evt) -> {
+                if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) {
+                    changed();
+                }
+            });
+        }
+        return panel;
+    }
+
+    void changed() {
+        if (!changed) {
+            changed = true;
+            pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true);
+        }
+        pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRule.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRule.java
new file mode 100755
index 0000000000000000000000000000000000000000..f14f110af205a2ef161e733d70b7d64ed7e0bfe8
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRule.java
@@ -0,0 +1,93 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.util.Objects;
+
+/**
+ * An object that represents an association between a MIME type or extension
+ * name to a user defined executable.
+ */
+class ExternalViewerRule implements Comparable<ExternalViewerRule> {
+
+    private final String name;
+    private final String exePath;
+    private final RuleType ruleType;
+
+    enum RuleType {
+        MIME, EXT
+    }
+
+    /**
+     * Creates a new ExternalViewerRule
+     *
+     * @param name    MIME type or extension
+     * @param exePath Absolute path of the exe file
+     * @param type    RuleType of the rule, either MIME or EXT
+     */
+    ExternalViewerRule(String name, String exePath, RuleType type) {
+        this.name = name;
+        this.exePath = exePath;
+        this.ruleType = type;
+    }
+
+    String getName() {
+        return name;
+    }
+
+    String getExePath() {
+        return exePath;
+    }
+
+    RuleType getRuleType() {
+        return ruleType;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Only one association is allowed per MIME type or extension, so rules are
+     * equal if the names are the same.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (other != null && other instanceof ExternalViewerRule) {
+            ExternalViewerRule that = (ExternalViewerRule) other;
+            if (this.getName().equals(that.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 67 * hash + Objects.hashCode(this.name);
+        return hash;
+    }
+
+    @Override
+    public int compareTo(ExternalViewerRule other) {
+        return this.getName().compareTo(other.getName());
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRulesManager.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRulesManager.java
new file mode 100755
index 0000000000000000000000000000000000000000..dc5ff27b205bedcad281db490ee4eb90c639ba1e
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerRulesManager.java
@@ -0,0 +1,110 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011-2016 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.directorytree;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+
+/**
+ * Manager for user's external viewer rules, used by the options panel and the
+ * ExternalViewerAction. Reads from and writes to a preferences file.
+ */
+class ExternalViewerRulesManager {
+
+    private static final String RULES_SETTINGS_NAME = "ExternalViewerRules"; //NON-NLS
+    private static final String RULES_SETTINGS_KEY = "Rules"; //NON-NLS
+    private static ExternalViewerRulesManager instance;
+    private List<ExternalViewerRule> userRules = new ArrayList<>();
+
+    /**
+     * Gets the singleton manager of the external viewer rules defined by users.
+     *
+     * @return The external viewer rules manager singleton.
+     */
+    synchronized static ExternalViewerRulesManager getInstance() {
+        if (instance == null) {
+            instance = new ExternalViewerRulesManager();
+            instance.loadUserDefinedRules();
+        }
+        return instance;
+    }
+
+    private ExternalViewerRulesManager() {
+    }
+
+    /**
+     * Loads user defined rules from the configuration settings file.
+     */
+    private void loadUserDefinedRules() {
+        String setting = ModuleSettings.getConfigSetting(RULES_SETTINGS_NAME, RULES_SETTINGS_KEY);
+        if (setting != null && !setting.isEmpty()) {
+            List<String> ruleTuples = Arrays.asList(setting.split("\\|"));
+            for (String ruleTuple : ruleTuples) {
+                String[] ruleParts = ruleTuple.split(">");
+                userRules.add(new ExternalViewerRule(ruleParts[0], ruleParts[1],
+                        ExternalViewerRule.RuleType.valueOf(ruleParts[2])));
+            }
+        }
+    }
+
+    /**
+     * Writes a list of ExternalViewerRule objects to a configuration settings
+     * file.
+     *
+     * @param rules to be written and saved.
+     */
+    synchronized void setUserRules(List<ExternalViewerRule> rules) {
+        StringBuilder setting = new StringBuilder();
+        for (ExternalViewerRule rule : rules) {
+            if (setting.length() != 0) {
+                setting.append("|");
+            }
+            setting.append(rule.getName()).append(">");
+            setting.append(rule.getExePath()).append(">");
+            setting.append(rule.getRuleType().name());
+        }
+        ModuleSettings.setConfigSetting(RULES_SETTINGS_NAME, RULES_SETTINGS_KEY, setting.toString());
+        userRules = new ArrayList<>(rules);
+    }
+
+    /**
+     * @return a list of the user's rules as ExternalViewerRule objects
+     */
+    synchronized List<ExternalViewerRule> getUserRules() {
+        // ExternalViewerRule objects are immutable
+        return new ArrayList<>(userRules);
+    }
+
+    /**
+     * Finds the executable path associated with a rule name (MIME type or
+     * extension). Returns an empty string if the rule name is not found.
+     * @param name of MIME type or extension.
+     * @return the associated executable absolute path.
+     */
+    synchronized String getExePathForName(String name) {
+        for (ExternalViewerRule rule : userRules) {
+            if (rule.getName().equals(name)) {
+                return rule.getExePath();
+            }
+        }
+        return "";
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/external-viewer-rules-32x32.png b/Core/src/org/sleuthkit/autopsy/directorytree/external-viewer-rules-32x32.png
new file mode 100755
index 0000000000000000000000000000000000000000..fa5d1bc5c3311a87273a9a88d6168ac9fc291e88
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/external-viewer-rules-32x32.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleFactory.java
index bb2c245a27b2a6a8cb78a26a7a54b2fbc1dd5def..f53a46a3d13cf9d3ea9a27f3bd190d5673cea5cb 100755
--- a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleFactory.java
@@ -215,7 +215,7 @@ public boolean hasIngestJobSettingsPanel() {
      * implementation of this method that throws an
      * UnsupportedOperationException.
      *
-     * @param setting Per ingest job settings to initialize the panel.
+     * @param settings Per ingest job settings to initialize the panel.
      *
      * @return An ingest job settings panel.
      */
diff --git a/Core/src/org/sleuthkit/autopsy/images/accounts.png b/Core/src/org/sleuthkit/autopsy/images/accounts.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a53d894dcd70631d2cf8d7e54eab9e90a9eed58
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/accounts.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/images/bank.png b/Core/src/org/sleuthkit/autopsy/images/bank.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d1cca358caa53068c5c8e358edb887916c38461
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/bank.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/images/credit-card-green.png b/Core/src/org/sleuthkit/autopsy/images/credit-card-green.png
new file mode 100644
index 0000000000000000000000000000000000000000..a35ae18a187503d13f5707698d3d1e7a00404172
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/credit-card-green.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/images/credit-card.png b/Core/src/org/sleuthkit/autopsy/images/credit-card.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8d6624d769f62b8064111348a77f33f8dfbec43
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/credit-card.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/images/credit-cards.png b/Core/src/org/sleuthkit/autopsy/images/credit-cards.png
new file mode 100644
index 0000000000000000000000000000000000000000..44f28404f49a5e83717cced62a699dfcbd8e5aa1
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/credit-cards.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/FileIngestPipeline.java b/Core/src/org/sleuthkit/autopsy/ingest/FileIngestPipeline.java
index 9f1cc0aaa52a479ecaf7cbc71ce1a864c19678cb..436418712a1db7bbb834faa52379d44be4ea0d13 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/FileIngestPipeline.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/FileIngestPipeline.java
@@ -209,25 +209,16 @@ String getDisplayName() {
             return displayName;
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void startUp(IngestJobContext context) throws IngestModuleException {
             module.startUp(context);
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public IngestModule.ProcessResult process(AbstractFile file) {
             return module.process(file);
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void shutDown() {
             module.shutDown();
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java
index 827d11a0698948ecc38243563619f11ae1daf49b..4393f185100a6ba40771a3912f2337dda2031e7c 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java
@@ -65,7 +65,7 @@ public String getDisplayName() {
     private final static AtomicLong nextId = new AtomicLong(0L);
     private final long id;
     private final Map<Long, DataSourceIngestJob> dataSourceJobs;
-    private final AtomicInteger incompleteJobsCount;    
+    private final AtomicInteger incompleteJobsCount;
     private volatile CancellationReason cancellationReason;
 
     /**
@@ -423,9 +423,9 @@ public static class DataSourceIngestModuleHandle {
          * used to get basic information about the module and to request
          * cancellation of the module.
          *
-         * @param DataSourceIngestJob The data source ingest job that owns the
-         *                            data source level ingest module.
-         * @param module              The data source level ingest module.
+         * @param job    The data source ingest job that owns the data source
+         *               level ingest module.
+         * @param module The data source level ingest module.
          */
         private DataSourceIngestModuleHandle(DataSourceIngestJob job, DataSourceIngestPipeline.PipelineModule module) {
             this.job = job;
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java
index d2bc1a98205337d4a7a7e73e04e157b796938646..e1b2ae916385b36351471de6bb9739fd8e18ee5b 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java
@@ -514,7 +514,7 @@ public synchronized IngestJobStartResult beginIngestJob(Collection<Content> data
      *
      * @return The ingest job that was started on success or null on failure.
      *
-     * @Deprecated. Use beginIngestJob() instead.
+     * @deprecated. Use beginIngestJob() instead.
      */
     @Deprecated
     public synchronized IngestJob startIngestJob(Collection<Content> dataSources, IngestJobSettings settings) {
@@ -1044,9 +1044,6 @@ private static final class PublishEventTask implements Runnable {
             this.publisher = publisher;
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void run() {
             publisher.publish(event);
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessage.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessage.java
index 5819a2f4241824a358edcbc0dad36eae41b73dec..50253e5b89f49db91a138618488eaa4de566e551 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessage.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessage.java
@@ -192,8 +192,6 @@ public static IngestMessage createMessage(MessageType messageType, String source
     /**
      * Create a simple message with a subject only
      *
-     * @param ID          ID of the message, unique in the context of module
-     *                    that generated it
      * @param messageType message type
      * @param source      originating module
      * @param subject     message subject to be displayed
@@ -227,8 +225,6 @@ public static IngestMessage createErrorMessage(String source, String subject, St
     /**
      * Create warning message
      *
-     * @param ID          ID of the message, unique in the context of module
-     *                    that generated it
      * @param source      originating module
      * @param subject     message subject to be displayed
      * @param detailsHtml html formatted detailed message (without leading and
@@ -248,8 +244,6 @@ public static IngestMessage createWarningMessage(String source, String subject,
 
     /**
      *
-     * @param ID          ID of the message, unique in the context of module
-     *                    that generated it
      * @param source      originating module
      * @param subject     message subject to be displayed
      * @param detailsHtml html formatted detailed message (without leading and
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactory.java
index 470d72855805a58a101c40a22a4db91ce88ab721..59b5b64d9c59f6cf144623c343a4212eb92eb18c 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactory.java
@@ -49,7 +49,7 @@
  * implementations must be marked with the following NetBeans Service provider
  * annotation:
  *
- * @ServiceProvider(service=IngestModuleFactory.class)
+ * <pre>@ServiceProvider(service=IngestModuleFactory.class)</pre>
  * <p>
  * IMPORTANT TIP: If an implementation of IngestModuleFactory does not need to
  * provide implementations of all of the IngestModuleFactory methods, it can
@@ -153,7 +153,7 @@ public interface IngestModuleFactory {
      * implementation of this method that throws an
      * UnsupportedOperationException.
      *
-     * @param setting Per ingest job settings to initialize the panel.
+     * @param settings Per ingest job settings to initialize the panel.
      *
      * @return An ingest job settings panel.
      */
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryAdapter.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryAdapter.java
index 14beecd05da5d58536c28f18e8ed2858f4b8d7d6..46a7427f026c747ba2cf1f34f027e4b14376ea65 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryAdapter.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryAdapter.java
@@ -54,7 +54,7 @@ public boolean hasIngestJobSettingsPanel() {
     }
 
     @Override
-    public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings ingestOptions) {
+    public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java
index bc3725ecbd40a702b3b7fc3aa3c5e231efcbb061..8310d3629723362fee12a82b030a8f373046b415 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestServices.java
@@ -71,7 +71,7 @@ public SleuthkitCase getCurrentSleuthkitCaseDb() {
      * Get a logger that incorporates the display name of an ingest module in
      * messages written to the Autopsy log files.
      *
-     * @param moduleClassName The display name of the ingest module.
+     * @param moduleDisplayName The display name of the ingest module.
      *
      * @return The custom logger for the ingest module.
      */
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
index 766891ae8606dd94c4d51fd40d1e22af5d01c2a2..2a56c71c85286c1a8c759ad56f9e635eb922265d 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
@@ -639,9 +639,6 @@ static AbstractFilePriority.Priority getPriority(final AbstractFile abstractFile
      */
     private final class DataSourceIngestTaskQueue implements IngestTaskQueue {
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public IngestTask getNextTask() throws InterruptedException {
             return IngestTasksScheduler.this.pendingDataSourceTasks.take();
@@ -654,9 +651,6 @@ public IngestTask getNextTask() throws InterruptedException {
      */
     private final class FileIngestTaskQueue implements IngestTaskQueue {
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public IngestTask getNextTask() throws InterruptedException {
             FileIngestTask task = IngestTasksScheduler.this.pendingFileTasks.takeFirst();
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java
index f793e33e07043d5ed7c517f46bb087f06bc5eab6..7846f122f30827056e06968ba003b57261dd95a2 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/events/BlackboardPostEvent.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,7 +31,6 @@
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
 import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 import org.sleuthkit.datamodel.TskCoreException;
 
 /**
@@ -48,8 +47,8 @@ public final class BlackboardPostEvent extends AutopsyEvent implements Serializa
      * Constructs an event to be published when new content is added to a case
      * or there is a change a recorded attribute of existing content.
      *
-     * @param contentEvent A ModuleDataEvent object containing the data
-     *                     associated with the blackboard post.
+     * @param eventData A ModuleDataEvent object containing the data associated
+     *                  with the blackboard post.
      */
     public BlackboardPostEvent(ModuleDataEvent eventData) {
         /**
@@ -61,11 +60,11 @@ public BlackboardPostEvent(ModuleDataEvent eventData) {
          */
         super(
                 IngestManager.IngestModuleEvent.DATA_ADDED.toString(),
-                new SerializableEventData(eventData.getModuleName(), eventData.getBlackboardArtifactType() , eventData.getArtifacts() != null
-                                ? eventData.getArtifacts()
-                                .stream()
-                                .map(BlackboardArtifact::getArtifactID)
-                                .collect(Collectors.toList()) : Collections.emptyList()),
+                new SerializableEventData(eventData.getModuleName(), eventData.getBlackboardArtifactType(), eventData.getArtifacts() != null
+                        ? eventData.getArtifacts()
+                        .stream()
+                        .map(BlackboardArtifact::getArtifactID)
+                        .collect(Collectors.toList()) : Collections.emptyList()),
                 null
         );
         this.eventData = eventData;
diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
index e6923d9674c018d4c772ee5a574acbdd12ac0e20..900329d3cbd806d2b7c8de837e75130fff686e78 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java
@@ -793,6 +793,7 @@ UnpackedNode addNode(String filePath) {
         /**
          * recursive method that traverses the path
          *
+         * @param parent
          * @param tokenPath
          *
          * @return
diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java
index 07d2a9a5bd66c5c942bfd06c7a0be1d9ba2cfe1d..80fd6280db0280e5c15da289ac4e9f4a29f8a85b 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java
@@ -72,7 +72,7 @@ private static class IngestJobTotals {
      * Update the match time total and increment num of files for this job
      *
      * @param ingestJobId
-     * @param matchTimeInc amount of time to add
+     * @param processTimeInc amount of time to add
      */
     private static synchronized void addToTotals(long ingestJobId, long processTimeInc) {
         IngestJobTotals ingestJobTotals = totalsForIngestJobs.get(ingestJobId);
diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form
index 77189d6ef0f2c098fd77dbad047ced5cb182fee1..8c34097b988074bd1ab0ba7ba653b6554ac21c3a 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.form
@@ -16,12 +16,12 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Component id="jPanel1" alignment="0" pref="838" max="32767" attributes="0"/>
+          <Component id="jPanel1" alignment="0" pref="817" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Component id="jPanel1" alignment="0" pref="500" max="32767" attributes="0"/>
+          <Component id="jPanel1" alignment="0" pref="526" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
   </Layout>
@@ -38,7 +38,7 @@
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jScrollPane1" max="32767" attributes="0"/>
+                  <Component id="jScrollPane1" pref="797" max="32767" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
               </Group>
           </Group>
@@ -47,7 +47,7 @@
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" alignment="0" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jScrollPane1" max="32767" attributes="0"/>
+                  <Component id="jScrollPane1" pref="504" max="32767" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
               </Group>
           </Group>
@@ -79,6 +79,7 @@
                           <Group type="102" attributes="0">
                               <EmptySpace max="-2" attributes="0"/>
                               <Group type="103" groupAlignment="0" attributes="0">
+                                  <Component id="jScrollPane2" alignment="0" pref="0" max="32767" attributes="0"/>
                                   <Group type="102" attributes="0">
                                       <Group type="103" groupAlignment="0" attributes="0">
                                           <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
@@ -90,7 +91,6 @@
                                       </Group>
                                       <EmptySpace min="0" pref="191" max="32767" attributes="0"/>
                                   </Group>
-                                  <Component id="jScrollPane2" alignment="0" pref="409" max="32767" attributes="0"/>
                               </Group>
                               <EmptySpace max="-2" attributes="0"/>
                           </Group>
@@ -102,7 +102,7 @@
                               <EmptySpace min="-2" max="-2" attributes="0"/>
                               <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
                               <EmptySpace max="-2" attributes="0"/>
-                              <Component id="jScrollPane2" pref="401" max="32767" attributes="0"/>
+                              <Component id="jScrollPane2" pref="427" max="32767" attributes="0"/>
                               <EmptySpace max="-2" attributes="0"/>
                               <Group type="103" groupAlignment="3" attributes="0">
                                   <Component id="newTypeButton" alignment="3" min="-2" max="-2" attributes="0"/>
@@ -175,7 +175,7 @@
                   <Layout>
                     <DimensionLayout dim="0">
                       <Group type="103" groupAlignment="0" attributes="0">
-                          <Group type="102" alignment="0" attributes="0">
+                          <Group type="102" attributes="0">
                               <EmptySpace max="-2" attributes="0"/>
                               <Group type="103" groupAlignment="0" attributes="0">
                                   <Component id="jScrollPane3" pref="0" max="32767" attributes="0"/>
@@ -188,7 +188,7 @@
                                               <Component id="removeExtButton" min="-2" max="-2" attributes="0"/>
                                           </Group>
                                       </Group>
-                                      <EmptySpace min="0" pref="40" max="32767" attributes="0"/>
+                                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
                                   </Group>
                               </Group>
                               <EmptySpace max="-2" attributes="0"/>
@@ -201,7 +201,7 @@
                               <EmptySpace max="-2" attributes="0"/>
                               <Component id="extHeaderLabel" min="-2" max="-2" attributes="0"/>
                               <EmptySpace max="-2" attributes="0"/>
-                              <Component id="jScrollPane3" pref="401" max="32767" attributes="0"/>
+                              <Component id="jScrollPane3" pref="427" max="32767" attributes="0"/>
                               <EmptySpace max="-2" attributes="0"/>
                               <Group type="103" groupAlignment="3" attributes="0">
                                   <Component id="newExtButton" alignment="3" min="-2" max="-2" attributes="0"/>
diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java
index eb9f6d1b11f5ddc9110625f86cafd5882f3b82df..1d5470b4df5efddd83e2e1cbfe0ea764504b88eb 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java
@@ -188,6 +188,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
             .addGroup(mimePanelLayout.createSequentialGroup()
                 .addContainerGap()
                 .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                     .addGroup(mimePanelLayout.createSequentialGroup()
                         .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                             .addComponent(jLabel1)
@@ -195,8 +196,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                                 .addComponent(newTypeButton)
                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                 .addComponent(removeTypeButton)))
-                        .addGap(0, 191, Short.MAX_VALUE))
-                    .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 409, Short.MAX_VALUE))
+                        .addGap(0, 191, Short.MAX_VALUE)))
                 .addContainerGap())
         );
         mimePanelLayout.setVerticalGroup(
@@ -205,7 +205,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                 .addContainerGap()
                 .addComponent(jLabel1)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 401, Short.MAX_VALUE)
+                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(mimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(newTypeButton)
@@ -251,7 +251,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                                 .addComponent(newExtButton)
                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                 .addComponent(removeExtButton)))
-                        .addGap(0, 40, Short.MAX_VALUE)))
+                        .addGap(0, 0, Short.MAX_VALUE)))
                 .addContainerGap())
         );
         extensionPanelLayout.setVerticalGroup(
@@ -260,7 +260,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                 .addContainerGap()
                 .addComponent(extHeaderLabel)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 401, Short.MAX_VALUE)
+                .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 427, Short.MAX_VALUE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(extensionPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(newExtButton)
@@ -278,14 +278,14 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel1Layout.createSequentialGroup()
                 .addContainerGap()
-                .addComponent(jScrollPane1)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 797, Short.MAX_VALUE)
                 .addContainerGap())
         );
         jPanel1Layout.setVerticalGroup(
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel1Layout.createSequentialGroup()
                 .addContainerGap()
-                .addComponent(jScrollPane1)
+                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
                 .addContainerGap())
         );
 
@@ -293,11 +293,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         this.setLayout(layout);
         layout.setHorizontalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE)
+            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 817, Short.MAX_VALUE)
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)
+            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 526, Short.MAX_VALUE)
         );
     }// </editor-fold>//GEN-END:initComponents
 
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties
index 4dfed1d15ba2f9ba6a72a77bf95e1e69827f4e9b..7225dbedf618775a02b57e16255316549122375b 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties
@@ -28,12 +28,10 @@ FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.message
 FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.title=Missing Interesting Files Set Name
 FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title=Save Failed
 FileTypeIdGlobalSettingsPanel.JOptionPane.loadFailed.title=Load Failed
-FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to file type definitions when ingest is running!
 FileTypeIdGlobalSettingsPanel.loadFileTypes.errorMessage=Failed to load existing file type definitions.
 FileTypeIdGlobalSettingsPanel.saveFileTypes.errorMessage=Failed to save file type definitions.
 FileTypeIdGlobalSettingsPanel.newTypeButton.text=New Type
 FileTypeIdGlobalSettingsPanel.jLabel2.text=Custom MIME Types:
-FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy can automatically detect many file types. Add your custom file types here.
 FileTypeIdGlobalSettingsPanel.startUp.fileTypeDetectorInitializationException.msg=Error initializing the file type detector.
 AddFileTypeSignaturePanel.offsetLabel.text=Byte Offset
 AddFileTypeSignaturePanel.signatureTextField.text=
@@ -53,3 +51,5 @@ AddFileTypePanel.addSigButton.text=Add Signature
 AddFileTypePanel.postHitCheckBox.text=Alert as an "Interesting File" when found
 AddFileTypePanel.setNameLabel.text=Set Name
 AddFileTypePanel.setNameTextField.text=
+FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to file type definitions when ingest is running!
+FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy can automatically detect many file types. Add your custom file types here.
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties
index ef48c700dc1904bbe561828d28fec2ce5ef1ad63..61626e69aabe010aaeb6c89316a6ed459a1aed8c 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties
@@ -9,7 +9,6 @@ FileTypeIdModuleFactory.createFileIngestModule.exception.msg=\u8a2d\u5b9a\u3092\
 FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText=\u65e2\u77e5\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u6570\u306b\u3088\u3063\u3066\u306f\u3001\u3053\u306e\u30dc\u30c3\u30af\u30b9\u3092\u9078\u629e\u3059\u308b\u3053\u3068\u306b\u3088\u308a\u3001\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u306e\u7279\u5b9a\u3092\u52a0\u901f\u3057\u307e\u3059\u3002
 FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text=\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\uff08NSRL\uff09\u3092\u30b9\u30ad\u30c3\u30d7
 FileTypeIdGlobalSettingsPanel.deleteTypeButton.text=\u524a\u9664
-FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u5b9f\u884c\u4e2d\u306b\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\uff01
 FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.title=\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\u540d\u304c\u6b20\u3051\u3066\u3044\u307e\u3059
 FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.message=MIME\u30bf\u30a4\u30d7\u304c\u5fc5\u8981\u3067\u3059\u3002
 FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.title=MIME\u30bf\u30a4\u30d7\u304c\u6b20\u3051\u3066\u3044\u307e\u3059
@@ -33,10 +32,11 @@ FileTypeIdGlobalSettingsPanel.offsetComboBox.startItem=\u958b\u59cb
 FileTypeIdGlobalSettingsPanel.offsetComboBox.endItem=\u505c\u6b62
 FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.length=\u30aa\u30d5\u30bb\u30c3\u30c8\u306f\u30b7\u30b0\u30cd\u30c1\u30e3\u30b5\u30a4\u30ba\u3088\u308a\u5c0f\u3055\u304f\u3066\u306f\u3044\u3051\u307e\u305b\u3093\u3002
 FileTypeIdGlobalSettingsPanel.jLabel2.text=MIME\u30bf\u30a4\u30d7\uff1a
-FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy\u306f\u81ea\u52d5\u7684\u306b\u591a\u304f\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u3092\u691c\u77e5\u3067\u304d\u307e\u3059\u3002\u3053\u3053\u306b\u306f\u3042\u306a\u305f\u306e\u30ab\u30b9\u30bf\u30e0\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 FileTypeIdGlobalSettingsPanel.startUp.fileTypeDetectorInitializationException.msg=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u30c7\u30a3\u30c6\u30af\u30bf\u3092\u8d77\u52d5\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
 AddFileTypeSignaturePanel.signatureTypeLabel.text=\u30b7\u30b0\u30cd\u30c1\u30e3\u30bf\u30a4\u30d7
 AddFileTypeSignaturePanel.signatureLabel.text=\u30b7\u30b0\u30cd\u30c1\u30e3
 AddFileTypeSignaturePanel.offsetRelativeToLabel.text=\u30aa\u30d5\u30bb\u30c3\u30c8\u306f\u6b21\u3068\u76f8\u5bfe\u7684
 AddFileTypeSignaturePanel.offsetLabel.text=\u30d0\u30a4\u30c8\u30aa\u30d5\u30bb\u30c3\u30c8
 AddFileTypePanel.mimeTypeLabel.text=MIME\u30bf\u30a4\u30d7
+FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u5b9f\u884c\u4e2d\u306b\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\uff01
+FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy\u306f\u81ea\u52d5\u7684\u306b\u591a\u304f\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u3092\u691c\u77e5\u3067\u304d\u307e\u3059\u3002\u3053\u3053\u306b\u306f\u3042\u306a\u305f\u306e\u30ab\u30b9\u30bf\u30e0\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java
index 4e06ee7d7462ecaac708ce2c70b1e1286ee0a761..22b064dca6b5dbe28a0f52fec9ba76f4acc85c33 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java
@@ -372,7 +372,7 @@ private static List<FileType> readSerializedFileTypes(String filePath) throws Cu
                 return filesSetsSettings.getUserDefinedFileTypes();
             }
         } catch (IOException | ClassNotFoundException ex) {
-            throw new CustomFileTypesException(String.format("Failed to read ssettings from %s", filePath), ex); //NON-NLS
+            throw new CustomFileTypesException(String.format("Failed to read settings from %s", filePath), ex); //NON-NLS
         }
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java
index f0b8e068c10cfc84728fd4259d0b0ecd12b866a5..378c758f16c10fb1ff1897c384e345358337ecee 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java
@@ -95,7 +95,7 @@ public List<String> getUserDefinedTypes() {
      * @return True or false.
      */
     public boolean isDetectable(String mimeType) {
-        return isDetectableAsCustomType(userDefinedFileTypes, mimeType) 
+        return isDetectableAsCustomType(userDefinedFileTypes, mimeType)
                 || isDetectableAsCustomType(autopsyDefinedFileTypes, mimeType)
                 || isDetectableByTika(mimeType);
     }
@@ -104,7 +104,8 @@ public boolean isDetectable(String mimeType) {
      * Determines whether or not a given MIME type is detectable as a
      * user-defined MIME type by this detector.
      *
-     * @param mimeType The MIME type name (e.g., "text/html").
+     * @param customTypes
+     * @param mimeType    The MIME type name (e.g., "text/html").
      *
      * @return True or false.
      */
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java
index 801866ff84099d9b919993e4704a57492b7f6504..5c02d69424625db1b4d354b9e6f03e775ad6f192 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java
@@ -71,9 +71,6 @@ public static boolean isMimeTypeDetectable(String mimeType) {
     FileTypeIdIngestModule() {
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void startUp(IngestJobContext context) throws IngestModuleException {
         jobId = context.getJobId();
@@ -85,9 +82,6 @@ public void startUp(IngestJobContext context) throws IngestModuleException {
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public ProcessResult process(AbstractFile file) {
 
@@ -107,9 +101,6 @@ public ProcessResult process(AbstractFile file) {
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void shutDown() {
         /**
diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java
index faaa097bfef39f4ac773849fdbd89783d9819c40..fd5b100e54537551ec41ccd581c46ab1ff869b0c 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java
@@ -42,13 +42,13 @@ public class TikaFileTypeDetector {
     /**
      * Detect the mime type of the passed in file and save it to the blackboard
      *
-     * @deprecated Use FileTypeDetector.detectAndPostToBlackboard(AbstractFile
-     * file) instead.
      * @param abstractFile
      *
      * @return mime type or null
      *
      * @throws TskCoreException
+     * @deprecated Use FileTypeDetector.detectAndPostToBlackboard(AbstractFile
+     * file) instead.
      */
     @Deprecated
     public synchronized String detectAndSave(AbstractFile abstractFile) throws TskCoreException {
@@ -67,10 +67,11 @@ public synchronized String detectAndSave(AbstractFile abstractFile) throws TskCo
     /**
      * Detect the mime type of the passed in file
      *
-     * @deprecated Use FileTypeDetector.detect(AbstractFile file) instead.
      * @param abstractFile
      *
      * @return mime type of detected format or null
+     *
+     * @deprecated Use FileTypeDetector.detect(AbstractFile file) instead.
      */
     @Deprecated
     public synchronized String detect(AbstractFile abstractFile) {
diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.form
index 9e4f54df438225054abf91499bb31a3e03cb2547..c24125cec4af992cf9edf84592256d6502e5ebd8 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.form
@@ -51,14 +51,6 @@
       </Properties>
     </Component>
   </NonVisualComponents>
-  <Properties>
-    <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
-      <Dimension value="[700, 500]"/>
-    </Property>
-    <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
-      <Dimension value="[750, 500]"/>
-    </Property>
-  </Properties>
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@@ -74,15 +66,12 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Component id="jScrollPane2" alignment="0" max="32767" attributes="0"/>
+          <Component id="jScrollPane2" alignment="0" pref="789" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" alignment="0" attributes="0">
-              <Component id="jScrollPane2" max="32767" attributes="0"/>
-              <EmptySpace max="-2" attributes="0"/>
-          </Group>
+          <Component id="jScrollPane2" alignment="0" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
   </Layout>
@@ -156,7 +145,7 @@
                           <Group type="102" attributes="0">
                               <Group type="103" groupAlignment="0" attributes="0">
                                   <Component id="hashDatabasesLabel" min="-2" max="-2" attributes="0"/>
-                                  <Group type="102" alignment="0" attributes="0">
+                                  <Group type="102" attributes="0">
                                       <Component id="createDatabaseButton" min="-2" pref="121" max="-2" attributes="0"/>
                                       <EmptySpace max="-2" attributes="0"/>
                                       <Component id="importDatabaseButton" min="-2" pref="132" max="-2" attributes="0"/>
@@ -167,7 +156,6 @@
                               <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
                           </Group>
                       </Group>
-                      <EmptySpace max="-2" attributes="0"/>
                   </Group>
               </Group>
             </DimensionLayout>
@@ -225,8 +213,9 @@
                               <Component id="sendIngestMessagesCheckBox" min="-2" max="-2" attributes="0"/>
                               <EmptySpace type="separate" max="-2" attributes="0"/>
                               <Component id="ingestWarningLabel" min="-2" max="-2" attributes="0"/>
+                              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
                           </Group>
-                          <Component id="jScrollPane1" min="-2" pref="423" max="-2" attributes="0"/>
+                          <Component id="jScrollPane1" max="32767" attributes="0"/>
                       </Group>
                       <EmptySpace max="-2" attributes="0"/>
                       <Group type="103" groupAlignment="3" attributes="0">
@@ -234,7 +223,7 @@
                           <Component id="importDatabaseButton" alignment="3" min="-2" max="-2" attributes="0"/>
                           <Component id="deleteDatabaseButton" alignment="3" min="-2" max="-2" attributes="0"/>
                       </Group>
-                      <EmptySpace pref="29" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
                   </Group>
               </Group>
             </DimensionLayout>
diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java
index 83c142bef70009f2c5298c6fa2348ece1495765e..6954a9d77fc036c64626405e5834ed242120c7cd 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java
@@ -527,9 +527,6 @@ private void initComponents() {
         jButton3.setFont(jButton3.getFont().deriveFont(jButton3.getFont().getStyle() & ~java.awt.Font.BOLD, 14));
         org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.jButton3.text")); // NOI18N
 
-        setMinimumSize(new java.awt.Dimension(700, 500));
-        setPreferredSize(new java.awt.Dimension(750, 500));
-
         ingestWarningLabel.setFont(ingestWarningLabel.getFont().deriveFont(ingestWarningLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
         ingestWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/hashdatabase/warning16.png"))); // NOI18N
         org.openide.awt.Mnemonics.setLocalizedText(ingestWarningLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.ingestWarningLabel.text")); // NOI18N
@@ -712,8 +709,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                                 .addComponent(importDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE)
                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                 .addComponent(deleteDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE)))
-                        .addGap(0, 0, Short.MAX_VALUE)))
-                .addContainerGap())
+                        .addGap(0, 0, Short.MAX_VALUE))))
         );
         jPanel1Layout.setVerticalGroup(
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -759,14 +755,15 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                         .addGap(18, 18, 18)
                         .addComponent(sendIngestMessagesCheckBox)
                         .addGap(18, 18, 18)
-                        .addComponent(ingestWarningLabel))
-                    .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 423, javax.swing.GroupLayout.PREFERRED_SIZE))
+                        .addComponent(ingestWarningLabel)
+                        .addGap(0, 0, Short.MAX_VALUE))
+                    .addComponent(jScrollPane1))
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(createDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                     .addComponent(importDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                     .addComponent(deleteDatabaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                .addContainerGap(29, Short.MAX_VALUE))
+                .addContainerGap())
         );
 
         jScrollPane2.setViewportView(jPanel1);
@@ -775,13 +772,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         this.setLayout(layout);
         layout.setHorizontalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addComponent(jScrollPane2)
+            .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 789, Short.MAX_VALUE)
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(layout.createSequentialGroup()
-                .addComponent(jScrollPane2)
-                .addContainerGap())
+            .addComponent(jScrollPane2)
         );
     }// </editor-fold>//GEN-END:initComponents
 
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java
index 261b23a3da9d83c5144580c90625218dd4957a69..bab3117d3a13acbd6541fa7b7da1125217eb2165 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java
@@ -114,9 +114,6 @@ private FilesIdentifierIngestJobSettingsPanel(FilesIdentifierIngestJobSettings s
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public IngestModuleIngestJobSettings getSettings() {
         List<String> enabledInterestingFilesSets = new ArrayList<>();
@@ -131,9 +128,6 @@ public IngestModuleIngestJobSettings getSettings() {
         return new FilesIdentifierIngestJobSettings(enabledInterestingFilesSets, disabledInterestingFilesSets);
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public void update(Observable o, Object arg
     ) {
@@ -195,25 +189,16 @@ void resetTableData(List<FilesSetRow> filesSetRows) {
             this.fireTableDataChanged();
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public int getRowCount() {
             return this.filesSetRows.size();
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public int getColumnCount() {
             return 2;
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public Object getValueAt(int rowIndex, int columnIndex) {
             if (columnIndex == 0) {
@@ -223,17 +208,11 @@ public Object getValueAt(int rowIndex, int columnIndex) {
             }
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public boolean isCellEditable(int rowIndex, int columnIndex) {
             return (columnIndex == 0);
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
             if (columnIndex == 0) {
@@ -241,9 +220,6 @@ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
             }
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public Class<?> getColumnClass(int c) {
             return getValueAt(0, c).getClass();
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java
index f207e720d25524eae7603841115ec459c917fb08..7b196d17b2c4c2f8c7c1064e5b028b50bfcab1ff 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java
@@ -251,9 +251,6 @@ boolean isSatisfied(AbstractFile file) {
             return true;
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public String toString() {
             // This override is designed to provide a display name for use with 
@@ -327,9 +324,6 @@ static final class MimeTypeCondition implements FileAttributeCondition {
                 this.mimeType = mimeType;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean passes(AbstractFile file) {
                 return this.mimeType.equals(file.getMIMEType());
@@ -517,9 +511,6 @@ enum Type {
                 this.type = type;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean passes(AbstractFile file) {
                 switch (this.type) {
@@ -639,9 +630,6 @@ public boolean textMatches(String textToMatch) {
                 return this.textMatcher.textMatches(textToMatch);
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public abstract boolean passes(AbstractFile file);
 
@@ -674,9 +662,6 @@ static final class ParentPathCondition extends AbstractTextCondition {
                 super(path);
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean passes(AbstractFile file) {
                 return this.textMatches(file.getParentPath() + "/");
@@ -719,9 +704,6 @@ static final class FullNameCondition extends AbstractTextCondition implements Fi
                 super(name);
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean passes(AbstractFile file) {
                 return this.textMatches(file.getName());
@@ -760,9 +742,6 @@ static final class ExtensionCondition extends AbstractTextCondition implements F
                 super(extension.pattern(), false);
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean passes(AbstractFile file) {
                 return this.textMatches(file.getNameExtension());
@@ -795,7 +774,7 @@ private static interface TextMatcher extends Serializable {
             /**
              * Determines whether a string of text is matched.
              *
-             * @param textToMatch The text string.
+             * @param subject The text string.
              *
              * @return True if the text matches, false otherwise.
              */
@@ -821,25 +800,16 @@ private static class CaseInsensitiveStringComparisionMatcher implements TextMatc
                 this.textToMatch = textToMatch;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public String getTextToMatch() {
                 return this.textToMatch;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean isRegex() {
                 return false;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean textMatches(String subject) {
                 return subject.equalsIgnoreCase(textToMatch);
@@ -867,25 +837,16 @@ private static class CaseInsensitivePartialStringComparisionMatcher implements T
                 this.pattern = Pattern.compile(Pattern.quote(textToMatch), Pattern.CASE_INSENSITIVE);
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public String getTextToMatch() {
                 return this.textToMatch;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean isRegex() {
                 return false;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean textMatches(String subject) {
                 return pattern.matcher(subject).find();
@@ -910,25 +871,16 @@ private static class RegexMatcher implements TextMatcher {
                 this.regex = regex;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public String getTextToMatch() {
                 return this.regex.pattern();
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean isRegex() {
                 return true;
             }
 
-            /**
-             * @inheritDoc
-             */
             @Override
             public boolean textMatches(String subject) {
                 // A single match is sufficient.
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java
index 20d6c1e5c7afc5c5adb7ea8a4ea8ce6b7cfcec89..95ab09eff1d0b2d43eeb37104d941c029532b03f 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java
@@ -294,8 +294,6 @@ private static void readFilesSet(Element setElem, Map<String, FilesSet> filesSet
          * Construct an interesting files set file name rule from the data in an
          * XML element.
          *
-         * @param filePath The path of the definitions file.
-         * @param setName  The name of the files set.
          * @param elem     The file name rule XML element.
          *
          * @return A file name rule, or null if there is an error (the error is
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form
index 483d6e386e96d85a71ab157acf014b4e1dad0252..b4c3076206be49df9075ad5f3dbd334778ff9285 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form
@@ -29,7 +29,7 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Component id="jScrollPane1" alignment="0" pref="762" max="32767" attributes="0"/>
+          <Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java
index df527b887da7a2f936fe27b904dbc9e66cbb2af3..ea1b2f2fdb77876aef3235d0a86d2b5d88bd76cb 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java
@@ -901,7 +901,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         this.setLayout(layout);
         layout.setHorizontalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 762, Short.MAX_VALUE)
+            .addComponent(jScrollPane1)
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
diff --git a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestModuleFactory.java
index 8498763d10d2a39baf6d358b425e67038eea16e4..0898f20928c3e748cd6f23e06fb55daf33feda0d 100755
--- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestModuleFactory.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2014 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,7 +20,6 @@
 
 import org.openide.util.NbBundle;
 import org.openide.util.lookup.ServiceProvider;
-import org.sleuthkit.autopsy.coreutils.Version;
 import org.sleuthkit.autopsy.ingest.FileIngestModule;
 import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
 import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
@@ -33,8 +32,8 @@
 @ServiceProvider(service = IngestModuleFactory.class)
 public class PhotoRecCarverIngestModuleFactory extends IngestModuleFactoryAdapter {
 
-    private static String VERSION = "7.0";
-    
+    private static final String VERSION = "7.0";
+
     /**
      * Gets the ingest module name for use within this package.
      *
@@ -44,43 +43,29 @@ static String getModuleName() {
         return NbBundle.getMessage(PhotoRecCarverIngestModuleFactory.class, "moduleDisplayName.text");
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleDisplayName() {
         return PhotoRecCarverIngestModuleFactory.getModuleName();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleDescription() {
         return NbBundle.getMessage(PhotoRecCarverIngestModuleFactory.class, "moduleDescription.text");
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleVersionNumber() {
         return VERSION;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public boolean isFileIngestModuleFactory() {
         return true;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
         return new PhotoRecCarverFileIngestModule();
     }
+
 }
diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java
index 697d58c2e797a046e37fbaa909a6c5fb68d40c4f..899de3630cbbdf4f645211cfbff4c7f536e6cea6 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModule.java
@@ -91,8 +91,6 @@ public static synchronized STIXReportModule getDefault() {
     }
 
     /**
-     * .
-     *
      * @param baseReportDir path to save the report
      * @param progressPanel panel to update the report's progress
      */
@@ -172,11 +170,13 @@ public void generateReport(String baseReportDir, ReportProgressPanel progressPan
                 try {
                     processFile(file.getAbsolutePath(), progressPanel, output);
                 } catch (TskCoreException | JAXBException ex) {
-                    logger.log(Level.SEVERE, String.format("Unable to process STIX file %s", file), ex); //NON-NLS
+                    String errMsg = String.format("Unable to process STIX file %s", file);
+                    logger.log(Level.SEVERE, errMsg, ex); //NON-NLS
                     MessageNotifyUtil.Notify.show("STIXReportModule", //NON-NLS
-                            ex.getLocalizedMessage(),
+                            errMsg,
                             MessageNotifyUtil.MessageType.ERROR);
                     hadErrors = true;
+                    break;
                 }
                 // Clear out the ID maps before loading the next file
                 idToObjectMap = new HashMap<String, ObjectType>();
@@ -212,6 +212,7 @@ public void generateReport(String baseReportDir, ReportProgressPanel progressPan
      *
      * @param stixFile      - Name of the file
      * @param progressPanel - Progress panel (for updating)
+     * @param output
      *
      * @throws JAXBException
      * @throws TskCoreException
@@ -279,6 +280,7 @@ private void processObservables(STIXPackage stix) {
      * artifacts.
      *
      * @param stix STIXPackage
+     * @param output
      */
     private void processIndicators(STIXPackage stix, BufferedWriter output) throws TskCoreException {
         if (stix.getIndicators() != null) {
@@ -363,6 +365,7 @@ private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) thro
      *                  indicator
      * @param resultStr - Full results for this indicator
      * @param found     - true if the indicator was found in datasource(s)
+     * @param output
      */
     private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) {
         if (output != null) {
@@ -399,6 +402,7 @@ private void writeResultsToFile(Indicator ind, String resultStr, boolean found,
      * Write the a header for the current file to the output file.
      *
      * @param a_fileName
+     * @param output
      */
     private void printFileHeader(String a_fileName, BufferedWriter output) {
         if (output != null) {
@@ -594,6 +598,7 @@ private ObservableResult evaluateSingleObservable(Observable obs, String spacing
      *
      * @param obj     The object to evaluate against the datasource(s)
      * @param spacing For formatting the output
+     * @param id
      *
      * @return
      */
diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java
index 316cb2b624f5f7a48feb42a0334262d8863e8e4b..8c1aae74ec7e6801b2c0f05f277fae1fe0cdd616 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java
@@ -96,9 +96,6 @@ public void startUp(IngestJobContext context) throws IngestModuleException {
         }
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
 
@@ -327,9 +324,6 @@ private AddDataSourceCallback(Path vmFile) {
             vmDataSources = new ArrayList<>();
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errList, List<Content> content) {
             for (String error : errList) {
@@ -357,9 +351,6 @@ public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, L
             }
         }
 
-        /**
-         * @inheritDoc
-         */
         @Override
         public void doneEDT(DataSourceProcessorResult result, List<String> errList, List<Content> newContents) {
             done(result, errList, newContents);
diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java
index c853dbd488be9dcb0a259311a68a62927d18d9d4..e26bb81e5186bab84e69aadea8f2fb8a8e236272 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java
@@ -41,41 +41,26 @@ static String getModuleName() {
         return NbBundle.getMessage(VMExtractorIngestModuleFactory.class, "VMExtractorIngestModuleFactory.moduleDisplayName");
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleDisplayName() {
         return getModuleName();
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleDescription() {
         return NbBundle.getMessage(this.getClass(), "VMExtractorIngestModuleFactory.moduleDescription");
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public String getModuleVersionNumber() {
         return NbBundle.getMessage(this.getClass(), "VMExtractorIngestModuleFactory.version");
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public boolean isDataSourceIngestModuleFactory() {
         return true;
     }
 
-    /**
-     * @inheritDoc
-     */
     @Override
     public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) {
         return new VMExtractorIngestModule();
diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
index 0f8360231bd39c19930cdf2781fdf75744f269c0..3a7da57e3feab51ed3b027642c086e93cb8e1618 100644
--- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
+++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
@@ -254,6 +254,9 @@ private String useDataTypeIcon(String dataType) {
                 case TSK_REMOTE_DRIVE:
                     in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
                     break;
+                case TSK_ACCOUNT:
+                    in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
+                    break;
                 default:
                     logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = " + dataType); //NON-NLS
                     in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
@@ -261,7 +264,17 @@ private String useDataTypeIcon(String dataType) {
                     iconFilePath = path + File.separator + iconFileName;
                     break;
             }
-        } else {  // no defined artifact found for this dataType 
+        } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
+            /* TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
+             * attribute, with a synthetic compound dataType name, so they are
+             * not caught by the switch statement above. For now we just give
+             * them all the general account icon, but we could do something else
+             * in the future.
+             */
+            in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
+            iconFileName = "accounts.png"; //NON-NLS
+            iconFilePath = path + File.separator + iconFileName;
+        } else {  // no defined artifact found for this dataType
             in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
             iconFileName = "star.png"; //NON-NLS
             iconFilePath = path + File.separator + iconFileName;
@@ -1113,13 +1126,17 @@ private String prepareThumbnail(AbstractFile file) {
         if (thumbFile.exists() == false) {
             return null;
         }
+        File to = new File(thumbsPath);
+        FileObject from = FileUtil.toFileObject(thumbFile);
+        FileObject dest = FileUtil.toFileObject(to);
         try {
-            File to = new File(thumbsPath);
-            FileObject from = FileUtil.toFileObject(thumbFile);
-            FileObject dest = FileUtil.toFileObject(to);
             FileUtil.copyFile(from, dest, thumbFile.getName(), "");
         } catch (IOException ex) {
             logger.log(Level.SEVERE, "Failed to write thumb file to report directory.", ex); //NON-NLS
+        } catch (NullPointerException ex) {
+            logger.log(Level.SEVERE, "NPE generated from FileUtil.copyFile, probably because FileUtil.toFileObject returned null. \n" +
+                    "The File argument for toFileObject was " + thumbFile + " with toString: " + thumbFile.toString() + "\n" +
+                    "The FileObject returned by toFileObject, passed into FileUtil.copyFile, was " + from, ex);
         }
 
         return THUMBS_REL_PATH + thumbFile.getName();
diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java
index 5dd88f680ad697fd0d0fcaabd3a15ef348ba4cdd..f4afd1bd1e415c97b0d81eca4c8dbda2ef335348 100755
--- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java
+++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2013 Basis Technology Corp.
+ * Copyright 2013-16 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,12 +18,16 @@
  */
 package org.sleuthkit.autopsy.report;
 
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimaps;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -43,6 +47,7 @@
 import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.BlackboardArtifactTag;
 import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.BlackboardAttribute.Type;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.ContentTag;
 import org.sleuthkit.datamodel.SleuthkitCase;
@@ -119,10 +124,10 @@ protected void execute() {
      */
     private void makeBlackboardArtifactTables() {
         // Make a comment string describing the tag names filter in effect. 
-        StringBuilder comment = new StringBuilder();
+        String comment = "";
         if (!tagNamesFilter.isEmpty()) {
-            comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text"));
-            comment.append(makeCommaSeparatedList(tagNamesFilter));
+            comment += NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text");
+            comment += makeCommaSeparatedList(tagNamesFilter);
         }
 
         // Add a table to the report for every enabled blackboard artifact type.
@@ -139,10 +144,10 @@ private void makeBlackboardArtifactTables() {
 
             // Keyword hits and hashset hit artifacts get special handling.
             if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
-                writeKeywordHits(tableReport, comment.toString(), tagNamesFilter);
+                writeKeywordHits(tableReport, comment, tagNamesFilter);
                 continue;
             } else if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
-                writeHashsetHits(tableReport, comment.toString(), tagNamesFilter);
+                writeHashsetHits(tableReport, comment, tagNamesFilter);
                 continue;
             }
 
@@ -152,52 +157,90 @@ private void makeBlackboardArtifactTables() {
                 continue;
             }
 
-            /*
-                 Gets all of the attribute types of this artifact type by adding
-                 all of the types to a set
-             */
-            Set<BlackboardAttribute.Type> attrTypeSet = new TreeSet<>((BlackboardAttribute.Type o1, BlackboardAttribute.Type o2) -> o1.getDisplayName().compareTo(o2.getDisplayName()));
-            for (ArtifactData data : artifactList) {
-                List<BlackboardAttribute> attributes = data.getAttributes();
-                for (BlackboardAttribute attribute : attributes) {
-                    attrTypeSet.add(attribute.getAttributeType());
+            /* TSK_ACCOUNT artifacts get grouped by their TSK_ACCOUNT_TYPE
+             * attribute, and then handed off to the standard method for writing
+             * tables. */
+            if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
+                //Group account artifacts by their account type
+                ListMultimap<String, ArtifactData> groupedArtifacts = Multimaps.index(artifactList,
+                        artifactData -> {
+                            try {
+                                return artifactData.getArtifact().getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)).getValueString();
+                            } catch (TskCoreException ex) {
+                                logger.log(Level.SEVERE, "Unable to get value of TSK_ACCOUNT_TYPE attribute. Defaulting to \"unknown\"", ex);
+                                return "unknown";
+                            }
+                        });
+                for (String accountType : groupedArtifacts.keySet()) {
+                    /* If the report is a ReportHTML, the data type name
+                     * eventualy makes it to useDataTypeIcon which expects but
+                     * does not require a artifact name, so we make a synthetic
+                     * compund name by appending a ":" and the account type.
+                     */
+                    final String compundDataTypeName = BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName() + ": " + accountType;
+                    writeTableForDataType(new ArrayList<>(groupedArtifacts.get(accountType)), type, compundDataTypeName, comment);
                 }
+            } else {
+                //all other artifact types are sent to writeTableForDataType directly
+                writeTableForDataType(artifactList, type, type.getDisplayName(), comment);
             }
-            // Get the columns appropriate for the artifact type. This is
-            // used to get the data that will be in the cells below based on
-            // type, and display the column headers.
-            List<Column> columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet);
-            if (columns.isEmpty()) {
-                continue;
-            }
-            columnHeaderMap.put(type.getTypeID(), columns);
-
-            // The artifact list is sorted now, as getting the row data is 
-            // dependent on having the columns, which is necessary for 
-            // sorting.
-            Collections.sort(artifactList);
-            List<String> columnHeaderNames = new ArrayList<>();
-            for (Column currColumn : columns) {
-                columnHeaderNames.add(currColumn.getColumnHeader());
+        }
+    }
+
+    /**
+     *
+     * Write the given list of artifacts to the table for the given type.
+     *
+     * @param artifactList The List of artifacts to include in the table.
+     * @param type         The Type of artifacts included in the table. All the
+     *                     artifacts in artifactList should be of this type.
+     * @param tableName    The name of the table.
+     * @param comment      A comment to put in the header.
+     */
+    private void writeTableForDataType(List<ArtifactData> artifactList, BlackboardArtifact.Type type, String tableName, String comment) {
+        /*
+         * Make a sorted set of all of the attribute types that are on any of
+         * the given artifacts.
+         */
+        Set<BlackboardAttribute.Type> attrTypeSet = new TreeSet<>(Comparator.comparing(BlackboardAttribute.Type::getDisplayName));
+        for (ArtifactData data : artifactList) {
+            List<BlackboardAttribute> attributes = data.getAttributes();
+            for (BlackboardAttribute attribute : attributes) {
+                attrTypeSet.add(attribute.getAttributeType());
             }
+        }
+        /* Get the columns appropriate for the artifact type. This is used to
+         * get the data that will be in the cells below based on type, and
+         * display the column headers.
+         */
+        List<Column> columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet);
+        if (columns.isEmpty()) {
+            return;
+        }
+        columnHeaderMap.put(type.getTypeID(), columns);
 
-            tableReport.startDataType(type.getDisplayName(), comment.toString());
-            tableReport.startTable(columnHeaderNames);
-            for (ArtifactData artifactData : artifactList) {
-                // Get the row data for this artifact, and has the 
-                // module add it.
-                List<String> rowData = artifactData.getRow();
-                if (rowData.isEmpty()) {
-                    continue;
-                }
+        /* The artifact list is sorted now, as getting the row data is dependent
+         * on having the columns, which is necessary for sorting.
+         */
+        Collections.sort(artifactList);
 
-                tableReport.addRow(rowData);
+        tableReport.startDataType(tableName, comment);
+        tableReport.startTable(Lists.transform(columns, Column::getColumnHeader));
+
+        for (ArtifactData artifactData : artifactList) {
+            // Get the row data for this artifact, and has the
+            // module add it.
+            List<String> rowData = artifactData.getRow();
+            if (rowData.isEmpty()) {
+                return;
             }
-            // Finish up this data type
-            progressPanel.increment();
-            tableReport.endTable();
-            tableReport.endDataType();
+
+            tableReport.addRow(rowData);
         }
+        // Finish up this data type
+        progressPanel.increment();
+        tableReport.endTable();
+        tableReport.endDataType();
     }
 
     /**
@@ -1449,6 +1492,12 @@ private List<Column> getArtifactTableColumns(int artifactTypeId, Set<BlackboardA
 
             columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.remotePath"),
                     new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH)));
+        } else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
+            columns.add(new StatusColumn());
+            attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE));
+            attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
+            attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
+            attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID));            
         } else {
             // This is the case that it is a custom type. The reason an else is 
             // necessary is to make sure that the source file column is added
@@ -1562,6 +1611,27 @@ private interface Column {
         Set<BlackboardAttribute.Type> removeTypeFromSet(Set<BlackboardAttribute.Type> types);
     }
 
+    private class StatusColumn implements Column {
+
+        @NbBundle.Messages("TableReportGenerator.StatusColumn.Header=Review Status")
+        @Override
+        public String getColumnHeader() {
+            return Bundle.TableReportGenerator_StatusColumn_Header();
+        }
+
+        @Override
+        public String getCellData(ArtifactData artData) {
+            return artData.getArtifact().getReviewStatus().getDisplayName();
+        }
+
+        @Override
+        public Set<BlackboardAttribute.Type> removeTypeFromSet(Set<BlackboardAttribute.Type> types) {
+            // This column doesn't have a type, so nothing to remove
+            return types;
+        }
+
+    }
+
     private class AttributeColumn implements Column {
 
         private final String columnHeader;
@@ -1621,10 +1691,6 @@ public String getColumnHeader() {
         @Override
         public String getCellData(ArtifactData artData) {
             return getFileUniquePath(artData.getContent());
-            /*else if (this.columnHeader.equals(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))) {
-             return makeCommaSeparatedList(artData.getTags());
-             }
-             return "";*/
         }
 
         @Override
diff --git a/Core/src/org/sleuthkit/autopsy/report/images/accounts.png b/Core/src/org/sleuthkit/autopsy/report/images/accounts.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a53d894dcd70631d2cf8d7e54eab9e90a9eed58
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/accounts.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/report/images/credit-card.png b/Core/src/org/sleuthkit/autopsy/report/images/credit-card.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8d6624d769f62b8064111348a77f33f8dfbec43
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/credit-card.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java
index 11907374c60ed12c0212d85aef8639ef2c6f86e2..9ad1760ef7182e612313139c8bf57785a5bac6b8 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java
@@ -109,7 +109,7 @@
  * listeners should only be accessed with this object's intrinsic lock held, or
  * on the EDT as indicated.
  * </li>
- * <ul>
+ * </ul>
  */
 @NbBundle.Messages({"Timeline.dialogs.title= Timeline",
     "TimeLinecontroller.updateNowQuestion=Do you want to update the events database now?"})
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
index bd72a08da105aab5d5a11794fcd2aa59700331d6..4fb092f0c53bac072c6261f54167733bad5439c2 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
@@ -35,6 +35,7 @@
 import javafx.scene.input.KeyEvent;
 import javafx.scene.layout.Priority;
 import javafx.scene.layout.VBox;
+import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
 import org.controlsfx.control.Notifications;
 import org.joda.time.Interval;
@@ -49,6 +50,7 @@
 import org.openide.windows.TopComponent;
 import static org.openide.windows.TopComponent.PROP_UNDOCKING_DISABLED;
 import org.openide.windows.WindowManager;
+import org.sleuthkit.autopsy.actions.AddBookmarkTagAction;
 import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
 import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
 import org.sleuthkit.autopsy.coreutils.Logger;
@@ -200,6 +202,9 @@ public TimeLineTopComponent(TimeLineController controller) {
         setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
         setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
 
+        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS
+        getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS
+
         this.controller = controller;
 
         //create linked result and content views
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java
index b2a4704aa8155c3c2c481b0b125ab7195f31f2bd..f6600ac0adb0436c681eb40100d385493757556d 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java
@@ -297,9 +297,6 @@ public List<Long> getEventIDs(Interval timeRange, Filter filter) {
      * system events for the same file, with the same timestamp, are combined
      * together.
      *
-     * @param timeRange The Interval that all returned events must be within.
-     * @param filter    The Filter that all returned events must pass.
-     *
      * @return A List of combined events, sorted by timestamp.
      */
     public List<CombinedEvent> getCombinedEvents() {
@@ -380,7 +377,7 @@ public List<EventStripe> getEventStripes() {
     }
 
     /**
-     * @param aggregation
+     * @param params
      *
      * @return a list of aggregated events that are within the requested time
      *         range and pass the requested filter, using the given aggregation
@@ -445,8 +442,8 @@ synchronized public boolean handleArtifactTagDeleted(BlackBoardArtifactTagDelete
      * @return A List of event IDs for the events that are derived from the
      *         given file.
      */
-    public List<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) {
-        return repo.getEventIDsForFile(file, includedDerivedArtifacts);
+    public List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
+        return repo.getEventIDsForFile(file, includeDerivedArtifacts);
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java
index 90ed0148d3474850a1ccd64d604bb7f9ff0de679..d01c509297aa8ce858ef101f9d6221d13e6e2e85 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/ArtifactEventType.java
@@ -61,13 +61,13 @@ public default int getArtifactTypeID() {
 
     /**
      * given an artifact, pull out the time stamp, and compose the descriptions.
-     * Each implementation of {@link ArtifactEventType} needs to implement
-     * parseAttributesHelper() as hook for {@link buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact)
+     * Each implementation of ArtifactEventType needs to implement
+     * parseAttributesHelper() as hook for buildEventDescription(org.sleuthkit.datamodel.BlackboardArtifact)
      * to invoke. Most subtypes can use this default implementation.
      *
      * @param artf
      *
-     * @return an {@link AttributeEventDescription} containing the timestamp
+     * @return an AttributeEventDescription containing the timestamp
      *         and description information
      *
      * @throws TskCoreException
@@ -103,7 +103,7 @@ default AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf)
     /**
      * bundles the per event information derived from a BlackBoard Artifact into
      * one object. Primarily used to have a single return value for
-     * {@link ArtifactEventType#buildEventDescription(ArtifactEventType, BlackboardArtifact)}.
+     * ArtifactEventType#buildEventDescription(ArtifactEventType, BlackboardArtifact).
      */
     static class AttributeEventDescription {
 
@@ -142,17 +142,16 @@ public AttributeEventDescription(long time, String shortDescription,
     }
 
     /**
-     * Build a {@link AttributeEventDescription} derived from a
-     * {@link BlackboardArtifact}. This is a template method that relies on each
-     * {@link ArtifactEventType}'s implementation of
-     * {@link ArtifactEventType#parseAttributesHelper()} to know how to go from
-     * {@link BlackboardAttribute}s to the event description.
+     * Build a AttributeEventDescription derived from a BlackboardArtifact. This
+     * is a template method that relies on each ArtifactEventType's
+     * implementation of ArtifactEventType#parseAttributesHelper() to know how
+     * to go from BlackboardAttributes to the event description.
      *
-     * @param artf the {@link BlackboardArtifact} to derive the event
-     *             description from
+     * @param type
+     * @param artf the BlackboardArtifact to derive the event description from
      *
-     * @return an {@link AttributeEventDescription} derived from the given
-     *         artifact, if the given artifact has no timestamp
+     * @return an AttributeEventDescription derived from the given artifact, if
+     *         the given artifact has no timestamp
      *
      * @throws TskCoreException is there is a problem accessing the blackboard
      *                          data
@@ -205,5 +204,4 @@ static BlackboardAttribute getAttributeSafe(BlackboardArtifact artf, BlackboardA
         }
     }
 
-    
 }
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/RootEventType.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/RootEventType.java
index 1c2d051105f2e171fbb996406a043b39157fa71f..d182976cf9bfe5933361a94a7f05d172a64f5c1b 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/RootEventType.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/eventtype/RootEventType.java
@@ -27,7 +27,7 @@
 import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
 
 /**
- * A singleton {@link } EventType to represent the root type of all event types.
+ * A singleton EventType to represent the root type of all event types.
  */
 public class RootEventType implements EventType {
 
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java
index 00502b191e050223c5fb5316d4830838d2e4b350..82cbff27023ec0ac9dcc48195d3fd0e24b4b5ec9 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java
@@ -81,8 +81,8 @@
 /**
  * Provides access to the Timeline SQLite database.
  *
- * This class borrows a lot of ideas and techniques from {@link  SleuthkitCase}.
- * Creating an abstract base class for SQLite databases, or using a higherlevel
+ * This class borrows a lot of ideas and techniques from SleuthkitCase. Creating
+ * an abstract base class for SQLite databases, or using a higherlevel
  * persistence api may make sense in the future.
  */
 public class EventDB {
@@ -103,7 +103,7 @@ public class EventDB {
      * the given path. If a database does not already exist at that path, one is
      * created.
      *
-     * @param autoCase the Autopsy {@link Case} the is events database is for.
+     * @param autoCase the Autopsy Case the is events database is for.
      *
      * @return a new EventDB or null if there was an error.
      */
@@ -734,7 +734,7 @@ List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts
 
     /**
      * create the tags table if it doesn't already exist. This is broken out as
-     * a separate method so it can be used by {@link #reInitializeTags() }
+     * a separate method so it can be used by reInitializeTags()
      */
     private void initializeTagsTable() {
         try (Statement stmt = con.createStatement()) {
@@ -1214,6 +1214,7 @@ List<EventStripe> getEventStripes(ZoomParams params) {
      * @param useSubTypes    use the sub_type column if true, else use the
      *                       base_type column
      * @param descriptionLOD the description level of detail for this event
+     * @param filter
      *
      * @return an AggregateEvent corresponding to the current row in the given
      *         result set
@@ -1317,8 +1318,6 @@ public class EventTransaction {
         /**
          * factory creation method
          *
-         * @param con the {@link  ava.sql.Connection}
-         *
          * @return a LogicalFileTransaction for the given connection
          *
          * @throws SQLException
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java
index bb273d0b0e01a90ce837ff3ab82c48dc8d09d492..90757b0a69615d99f99eb19b820822e73253fe7a 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java
@@ -222,8 +222,8 @@ synchronized public int countAllEvents() {
      * @return A List of event IDs for the events that are derived from the
      *         given file.
      */
-    public List<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) {
-        return eventDB.getEventIDsForFile(file, includedDerivedArtifacts);
+    public List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
+        return eventDB.getEventIDsForFile(file, includeDerivedArtifacts);
     }
 
     /**
@@ -677,11 +677,10 @@ protected void done() {
         }
 
         /**
-         * populate all the events of one subtype
+         * populate all the events of one type
          *
-         * @param subType the subtype to populate
+         * @param type the type to populate
          * @param trans   the db transaction to use
-         * @param skCase  a reference to the sleuthkit case
          */
         @NbBundle.Messages({"# {0} - event type ", "progressWindow.populatingXevents=Populating {0} events"})
         private void populateEventType(final ArtifactEventType type, EventDB.EventTransaction trans) {
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
index 5d57134c7d4660cf735fc3e207092a79568cb782..840ce0e83f9bad8fbbf1d0899f00e7c2c12f542f 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
@@ -241,11 +241,11 @@ private void zoomToSelectedInterval() {
     protected abstract String formatSpan(final X date);
 
     /**
-     * parse an x-axis value to a {@link DateTime}
+     * parse an x-axis value to a DateTime
      *
      * @param date a x-axis value of type X
      *
-     * @return a {@link DateTime} corresponding to the given x-axis value
+     * @return a DateTime corresponding to the given x-axis value
      */
     protected abstract DateTime parseDateTime(X date);
 
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
index 46df5150e992b20e6a4874f2e0de373b4321cbc0..a6a2006f040c54f9bbd0967a879d8089145907aa 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
@@ -706,7 +706,7 @@ private void setViewSettingsControls(List<Node> newSettingsNodes) {
      * Show the given List of Nodes in the time range ToolBar. Replaces any
      * Nodes that may have previously been set with the given List of Nodes.
      *
-     * @param newSettingsNodes The Nodes to show in the time range ToolBar.
+     * @param timeNavigationNodes The Nodes to show in the time range ToolBar.
      */
     @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
     private void setTimeNavigationControls(List<Node> timeNavigationNodes) {
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java
index 3f32941725e4192b101eb6856a09aecda5bafcff..60f17fcf6c344e53722ad4927e8163d31b1d12fe 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DescriptionVisibility.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2014 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,8 +19,8 @@
 package org.sleuthkit.autopsy.timeline.ui.detailview;
 
 /**
- * Level of description shown in UI NOTE: this is a separate concept form
- * {@link DescriptionLOD}
+ * Level of description shown in UI. NOTE: this is a separate concept from
+ * DescriptionLOD.
  */
 public enum DescriptionVisibility {
 
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java
index 22af3821250347db0045db70f8011452d063dcd2..48da45ff7bdc86589ffd4cd4c19dedf8e996ee62 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java
@@ -102,12 +102,6 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
      * Constructor for a DetailViewPane
      *
      * @param controller       the Controller to use
-     * @param partPane         the Pane that represents the smaller part of the
-     *                         time unit displayed on the horizontal axis
-     * @param contextPane      the Pane that represents the larger/contextual
-     *                         part of the time unit displayed on the horizontal
-     *                         axis
-     * @param bottomLeftSpacer a spacer to keep everything aligned.
      */
     public DetailViewPane(TimeLineController controller) {
         super(controller);
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChart.java
index a2fac7feb9f52c3f911a6597c276c866ba3e6dbe..95efa99f36c544cb03ebe553c18a553e2be80e20 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChart.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChart.java
@@ -468,7 +468,7 @@ private void syncPinnedHeight() {
          *
          * @param chartLane           The Chart lane to add the listeners to.
          * @param mouseClickedHandler The MouseClickedHandler to add to chart.
-         * @param chartDragHandler1   The ChartDragHandler to add to the chart
+         * @param chartDragHandler    The ChartDragHandler to add to the chart
          *                            as pressed, released, dragged, and clicked
          *                            handler.
          */
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChartLane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChartLane.java
index 6c743c313892ba75f48548969312b5ed161860fe..fa19b8f2e306024465a2a81bc08584ebc5e4f214 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChartLane.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailsChartLane.java
@@ -62,13 +62,13 @@
 import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
 
 /**
- * One "lane" of a the details view, contains all the core logic and
- * layout code.
+ * One "lane" of a the details view, contains all the core logic and layout
+ * code.
  *
  * NOTE: It was too hard to control the threading of this chart via the
- * complicated default listeners. Instead clients should use {@link #addDataItem(javafx.scene.chart.XYChart.Data)
- * } and {@link #removeDataItem(javafx.scene.chart.XYChart.Data) } to add and
- * remove data.
+ * complicated default listeners. Instead clients should use
+ * addDataItem(javafx.scene.chart.XYChart.Data) and
+ * removeDataItem(javafx.scene.chart.XYChart.Data) to add and remove data.
  */
 abstract class DetailsChartLane<Y extends TimeLineEvent> extends XYChart<DateTime, Y> implements ContextMenuProvider {
 
@@ -225,12 +225,8 @@ public ReadOnlyDoubleProperty maxVScrollProperty() {
      * greater than the left x coord, increment y position, do check again until
      * maxXatY less than left x coord.
      *
-     * @param nodes            collection of nodes to layout, sorted by event
-     *                         start time
-     * @param minY             the minimum y coordinate to position the nodes
-     *                         at.
-     * @param descriptionWidth the value of the maximum description width to set
-     *                         for each node.
+     * @param nodes collection of nodes to layout, sorted by event start time
+     * @param minY  the minimum y coordinate to position the nodes at.
      *
      * @return the maximum y coordinate used by any of the layed out nodes.
      */
@@ -358,8 +354,8 @@ synchronized Iterable<EventNodeBase<?>> getAllNodes() {
      */
     synchronized Iterable<EventNodeBase<?>> getNodes(Predicate<EventNodeBase<?>> p) {
         //use this recursive function to flatten the tree of nodes into an single stream.
-        Function<EventNodeBase<?>, Stream<EventNodeBase<?>>> stripeFlattener =
-                new Function<EventNodeBase<?>, Stream<EventNodeBase<?>>>() {
+        Function<EventNodeBase<?>, Stream<EventNodeBase<?>>> stripeFlattener
+                = new Function<EventNodeBase<?>, Stream<EventNodeBase<?>>>() {
             @Override
             public Stream<EventNodeBase<?>> apply(EventNodeBase<?> node) {
                 return Stream.concat(
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PrimaryDetailsChartLane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PrimaryDetailsChartLane.java
index 112baf0ebcf7116e5361338da441c63cc3652711..70a61543bb4abd4a1eb478d3959b56d2879f02c7 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PrimaryDetailsChartLane.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/PrimaryDetailsChartLane.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2016 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,8 +22,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import javafx.collections.ListChangeListener;
 import javafx.scene.chart.Axis;
-import javafx.scene.chart.NumberAxis;
-import javafx.scene.chart.XYChart;
 import javafx.scene.shape.Line;
 import javafx.scene.shape.StrokeLineCap;
 import org.sleuthkit.autopsy.coreutils.ThreadConfined;
@@ -32,11 +30,11 @@
 import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
 
 /**
- * Custom implementation of {@link XYChart} to graph events on a horizontal
+ * Custom implementation of XYChart to graph events on a horizontal
  * timeline.
  *
- * The horizontal {@link DateAxis} controls the tick-marks and the horizontal
- * layout of the nodes representing events. The vertical {@link NumberAxis} does
+ * The horizontal DateAxis controls the tick-marks and the horizontal
+ * layout of the nodes representing events. The vertical NumberAxis does
  * nothing (although a custom implementation could help with the vertical
  * layout?)
  *
@@ -99,6 +97,7 @@ private double getParentXForEpochMillis(Long epochMillis) {
         return getXAxis().localToParent(getXForEpochMillis(epochMillis), 0).getX();
     }
 
+    @Override
     void doAdditionalLayout() {
         for (final Map.Entry<EventCluster, Line> entry : projectionMap.entrySet()) {
             final EventCluster cluster = entry.getKey();
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java
index 049b7b63dc53a7077ac9433741e3c46f22f9225c..860a6e931232daed9c52ad2005032d62ba5d0ea8 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java
@@ -56,7 +56,7 @@
 import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
 
 /**
- * Shows all {@link  EventBundles} from the assigned {@link DetailViewPane} in a
+ * Shows all EventBundles from the assigned DetailViewPane in a
  * tree organized by type and then description. Hidden bundles are shown grayed
  * out. Right clicking on a item in the tree shows a context menu to show/hide
  * it.
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
index ef54ca9373eccaf06512df8f6fa1fa6dda040980..3c46d444d8e49520e7a7d66482d8b881015d80d5 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterSetPanel.java
@@ -51,8 +51,8 @@
 /**
  * The FXML controller for the filter ui.
  *
- * This also implements {@link TimeLineView} since it dynamically updates its
- * filters based on the contents of a {@link FilteredEventsModel}
+ * This also implements TimeLineView since it dynamically updates its
+ * filters based on the contents of a FilteredEventsModel
  */
 final public class FilterSetPanel extends BorderPane {
 
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
index 58be2a2554f01e3d1c324cf3a9c6ab2907e8bed8..1f24ed60109f8cde1868da89353bfaa33e54d8e8 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/FilterTreeItem.java
@@ -35,9 +35,10 @@ final public class FilterTreeItem extends TreeItem<Filter> {
      * the given filter
      *
      *
-     * @param filter the filter for this item. if f has sub-filters, tree items
-     *               will be made for them added added to the children of this
-     *               FilterTreeItem
+     * @param filter       the filter for this item. if f has sub-filters, tree
+     *                     items will be made for them added added to the
+     *                     children of this FilterTreeItem
+     * @param expansionMap
      */
     public FilterTreeItem(Filter filter, ObservableMap<Filter, Boolean> expansionMap) {
         super(filter);
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/utils/RangeDivisionInfo.java b/Core/src/org/sleuthkit/autopsy/timeline/utils/RangeDivisionInfo.java
index 92a3caab665c4a81e5c4a046035f20a773fe34ef..ac91ff97216e0fc8436e6fd7a2283ccbba45801f 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/utils/RangeDivisionInfo.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/utils/RangeDivisionInfo.java
@@ -39,7 +39,7 @@
 
 /**
  * Bundles up the results of analyzing a time range for the appropriate
- * {@link TimeUnits} to use to visualize it. Partly, this class exists so I
+ * TimeUnits to use to visualize it. Partly, this class exists so I
  * don't have to have more member variables in other places , and partly because
  * I can only return a single value from a function. This might only be a
  * temporary design but is working well for now.
@@ -58,7 +58,7 @@ public class RangeDivisionInfo {
     private final int numberOfBlocks;
 
     /**
-     * a {@link DateTimeFormatter} corresponding to the block size for the tick
+     * a DateTimeFormatter corresponding to the block size for the tick
      * marks on the date axis of the graph
      */
     private final DateTimeFormatter tickFormatter;
@@ -76,7 +76,7 @@ public class RangeDivisionInfo {
     private final long upperBound;
 
     /**
-     * the time range this {@link RangeDivisionInfo} describes
+     * the time range this RangeDivisionInfo describes
      */
     private final Interval timeRange;
     private ImmutableList<Interval> intervals;
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java
index 20d75c32db6a03472b3b827f94a001e4399176a6..ef74cd87774498b1225ecc66ee4cefa175202bb4 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/zooming/ZoomSettingsPane.java
@@ -143,11 +143,6 @@ public void initialize() {
      * from Integer index to Enum value displayed as the slider tick
      * label(labelIndexMapper).
      *
-     * @param <DriverType>        The type of the driving model property.
-     * @param <EnumType>          The type of the Enum that is represented along
-     *                            the slider.
-     *
-     *
      * @param slider              The slider that we are configuring.
      *
      * @param sliderValueConsumer The consumer that will get passed the newly
diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml
index 7c3f27396cc7d46a1574b21f21eb4170be54e15d..26859599e2dd0176ff48e33cf4d71fe13a14477b 100644
--- a/CoreLibs/ivy.xml
+++ b/CoreLibs/ivy.xml
@@ -16,15 +16,17 @@
         <dependency conf="autopsy_core->*" org="org.jbundle.thin.base.screen" name="jcalendarbutton" rev="1.4.6"/>
         
         <!-- commmon -->
-        <dependency org="com.google.guava" name="guava" rev="18.0"/>
+        <dependency org="com.google.guava" name="guava" rev="19.0"/>
         <dependency conf="autopsy_core->*" org="org.apache.commons" name="commons-lang3" rev="3.0"/>
+        <dependency conf="autopsy_core->*" org="org.apache.commons" name="commons-csv" rev="1.4"/>
+    
         <!-- keep old commons-lang because some deps may need it at runtime. 
         Note there is no namespace collision with ver 3 -->
         <dependency conf="autopsy_core->*" org="commons-lang" name="commons-lang" rev="2.6"/> 
         <dependency conf="autopsy_core->*" org="commons-logging" name="commons-logging" rev="1.1.2"/>
         <dependency conf="autopsy_core->*" org="commons-io" name="commons-io" rev="2.4"/>
         <dependency conf="autopsy_core->*" org="log4j" name="log4j" rev="1.2.17"/>
-        
+              
         <!-- <dependency conf="autopsy_core->*" org="org.jdom" name="jdom" rev="1.1.3"/> -->
         <dependency conf="autopsy_core->*" org="org.apache.poi" name="poi-excelant" rev="3.8"/>
         <dependency conf="autopsy_core->*" org="org.apache.poi" name="poi-scratchpad" rev="3.8"/>
@@ -64,8 +66,5 @@
         <dependency conf="autopsy_core->*" org="com.twelvemonkeys.imageio" name="imageio-thumbsdb" rev="3.2" />
         <dependency conf="autopsy_core->*" org="com.twelvemonkeys.imageio" name="imageio-core" rev="3.2" />
         <dependency conf="autopsy_core->*" org="com.twelvemonkeys.imageio" name="imageio-metadata" rev="3.2" />
-
-      
-
-    </dependencies>
+</dependencies>
 </ivy-module>
diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties
index da6ee3ad6dcf410df4a2b092e75ebeae9d6889dd..3f1452877a60d13dc30478ed6132196f3ccb902d 100644
--- a/CoreLibs/nbproject/project.properties
+++ b/CoreLibs/nbproject/project.properties
@@ -7,6 +7,7 @@ file.reference.common-image-3.2.jar=release/modules/ext/common-image-3.2.jar
 file.reference.common-io-3.2.jar=release/modules/ext/common-io-3.2.jar
 file.reference.common-lang-3.2.jar=release/modules/ext/common-lang-3.2.jar
 file.reference.commons-codec-1.5.jar=release/modules/ext/commons-codec-1.5.jar
+file.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4.jar
 file.reference.commons-io-2.4.jar=release/modules/ext/commons-io-2.4.jar
 file.reference.commons-lang-2.6.jar=release/modules/ext/commons-lang-2.6.jar
 file.reference.commons-lang3-3.0-javadoc.jar=release/modules/ext/commons-lang3-3.0-javadoc.jar
@@ -21,7 +22,7 @@ file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar
 file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
 file.reference.gson-1.4.jar=release/modules/ext/gson-1.4.jar
 file.reference.gstreamer-java-1.5.jar=release/modules/ext/gstreamer-java-1.5.jar
-file.reference.guava-18.0.jar=release/modules/ext/guava-18.0.jar
+file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar
 file.reference.imageio-bmp-3.2.jar=release/modules/ext/imageio-bmp-3.2.jar
 file.reference.imageio-core-3.2.jar=release/modules/ext/imageio-core-3.2.jar
 file.reference.imageio-icns-3.2.jar=release/modules/ext/imageio-icns-3.2.jar
@@ -71,16 +72,18 @@ file.reference.xml-apis-1.0.b2.jar=release/modules/ext/xml-apis-1.0.b2.jar
 file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
+javadoc.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-javadoc.jar
 javadoc.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-javadoc.jar
 javadoc.reference.controlsfx-8.40.11.jar=release/modules/ext/controlsfx-8.40.11-javadoc.jar
-javadoc.reference.guava-18.0.jar=release/modules/ext/guava-18.0-javadoc.jar
+javadoc.reference.guava-19.0.jar=release/modules/ext/guava-19.0-javadoc.jar
 javadoc.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-javadoc.jar
 javadoc.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-javadoc.jar
 javadoc.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-javadoc.jar
 nbm.needs.restart=true
+source.reference.commons-csv-1.4.jar=release/modules/ext/commons-csv-1.4-sources.jar
 source.reference.compiler-0.9.1.jar=release/modules/ext/compiler-0.9.1-sources.jar
 source.reference.controlsfx-8.40.11.jar=release/modules/ext/controlsfx-8.40.11-sources.jar
-source.reference.guava-18.0.jar=release/modules/ext/guava-18.0-sources.jar
+source.reference.guava-19.0.jar=release/modules/ext/guava-19.0-sources.jar
 source.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0-r4-sources.jar
 source.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4-sources.jar
 source.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4-sources.jar
diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml
index 2b6af068372045f2b40c028f835ba24a7a03538c..358637d979c08fddc23a051aa3e64f549a3ff9e8 100644
--- a/CoreLibs/nbproject/project.xml
+++ b/CoreLibs/nbproject/project.xml
@@ -228,6 +228,7 @@
                 <package>org.apache.commons.codec.digest</package>
                 <package>org.apache.commons.codec.language</package>
                 <package>org.apache.commons.codec.net</package>
+                <package>org.apache.commons.csv</package>
                 <package>org.apache.commons.io</package>
                 <package>org.apache.commons.io.comparator</package>
                 <package>org.apache.commons.io.filefilter</package>
@@ -742,10 +743,6 @@
                 <runtime-relative-path>ext/mail-1.4.3.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/mail-1.4.3.jar</binary-origin>
             </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/guava-18.0.jar</runtime-relative-path>
-                <binary-origin>release/modules/ext/guava-18.0.jar</binary-origin>
-            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/imageio-tga-3.2.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/imageio-tga-3.2.jar</binary-origin>
@@ -814,6 +811,10 @@
                 <runtime-relative-path>ext/jfxtras-controls-8.0-r4.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/jfxtras-controls-8.0-r4.jar</binary-origin>
             </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/commons-csv-1.4.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/commons-csv-1.4.jar</binary-origin>
+            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/imageio-sgi-3.2.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/imageio-sgi-3.2.jar</binary-origin>
@@ -846,6 +847,10 @@
                 <runtime-relative-path>ext/slf4j-simple-1.6.1.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/slf4j-simple-1.6.1.jar</binary-origin>
             </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/guava-19.0.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/guava-19.0.jar</binary-origin>
+            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/imageio-bmp-3.2.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/imageio-bmp-3.2.jar</binary-origin>
@@ -894,6 +899,10 @@
                 <runtime-relative-path>ext/poi-3.8.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/poi-3.8.jar</binary-origin>
             </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/controlsfx-8.40.11.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/controlsfx-8.40.11.jar</binary-origin>
+            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/commons-lang3-3.0.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/commons-lang3-3.0.jar</binary-origin>
@@ -918,10 +927,6 @@
                 <runtime-relative-path>ext/gstreamer-java-1.5.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/gstreamer-java-1.5.jar</binary-origin>
             </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/controlsfx-8.40.11.jar</runtime-relative-path>
-                <binary-origin>release/modules/ext/controlsfx-8.40.11.jar</binary-origin>
-            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/dom4j-1.6.1.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/dom4j-1.6.1.jar</binary-origin>
diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml
index b225bc745e6bce67e9273c4d7c243c0bf4a18365..82215f7680baed2f411c7de82117d105402f86b3 100644
--- a/ImageGallery/nbproject/project.xml
+++ b/ImageGallery/nbproject/project.xml
@@ -127,7 +127,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>10</release-version>
-                        <specification-version>10.5</specification-version>
+                        <specification-version>10.6</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
index 604d674ba01c0cd187eca30509327fb805e23bd4..c277da47c9c36e252912c8e7a645a8597cb660a2 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
@@ -49,7 +49,7 @@
  * Top component which displays ImageGallery interface.
  *
  * Although ImageGallery doesn't currently use the explorer manager, this
- * Topcomponenet provides one through the getExplorerManager method. However,
+ * TopComponent provides one through the getExplorerManager method. However,
  * this does not seem to function correctly unless a Netbeans provided explorer
  * view is present in the TopComponenet, even if it is invisible/ zero sized
  */
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
index 7ac2200ae960934f91f7d2b451b064d74b13c415..2172aacf518bbe7988ec3af458b7b026fb89ca3b 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java
@@ -91,10 +91,10 @@ public DrawableTagsManager(TagsManager autopsyTagsManager) {
     /**
      * register an object to receive CategoryChangeEvents
      *
-     * @param listner
+     * @param listener
      */
-    public void registerListener(Object listner) {
-        tagsEventBus.register(listner);
+    public void registerListener(Object listener) {
+        tagsEventBus.register(listener);
     }
 
     /**
@@ -217,15 +217,18 @@ public List<ContentTag> getContentTags(DrawableFile drawable) throws TskCoreExce
     public TagName getTagName(String displayName) throws TskCoreException {
         synchronized (autopsyTagsManagerLock) {
             try {
-                for (TagName tn : autopsyTagsManager.getAllTagNames()) {
-                    if (displayName.equals(tn.getDisplayName())) {
-                        return tn;
-                    }
+                TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
+                if (returnTagName != null) {
+                    return returnTagName;
                 }
                 try {
                     return autopsyTagsManager.addTagName(displayName);
                 } catch (TagsManager.TagNameAlreadyExistsException ex) {
-                    throw new TskCoreException("tagame exists but wasn't found", ex);
+                    returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
+                    if (returnTagName != null) {
+                        return returnTagName;
+                    }
+                    throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
                 }
             } catch (NullPointerException | IllegalStateException ex) {
                 LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml
index aba7982201b6e036ddd1b2559cc974ad7869d2b5..a8ced6dedaea2c6677d28fa1b9e724c9b97cdb07 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml
@@ -12,7 +12,7 @@
 <?import javafx.scene.image.ImageView?>
 <?import javafx.scene.layout.HBox?>
 
-<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
+<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
     <items> 
         <HBox alignment="CENTER" spacing="5.0">
             <children>
@@ -25,6 +25,11 @@
       
             </children>
         </HBox>
+      <ImageView fx:id="sortHelpImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
+         <image>
+            <Image url="@../images/question-frame.png" />
+         </image>
+      </ImageView>
           
   
    
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
index 55be749d20ccc3afff46d720eebcbe8e1901ea6c..72f962272986dd764d2e63a0386e96125729fe58 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java
@@ -28,12 +28,21 @@
 import javafx.beans.property.DoubleProperty;
 import javafx.collections.FXCollections;
 import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.Label;
 import javafx.scene.control.MenuItem;
 import javafx.scene.control.Slider;
 import javafx.scene.control.SplitMenuButton;
 import javafx.scene.control.ToolBar;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.text.Text;
+import org.controlsfx.control.PopOver;
 import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
@@ -56,6 +65,9 @@ public class Toolbar extends ToolBar {
 
     private static final int SIZE_SLIDER_DEFAULT = 100;
 
+    @FXML
+    private ImageView sortHelpImageView;
+
     @FXML
     private ComboBox<DrawableAttribute<?>> groupByBox;
 
@@ -104,7 +116,9 @@ public DoubleProperty thumbnailSizeProperty() {
         "Toolbar.descRadio=Descending",
         "Toolbar.tagImageViewLabel=Tag Group's Files:",
         "Toolbar.categoryImageViewLabel=Categorize Group's Files:",
-        "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
+        "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):",
+        "Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.",
+        "Toolbar.sortHelpTitle=Group Sorting",})
     void initialize() {
         assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
         assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
@@ -169,7 +183,43 @@ void initialize() {
         sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
         sortChooser.setComparator(GroupSortBy.PRIORITY);
         getItems().add(1, sortChooser);
+        sortHelpImageView.setCursor(Cursor.HAND);
+
+        sortHelpImageView.setOnMouseClicked(clicked -> {
+            Text text = new Text(Bundle.Toolbar_sortHelp());
+            text.setWrappingWidth(480);  //This is a hack to fix the layout.
+            showPopoverHelp(sortHelpImageView,
+                    Bundle.Toolbar_sortHelpTitle(),
+                    sortHelpImageView.getImage(), text);
+        });
+
+    }
 
+    /**
+     *
+     * Static utility to to show a Popover with the given Node as owner.
+     *
+     * @param owner       The owner of the Popover
+     * @param headerText  A short String that will be shown in the top-left
+     *                    corner of the Popover.
+     * @param headerImage An Image that will be shown at the top-right corner of
+     *                    the Popover.
+     * @param content     The main content of the Popover, shown in the
+     *                    bottom-center
+     *
+     */
+    private static void showPopoverHelp(final Node owner, final String headerText, final Image headerImage, final Node content) {
+        Pane borderPane = new BorderPane(null, null, new ImageView(headerImage),
+                content,
+                new Label(headerText));
+        borderPane.setPadding(new Insets(10));
+        borderPane.setPrefWidth(500);
+
+        PopOver popOver = new PopOver(borderPane);
+        popOver.setDetachable(false);
+        popOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER);
+
+        popOver.show(owner);
     }
 
     private void syncGroupControlsEnabledState(GroupViewState newViewState) {
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/question-frame.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/question-frame.png
new file mode 100644
index 0000000000000000000000000000000000000000..be52814717ef790cd13d33c7ec1fc9c5d14b0eb2
Binary files /dev/null and b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/question-frame.png differ
diff --git a/KeywordSearch/build.xml b/KeywordSearch/build.xml
index b526b9599144285096be1e78e5c3892ac76a02b8..d2ff2020a37fd8366b6de5af72874cd462e071b8 100644
--- a/KeywordSearch/build.xml
+++ b/KeywordSearch/build.xml
@@ -23,7 +23,7 @@
     <target name="-download-ivy" unless="ivy.available">
         <mkdir dir="${ivy.jar.dir}"/>
         <get src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar" 
-         dest="${ivy.jar.file}" usetimestamp="true"/>
+             dest="${ivy.jar.file}" usetimestamp="true"/>
     </target>
 
     <!-- init-ivy will bootstrap Ivy if the user doesn't have it already -->
@@ -32,8 +32,10 @@
             <fileset dir="${ivy.jar.dir}" includes="*.jar"/>
         </path>
         <taskdef resource="org/apache/ivy/ant/antlib.xml"
-             uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+                 uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
     </target>
+    
+
  
 
     <target name="init" depends="basic-init,files-init,build-init,-javac-init,init-ivy">
diff --git a/KeywordSearch/ivy.xml b/KeywordSearch/ivy.xml
index 46496a3cd9bb6f0cf03b5b317d5401b0ad0a360a..8cb58d6c46720bafec46fe9319443bae78754543 100644
--- a/KeywordSearch/ivy.xml
+++ b/KeywordSearch/ivy.xml
@@ -21,9 +21,11 @@
         <!-- Autopsy -->
         <dependency conf="autopsy->*" org="org.apache.solr" name="solr-solrj" rev="6.2.1"/>
         <dependency conf="autopsy->*" org="commons-lang" name="commons-lang" rev="2.4"/>
+        <dependency conf="autopsy->*" org="commons-validator" name="commons-validator" rev="1.5.1"/>
         <dependency conf="autopsy->*" org="org.apache.tika" name="tika-parsers" rev="1.5"/>
+        
         <!-- metadata-extractor is required by Tika but it depends on version 2.6.2 which suffers
-          from an XMP library issue. -->
+        from an XMP library issue. -->
         <dependency conf="autopsy->*" org="com.drewnoakes" name="metadata-extractor" rev="2.7.2" />
         <!-- icu4j for pdfbox bidirectional text support, needs to be defined explicitely (it is optional) -->
         <dependency conf="autopsy->*" org="com.ibm.icu" name="icu4j" rev="3.8"/>
@@ -39,6 +41,6 @@
         <dependency conf="slf4j-libs->default" org="org.slf4j" name="slf4j-log4j12" rev="1.7.10"/>
         <dependency conf="slf4j-libs->default" org="org.slf4j" name="jcl-over-slf4j" rev="1.7.10"/>
         <dependency conf="slf4j-libs->default" org="org.slf4j" name="jul-to-slf4j" rev="1.7.10"/>
-        
+              
     </dependencies>
 </ivy-module>
diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties
index cb50270b3e2cacf11ab6f3eed8af2247403becda..ac11234f182f790e46e9e0d79bba4b96dd67d4ec 100644
--- a/KeywordSearch/nbproject/project.properties
+++ b/KeywordSearch/nbproject/project.properties
@@ -1,6 +1,63 @@
+file.reference.apache-mime4j-core-0.7.2.jar=release/modules/ext/apache-mime4j-core-0.7.2.jar
+file.reference.apache-mime4j-dom-0.7.2.jar=release/modules/ext/apache-mime4j-dom-0.7.2.jar
+file.reference.asm-all-3.1.jar=release/modules/ext/asm-all-3.1.jar
+file.reference.aspectjrt-1.6.11.jar=release/modules/ext/aspectjrt-1.6.11.jar
+file.reference.bcmail-jdk15-1.45.jar=release/modules/ext/bcmail-jdk15-1.45.jar
+file.reference.bcprov-jdk15-1.45.jar=release/modules/ext/bcprov-jdk15-1.45.jar
+file.reference.commons-compress-1.5.jar=release/modules/ext/commons-compress-1.5.jar
+file.reference.commons-io-2.3.jar=release/modules/ext/commons-io-2.3.jar
+file.reference.commons-lang-2.4-javadoc.jar=release/modules/ext/commons-lang-2.4-javadoc.jar
+file.reference.commons-lang-2.4-sources.jar=release/modules/ext/commons-lang-2.4-sources.jar
+file.reference.commons-lang-2.4.jar=release/modules/ext/commons-lang-2.4.jar
+file.reference.commons-logging-api-1.1.jar=release/modules/ext/commons-logging-api-1.1.jar
+file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar
+file.reference.fontbox-1.8.4.jar=release/modules/ext/fontbox-1.8.4.jar
+file.reference.geronimo-stax-api_1.0_spec-1.0.1.jar=release/modules/ext/geronimo-stax-api_1.0_spec-1.0.1.jar
+file.reference.httpclient-4.3.1.jar=release/modules/ext/httpclient-4.3.1.jar
+file.reference.httpcore-4.3.jar=release/modules/ext/httpcore-4.3.jar
+file.reference.httpmime-4.3.1.jar=release/modules/ext/httpmime-4.3.1.jar
+file.reference.icu4j-3.8.jar=release/modules/ext/icu4j-3.8.jar
+file.reference.isoparser-1.0-RC-1.jar=release/modules/ext/isoparser-1.0-RC-1.jar
+file.reference.jdom-1.0.jar=release/modules/ext/jdom-1.0.jar
+file.reference.jempbox-1.8.4.jar=release/modules/ext/jempbox-1.8.4.jar
+file.reference.jericho-html-3.3-javadoc.jar=release/modules/ext/jericho-html-3.3-javadoc.jar
+file.reference.jericho-html-3.3-sources.jar=release/modules/ext/jericho-html-3.3-sources.jar
+file.reference.jericho-html-3.3.jar=release/modules/ext/jericho-html-3.3.jar
+file.reference.juniversalchardet-1.0.3.jar=release/modules/ext/juniversalchardet-1.0.3.jar
+file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar
+file.reference.metadata-extractor-2.7.2.jar=release/modules/ext/metadata-extractor-2.7.2.jar
+file.reference.netcdf-4.2-min.jar=release/modules/ext/netcdf-4.2-min.jar
+file.reference.noggit-0.5.jar=release/modules/ext/noggit-0.5.jar
+file.reference.org.apache.felix.scr.annotations-1.6.0.jar=release/modules/ext/org.apache.felix.scr.annotations-1.6.0.jar
+file.reference.org.apache.felix.scr.generator-1.1.2.jar=release/modules/ext/org.apache.felix.scr.generator-1.1.2.jar
+file.reference.org.osgi.compendium-4.0.0.jar=release/modules/ext/org.osgi.compendium-4.0.0.jar
+file.reference.org.osgi.core-4.0.0.jar=release/modules/ext/org.osgi.core-4.0.0.jar
+file.reference.pdfbox-1.8.4.jar=release/modules/ext/pdfbox-1.8.4.jar
+file.reference.poi-3.10-beta2.jar=release/modules/ext/poi-3.10-beta2.jar
+file.reference.poi-ooxml-3.10-beta2.jar=release/modules/ext/poi-ooxml-3.10-beta2.jar
+file.reference.poi-ooxml-schemas-3.10-beta2.jar=release/modules/ext/poi-ooxml-schemas-3.10-beta2.jar
+file.reference.poi-scratchpad-3.10-beta2.jar=release/modules/ext/poi-scratchpad-3.10-beta2.jar
+file.reference.qdox-1.12.jar=release/modules/ext/qdox-1.12.jar
+file.reference.rome-0.9.jar=release/modules/ext/rome-0.9.jar
+file.reference.slf4j-api-1.7.6.jar=release/modules/ext/slf4j-api-1.7.6.jar
+file.reference.solr-solrj-4.9.1-javadoc.jar=release/modules/ext/solr-solrj-4.9.1-javadoc.jar
+file.reference.solr-solrj-4.9.1-sources.jar=release/modules/ext/solr-solrj-4.9.1-sources.jar
+file.reference.solr-solrj-4.9.1.jar=release/modules/ext/solr-solrj-4.9.1.jar
+file.reference.tagsoup-1.2.1.jar=release/modules/ext/tagsoup-1.2.1.jar
+file.reference.tika-core-1.5.jar=release/modules/ext/tika-core-1.5.jar
+file.reference.tika-parsers-1.5.jar=release/modules/ext/tika-parsers-1.5.jar
+file.reference.vorbis-java-core-0.1-tests.jar=release/modules/ext/vorbis-java-core-0.1-tests.jar
+file.reference.vorbis-java-tika-0.1.jar=release/modules/ext/vorbis-java-tika-0.1.jar
+file.reference.wstx-asl-3.2.7.jar=release/modules/ext/wstx-asl-3.2.7.jar
+file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar
+file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar
+file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
 license.file=../LICENSE-2.0.txt
 nbm.homepage=http://www.sleuthkit.org/autopsy/
 nbm.needs.restart=true
 spec.version.base=6.3
+javadoc.reference.commons-validator-1.5.1.jar=release/modules/ext/commons-validator-1.5.1-javadoc.jar
+file.reference.commons-validator-1.5.1.jar=release/modules/ext/commons-validator-1.5.1.jar
+source.reference.commons-validator-1.5.1.jar=release/modules/ext/commons-validator-1.5.1-sources.jar 
diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml
index 9fd6d771fc15dd19cdfc64ee270d15ca4406da91..23005115aa87db1f543a44bd9ac7018ea1464900 100644
--- a/KeywordSearch/nbproject/project.xml
+++ b/KeywordSearch/nbproject/project.xml
@@ -119,7 +119,16 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>10</release-version>
-                        <specification-version>10.5</specification-version>
+                        <specification-version>10.6</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.sleuthkit.autopsy.corelibs</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>3</release-version>
+                        <specification-version>1.1</specification-version>
                     </run-dependency>
                 </dependency>
             </module-dependencies>
@@ -308,10 +317,6 @@
                 <runtime-relative-path>ext/poi-scratchpad-3.10-beta2.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/poi-scratchpad-3.10-beta2.jar</binary-origin>
             </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/commons-logging-1.1.1.jar</runtime-relative-path>
-                <binary-origin>release/modules/ext/commons-logging-1.1.1.jar</binary-origin>
-            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/netcdf-4.2-min.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/netcdf-4.2-min.jar</binary-origin>
@@ -360,6 +365,10 @@
                 <runtime-relative-path>ext/httpcore-4.3.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/httpcore-4.3.jar</binary-origin>
             </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/commons-validator-1.5.1.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/commons-validator-1.5.1.jar</binary-origin>
+            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/commons-lang-2.4.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/commons-lang-2.4.jar</binary-origin>
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java
index 83dac4645bdf3afc2fae1fe6789fbfe3e3e2b763..5253e5e24092a56eb09dbcd7cf54211ec4cec78c 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileChunk.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2012 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,47 +19,73 @@
 package org.sleuthkit.autopsy.keywordsearch;
 
 import java.nio.charset.Charset;
-import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException;
 
 /**
- * Represents each string chunk to be indexed, a derivative of TextExtractor
- * file
+ * A representation of a chunk of text from a file that can be used, when
+ * supplied with an Ingester, to index the chunk for search.
  */
-class AbstractFileChunk {
+final class AbstractFileChunk {
 
-    private int chunkID;
-    private TextExtractor parent;
+    private final int chunkNumber;
+    private final TextExtractor textExtractor;
 
-    AbstractFileChunk(TextExtractor parent, int chunkID) {
-        this.parent = parent;
-        this.chunkID = chunkID;
+    /**
+     * Constructs a representation of a chunk of text from a file that can be
+     * used, when supplied with an Ingester, to index the chunk for search.
+     *
+     * @param textExtractor A TextExtractor for the file.
+     * @param chunkNumber   A sequence number for the chunk.
+     */
+    AbstractFileChunk(TextExtractor textExtractor, int chunkNumber) {
+        this.textExtractor = textExtractor;
+        this.chunkNumber = chunkNumber;
     }
 
-    public TextExtractor getParent() {
-        return parent;
+    /**
+     * Gets the TextExtractor for the source file of the text chunk.
+     *
+     * @return A reference to the TextExtractor.
+     */
+    TextExtractor getTextExtractor() {
+        return textExtractor;
     }
 
-    public int getChunkId() {
-        return chunkID;
+    /**
+     * Gets the sequence number of the text chunk.
+     *
+     * @return The chunk number.
+     */
+    int getChunkNumber() {
+        return chunkNumber;
     }
 
     /**
-     * return String representation of the absolute id (parent and child)
+     * Gets the id of the text chunk.
      *
-     * @return
+     * @return An id of the form [source file object id]_[chunk number]
      */
-    String getIdString() {
-        return Server.getChunkIdString(this.parent.getSourceFile().getId(), this.chunkID);
+    String getChunkId() {
+        return Server.getChunkIdString(this.textExtractor.getSourceFile().getId(), this.chunkNumber);
     }
 
-    void index(Ingester ingester, byte[] content, long contentSize, Charset indexCharset) throws IngesterException {
-        ByteContentStream bcs = new ByteContentStream(content, contentSize, parent.getSourceFile(), indexCharset);
+    /**
+     * Indexes the text chunk.
+     *
+     * @param ingester   An Ingester to do the indexing.
+     * @param chunkBytes The raw bytes of the text chunk.
+     * @param chunkSize  The size of the text chunk in bytes.
+     * @param charSet    The char set to use during indexing.
+     *
+     * @throws org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException
+     */
+    void index(Ingester ingester, byte[] chunkBytes, long chunkSize, Charset charSet) throws IngesterException {
+        ByteContentStream bcs = new ByteContentStream(chunkBytes, chunkSize, textExtractor.getSourceFile(), charSet);
         try {
-            ingester.ingest(this, bcs, content.length);
-        } catch (Exception ingEx) {
-            throw new IngesterException(NbBundle.getMessage(this.getClass(), "AbstractFileChunk.index.exception.msg",
-                    parent.getSourceFile().getId(), chunkID), ingEx);
+            ingester.ingest(this, bcs, chunkBytes.length);
+        } catch (Exception ex) {
+            throw new IngesterException(String.format("Error ingesting (indexing) file chunk: %s", getChunkId()), ex);
         }
     }
+
 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileStringContentStream.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileStringContentStream.java
index b62cc46bba15563e186d378f999d1978c404ec71..e8a7efdde0d429bbc7901846c07d4c9e8c9ac12d 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileStringContentStream.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AbstractFileStringContentStream.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,7 +25,6 @@
 import java.nio.charset.Charset;
 
 import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
 import org.apache.solr.common.util.ContentStream;
 import org.sleuthkit.datamodel.AbstractContent;
 import org.sleuthkit.datamodel.AbstractFile;
@@ -36,11 +35,10 @@
 class AbstractFileStringContentStream implements ContentStream {
     //input
 
-    private AbstractFile content;
-    private Charset charset;
+    private final AbstractFile content;
+    private final Charset charset;
     //converted
-    private InputStream stream;
-    private static Logger logger = Logger.getLogger(AbstractFileStringContentStream.class.getName());
+    private final InputStream stream;
 
     public AbstractFileStringContentStream(AbstractFile content, Charset charset, InputStream inputStream) {
         this.content = content;
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AccountsText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AccountsText.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a110ef9b68dbd9fa61da68f9374ad43e4fe43af
--- /dev/null
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AccountsText.java
@@ -0,0 +1,372 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2016 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.keywordsearch;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrRequest.METHOD;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocument;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.Version;
+
+/**
+ * Highlights account hits for a given document. Knows about pages and such for
+ * the content viewer.
+ *
+ * Note: This class started as a copy-and-paste of HighlightedText, but it
+ * proved too messy to modify HighlightedText to work for accounts also. This
+ * and HighlightedText are very similar and could probably use some refactoring
+ * to reduce code duplication.
+ */
+class AccountsText implements IndexedText {
+
+    private static final Logger LOGGER = Logger.getLogger(AccountsText.class.getName());
+    private static final boolean DEBUG = (Version.getBuildType() == Version.Type.DEVELOPMENT);
+
+    private static final String HIGHLIGHT_PRE = "<span style='background:yellow'>"; //NON-NLS
+    private static final String HIGHLIGHT_POST = "</span>"; //NON-NLS
+    private static final String ANCHOR_NAME_PREFIX = AccountsText.class.getName() + "_";
+
+    private static final String INSERT_PREFIX = "<a name='" + ANCHOR_NAME_PREFIX; //NON-NLS
+    private static final String INSERT_POSTFIX = "'></a>$0"; //$0 will insert current regex match  //NON-NLS
+    private static final Pattern ANCHOR_DETECTION_PATTERN = Pattern.compile(HIGHLIGHT_PRE);
+
+    private static final String HIGHLIGHT_FIELD = LuceneQuery.HIGHLIGHT_FIELD_REGEX;
+
+    private final Server solrServer;
+    private final String solrDocumentId;
+    private final long solrObjectId;
+    private final Integer chunkId;
+    private final Set<String> keywords = new HashSet<>();
+    private final String displayName;
+    private final String queryString;
+
+    private boolean isPageInfoLoaded = false;
+    private int numberPagesForFile = 0;
+    private int currentPage = 0;
+    //list of pages, used for iterating back and forth.  Only stores pages with hits
+    private final List<Integer> pages = new ArrayList<>();
+    //map from page/chunk to number of hits. value is 0 if not yet known.
+    private final LinkedHashMap<Integer, Integer> numberOfHitsPerPage = new LinkedHashMap<>();
+    //map from page/chunk number to current hit on that page.
+    private final HashMap<Integer, Integer> currentHitPerPage = new HashMap<>();
+
+    @NbBundle.Messages({
+        "AccountsText.creditCardNumber=Credit Card Number",
+        "AccountsText.creditCardNumbers=Credit Card Numbers"})
+    AccountsText(String objectId, Set<String> keywords) {
+        this.solrDocumentId = objectId;
+        this.keywords.addAll(keywords);
+
+        //build the query string
+        this.queryString = HIGHLIGHT_FIELD + ":"
+                + keywords.stream()
+                .map(keyword -> "/.*?" + KeywordSearchUtil.escapeLuceneQuery(keyword) + ".*?/")//surround each "keyword" with match anything regex.
+                .collect(Collectors.joining(" ")); //collect as space separated string
+
+        this.solrServer = KeywordSearch.getServer();
+
+        final int separatorIndex = solrDocumentId.indexOf(Server.CHUNK_ID_SEPARATOR);
+        if (-1 == separatorIndex) {
+            //no chunk id in solrDocumentId
+            this.solrObjectId = Long.parseLong(solrDocumentId);
+            this.chunkId = null;
+        } else {
+            //solrDocumentId includes chunk id
+            this.solrObjectId = Long.parseLong(solrDocumentId.substring(0, separatorIndex));
+            this.chunkId = Integer.parseInt(solrDocumentId.substring(separatorIndex + 1));
+        }
+
+        displayName = keywords.size() == 1
+                ? Bundle.AccountsText_creditCardNumber()
+                : Bundle.AccountsText_creditCardNumbers();
+    }
+
+    long getObjectId() {
+        return this.solrObjectId;
+    }
+
+    @Override
+    public int getNumberPages() {
+        return this.numberPagesForFile;
+    }
+
+    @Override
+    public int getCurrentPage() {
+        return this.currentPage;
+    }
+
+    @Override
+    public boolean hasNextPage() {
+        return pages.indexOf(this.currentPage) < pages.size() - 1;
+
+    }
+
+    @Override
+    public boolean hasPreviousPage() {
+        return pages.indexOf(this.currentPage) > 0;
+
+    }
+
+    @Override
+    @NbBundle.Messages("AccountsText.nextPage.exception.msg=No next page.")
+    public int nextPage() {
+        if (hasNextPage()) {
+            currentPage = pages.get(pages.indexOf(this.currentPage) + 1);
+            return currentPage;
+        } else {
+            throw new IllegalStateException(Bundle.AccountsText_nextPage_exception_msg());
+        }
+    }
+
+    @Override
+    @NbBundle.Messages("AccountsText.previousPage.exception.msg=No previous page.")
+    public int previousPage() {
+        if (hasPreviousPage()) {
+            currentPage = pages.get(pages.indexOf(this.currentPage) - 1);
+            return currentPage;
+        } else {
+            throw new IllegalStateException(Bundle.AccountsText_previousPage_exception_msg());
+        }
+    }
+
+    @Override
+    public boolean hasNextItem() {
+        if (this.currentHitPerPage.containsKey(currentPage)) {
+            return this.currentHitPerPage.get(currentPage) < this.numberOfHitsPerPage.get(currentPage);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean hasPreviousItem() {
+        if (this.currentHitPerPage.containsKey(currentPage)) {
+            return this.currentHitPerPage.get(currentPage) > 1;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    @NbBundle.Messages("AccountsText.nextItem.exception.msg=No next item.")
+    public int nextItem() {
+        if (hasNextItem()) {
+            return currentHitPerPage.merge(currentPage, 1, Integer::sum);
+        } else {
+            throw new IllegalStateException(Bundle.AccountsText_nextItem_exception_msg());
+        }
+    }
+
+    @Override
+    @NbBundle.Messages("AccountsText.previousItem.exception.msg=No previous item.")
+    public int previousItem() {
+        if (hasPreviousItem()) {
+            return currentHitPerPage.merge(currentPage, -1, Integer::sum);
+        } else {
+            throw new IllegalStateException(Bundle.AccountsText_previousItem_exception_msg());
+        }
+    }
+
+    @Override
+    public int currentItem() {
+        if (this.currentHitPerPage.containsKey(currentPage)) {
+            return currentHitPerPage.get(currentPage);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public LinkedHashMap<Integer, Integer> getHitsPages() {
+        return this.numberOfHitsPerPage;
+    }
+
+    /**
+     * Initialize this object with information about which pages/chunks have
+     * hits. Multiple calls will not change the initial results.
+     */
+    synchronized private void loadPageInfo() {
+        if (isPageInfoLoaded) {
+            return;
+        }
+        if (chunkId != null) {//if a chunk is specified, only show that chunk/page
+            this.numberPagesForFile = 1;
+            this.currentPage = chunkId;
+            this.numberOfHitsPerPage.put(chunkId, 0);
+            this.pages.add(chunkId);
+            this.currentHitPerPage.put(chunkId, 0);
+        } else {
+            try {
+                this.numberPagesForFile = solrServer.queryNumFileChunks(this.solrObjectId);
+            } catch (KeywordSearchModuleException | NoOpenCoreException ex) {
+                LOGGER.log(Level.WARNING, "Could not get number pages for content " + this.solrDocumentId, ex); //NON-NLS
+                return;
+            }
+
+            //if has chunks, get pages with hits
+            TreeSet<Integer> sortedPagesWithHits = new TreeSet<>();
+            SolrQuery q = new SolrQuery();
+            q.setShowDebugInfo(DEBUG); //debug
+            q.setQuery(queryString);
+            q.setFields(Server.Schema.ID.toString());  //for this case we only need the document ids
+            q.addFilterQuery(Server.Schema.ID.toString() + ":" + this.solrObjectId + Server.CHUNK_ID_SEPARATOR + "*");
+
+            try {
+                QueryResponse response = solrServer.query(q, METHOD.POST);
+                for (SolrDocument resultDoc : response.getResults()) {
+                    final String resultDocumentId = resultDoc.getFieldValue(Server.Schema.ID.toString()).toString();
+                    // Put the solr chunk id in the map
+                    String resultChunkID = StringUtils.substringAfter(resultDocumentId, Server.CHUNK_ID_SEPARATOR);
+                    if (StringUtils.isNotBlank(resultChunkID)) {
+                        sortedPagesWithHits.add(Integer.parseInt(resultChunkID));
+                    } else {
+                        sortedPagesWithHits.add(0);
+                    }
+                }
+
+            } catch (KeywordSearchModuleException | NoOpenCoreException | NumberFormatException ex) {
+                LOGGER.log(Level.WARNING, "Error executing Solr highlighting query: " + keywords, ex); //NON-NLS
+            }
+
+            //set page to first page having highlights
+            if (sortedPagesWithHits.isEmpty()) {
+                this.currentPage = 0;
+            } else {
+                this.currentPage = sortedPagesWithHits.first();
+            }
+
+            for (Integer page : sortedPagesWithHits) {
+                numberOfHitsPerPage.put(page, 0); //unknown number of matches in the page
+                pages.add(page);
+                currentHitPerPage.put(page, 0); //set current hit to 0th
+            }
+        }
+
+        isPageInfoLoaded = true;
+    }
+
+    @Override
+    @NbBundle.Messages({"AccountsText.getMarkup.noMatchMsg="
+        + "<html><pre><span style\\\\='background\\\\:yellow'>There were no keyword hits on this page. <br />"
+        + "The keyword could have been in the file name."
+        + " <br />Advance to another page if present, or to view the original text, choose File Text"
+        + " <br />in the drop down menu to the right...</span></pre></html>",
+        "AccountsText.getMarkup.queryFailedMsg="
+        + "<html><pre><span style\\\\='background\\\\:yellow'>Failed to retrieve keyword hit results."
+        + " <br />Confirm that Autopsy can connect to the Solr server. "
+        + "<br /></span></pre></html>"})
+    public String getText() {
+        loadPageInfo(); //inits once
+
+        SolrQuery q = new SolrQuery();
+        q.setShowDebugInfo(DEBUG); //debug
+        q.addHighlightField(HIGHLIGHT_FIELD);
+        q.setQuery(queryString);
+
+        //set the documentID filter
+        String queryDocumentID = this.solrObjectId + Server.CHUNK_ID_SEPARATOR + this.currentPage;
+        q.addFilterQuery(Server.Schema.ID.toString() + ":" + queryDocumentID);
+
+        //configure the highlighter
+        q.setParam("hl.useFastVectorHighlighter", "true"); //fast highlighter scales better than standard one NON-NLS
+        q.setParam("hl.tag.pre", HIGHLIGHT_PRE); //makes sense for FastVectorHighlighter only NON-NLS
+        q.setParam("hl.tag.post", HIGHLIGHT_POST); //makes sense for FastVectorHighlighter only NON-NLS
+        q.setParam("hl.fragListBuilder", "single"); //makes sense for FastVectorHighlighter only NON-NLS
+        q.setParam("hl.maxAnalyzedChars", Server.HL_ANALYZE_CHARS_UNLIMITED); //docs says makes sense for the original Highlighter only, but not really //NON-NLS
+
+        try {
+            //extract highlighting and bail early on null responses
+            Map<String, Map<String, List<String>>> highlightingPerDocument = solrServer.query(q, METHOD.POST).getHighlighting();
+            Map<String, List<String>> highlightingPerField = highlightingPerDocument.get(queryDocumentID);
+            if (highlightingPerField == null) {
+                return Bundle.AccountsText_getMarkup_noMatchMsg();
+            }
+            List<String> highlights = highlightingPerField.get(HIGHLIGHT_FIELD);
+            if (highlights == null) {
+                return Bundle.AccountsText_getMarkup_noMatchMsg();
+            }
+
+            //There should only be one item
+            String highlighting = highlights.get(0).trim();
+
+            /*
+             * use regex matcher to iterate over occurences of HIGHLIGHT_PRE,
+             * and prepend them with an anchor tag.
+             */
+            Matcher m = ANCHOR_DETECTION_PATTERN.matcher(highlighting);
+            StringBuffer sb = new StringBuffer(highlighting.length());
+            int count = 0;
+            while (m.find()) {
+                count++;
+                m.appendReplacement(sb, INSERT_PREFIX + count + INSERT_POSTFIX);
+            }
+            m.appendTail(sb);
+
+            //store total hits for this page, now that we know it
+            this.numberOfHitsPerPage.put(this.currentPage, count);
+            if (this.currentItem() == 0 && this.hasNextItem()) {
+                this.nextItem();
+            }
+
+            // extracted content (minus highlight tags) is HTML-escaped
+            return "<html><pre>" + sb.toString() + "</pre></html>"; //NON-NLS
+        } catch (Exception ex) {
+            LOGGER.log(Level.WARNING, "Error executing Solr highlighting query: " + keywords, ex); //NON-NLS
+            return Bundle.AccountsText_getMarkup_queryFailedMsg();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    @Override
+    public boolean isSearchable() {
+        return true;
+    }
+
+    @Override
+    public String getAnchorPrefix() {
+        return ANCHOR_NAME_PREFIX;
+    }
+
+    @Override
+    public int getNumberHits() {
+        if (!this.numberOfHitsPerPage.containsKey(this.currentPage)) {
+            return 0;
+        }
+        return this.numberOfHitsPerPage.get(this.currentPage);
+    }
+}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties
index 66a007a67c7b138801863633773dc07907b27b9c..1ac7f0fbbda57735998fc157a9595a58f12eb353 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties
@@ -157,7 +157,6 @@ DropdownSearchPanel.cutMenuItem.text=Cut
 DropdownSearchPanel.selectAllMenuItem.text=Select All
 DropdownSearchPanel.pasteMenuItem.text=Paste
 DropdownSearchPanel.copyMenuItem.text=Copy
-AbstractFileChunk.index.exception.msg=Problem ingesting file string chunk\: {0}, chunk\: {1}
 AbstractFileStringContentStream.getSize.exception.msg=Cannot tell how many chars in converted string, until entire string is converted
 AbstractFileStringContentStream.getSrcInfo.text=File\:{0}
 ByteContentStream.getSrcInfo.text=File\:{0}
@@ -187,7 +186,6 @@ Ingester.FscContentStream.getSrcInfo=File\:{0}
 Ingester.FscContentStream.getReader=Not supported yet.
 Ingester.NullContentStream.getSrcInfo.text=File\:{0}
 Ingester.NullContentStream.getReader=Not supported yet.
-Keyword.toString.text=Keyword'{'query\={0}, isLiteral\={1}, keywordType\={2}'}'
 KeywordSearch.moduleErr=Module Error
 KeywordSearch.fireNumIdxFileChg.moduleErr.msg=A module caused an error listening to KeywordSearch updates. See log to determine which module. Some data could be incomplete.
 KeywordSearchListsEncase.save.exception.msg=Not supported yet.
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties
index 827842afd26822191a8991156c6365698a10b51e..b96be1942903d022316a8d3a3613a5c600691b7e 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties
@@ -133,7 +133,6 @@ OptionsCategory_Keywords_KeywordSearchOptions=\u30ad\u30fc\u30ef\u30fc\u30c9\u69
 ExtractedContentPanel.pageOfLabel.text=of
 ExtractedContentPanel.pageCurLabel.text=-
 ExtractedContentPanel.pageTotalLabel.text=-
-AbstractFileChunk.index.exception.msg=\u30d5\u30a1\u30a4\u30eb\u30b9\u30c8\u30ea\u30f3\u30b0\u30c1\u30e3\u30f3\u30af\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u4e2d\u306b\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a {0}, \u30c1\u30e3\u30f3\u30af\: {1}
 AbstractFileStringContentStream.getSize.exception.msg=\u30b9\u30c8\u30ea\u30f3\u30b0\u5168\u4f53\u304c\u5909\u63db\u3055\u308c\u306a\u3051\u308c\u3070\u3001\u5909\u63db\u3055\u308c\u305f\u30b9\u30c8\u30ea\u30f3\u30b0\u5185\u306e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u6570\u306f\u4e0d\u660e\u3067\u3059\u3002
 AbstractFileStringContentStream.getSrcInfo.text=\u30d5\u30a1\u30a4\u30eb\uff1a{0}
 ByteContentStream.getSrcInfo.text=\u30d5\u30a1\u30a4\u30eb\uff1a{0}
@@ -208,7 +207,6 @@ KeywordSearchIngestModule.doInBackGround.pendingMsg=\uff08\u30da\u30f3\u30c7\u30
 SearchRunner.doInBackGround.cancelMsg=\uff08\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026\uff09
 Server.addDoc.exception.msg2=\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30cf\u30f3\u30c9\u30e9\u30fc\u3092\u4f7f\u7528\u3057\u307e\u3057\u305f\u304c\u3001\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u6b21\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0}
 ExtractedContentViewer.getSolrContent.txtBodyItal=<span style\=''font-style\:italic''>{0}</span>
-Keyword.toString.text=Keyword'{'query\={0}, isLiteral\={1}, keywordType\={2}'}'
 KeywordSearchJobSettingsPanel.keywordSearchEncodings.text=-
 KeywordSearchJobSettingsPanel.languagesValLabel.text=-
 KeywordSearchJobSettingsPanel.encodingsLabel.text=\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\uff1a
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java
index a58328c11376aa6a04243d9234d6a7b0503a9e07..7a1986d341110470ba9b7bdd3de7c11a6816af73 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java
@@ -562,8 +562,8 @@ private class KeywordTableEntry implements Comparable<KeywordTableEntry> {
             Boolean regex;
 
             KeywordTableEntry(Keyword keyword) {
-                this.name = keyword.getQuery();
-                this.regex = !keyword.isLiteral();
+                this.name = keyword.getSearchTerm();
+                this.regex = !keyword.searchTermIsLiteral();
             }
 
             @Override
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleKeywordSearchPanel.form
similarity index 100%
rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form
rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleKeywordSearchPanel.form
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleKeywordSearchPanel.java
similarity index 68%
rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java
rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleKeywordSearchPanel.java
index 4f5ffe545b35ef78af2265da7edb161a5ee1c127..6195f112ef2d3e51299405b713f26b75a6a4247b 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleKeywordSearchPanel.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2014 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@
  */
 package org.sleuthkit.autopsy.keywordsearch;
 
-import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
@@ -27,11 +26,15 @@
 import java.util.List;
 import java.util.logging.Level;
 import javax.swing.JMenuItem;
-
 import org.sleuthkit.autopsy.coreutils.Logger;
 
 /**
- * A simple UI for finding text after ingest
+ * A dropdown panel that provides GUI components that allow a user to do three
+ * types of ad hoc single keyword searches. The first option is a standard
+ * Lucene query for one or more terms, with or without wildcards and explicit
+ * Boolean operators, or a phrase. The second option is a Lucene query for a
+ * substring of a single rerm. The third option is a regex query using first the
+ * terms component, followed by standard Lucene queries for any terms found.
  *
  * The toolbar uses a different font from the rest of the application,
  * Monospaced 14, due to the necessity to find a font that displays both Arabic
@@ -39,48 +42,62 @@
  * perform this task at the desired size, and neither could numerous other
  * fonts.
  */
-public class DropdownSingleTermSearchPanel extends KeywordSearchPanel {
+public class DropdownSingleKeywordSearchPanel extends KeywordSearchPanel {
+
+    private static final long serialVersionUID = 1L;
+    private static final Logger LOGGER = Logger.getLogger(DropdownSingleKeywordSearchPanel.class.getName());
+    private static DropdownSingleKeywordSearchPanel defaultInstance = null;
 
-    private static final Logger logger = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName());
-    private static DropdownSingleTermSearchPanel instance = null;
+    /**
+     * Gets the default instance of a dropdown panel that provides GUI
+     * components that allow a user to do three types of ad hoc keyword
+     * searches.
+     */
+    public static synchronized DropdownSingleKeywordSearchPanel getDefault() {
+        if (null == defaultInstance) {
+            defaultInstance = new DropdownSingleKeywordSearchPanel();
+        }
+        return defaultInstance;
+    }
 
     /**
-     * Creates new form DropdownSingleTermSearchPanel
+     * Constructs a dropdown panel that provides GUI components that allow a
+     * user to do three types of ad hoc searches.
      */
-    public DropdownSingleTermSearchPanel() {
+    public DropdownSingleKeywordSearchPanel() {
         initComponents();
         customizeComponents();
     }
 
+    /**
+     * Does additional initialization of the GUI components created by the
+     * initComponents method.
+     */
     private void customizeComponents() {
         keywordTextField.addFocusListener(new FocusListener() {
             @Override
             public void focusGained(FocusEvent e) {
-                //do nothing
             }
 
             @Override
             public void focusLost(FocusEvent e) {
                 if (keywordTextField.getText().equals("")) {
-                    resetSearchBox();
+                    clearSearchBox();
                 }
             }
         });
 
         keywordTextField.setComponentPopupMenu(rightClickMenu);
-        ActionListener actList = new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                JMenuItem jmi = (JMenuItem) e.getSource();
-                if (jmi.equals(cutMenuItem)) {
-                    keywordTextField.cut();
-                } else if (jmi.equals(copyMenuItem)) {
-                    keywordTextField.copy();
-                } else if (jmi.equals(pasteMenuItem)) {
-                    keywordTextField.paste();
-                } else if (jmi.equals(selectAllMenuItem)) {
-                    keywordTextField.selectAll();
-                }
+        ActionListener actList = (ActionEvent e) -> {
+            JMenuItem jmi = (JMenuItem) e.getSource();
+            if (jmi.equals(cutMenuItem)) {
+                keywordTextField.cut();
+            } else if (jmi.equals(copyMenuItem)) {
+                keywordTextField.copy();
+            } else if (jmi.equals(pasteMenuItem)) {
+                keywordTextField.paste();
+            } else if (jmi.equals(selectAllMenuItem)) {
+                keywordTextField.selectAll();
             }
         };
         cutMenuItem.addActionListener(actList);
@@ -89,36 +106,43 @@ public void actionPerformed(ActionEvent e) {
         selectAllMenuItem.addActionListener(actList);
     }
 
-    public static synchronized DropdownSingleTermSearchPanel getDefault() {
-        if (instance == null) {
-            instance = new DropdownSingleTermSearchPanel();
-        }
-        return instance;
-    }
-
+    /**
+     * Add an action listener to the Search buttom component of the panel.
+     *
+     * @param actionListener The actin listener.
+     */
     void addSearchButtonActionListener(ActionListener actionListener) {
         searchButton.addActionListener(actionListener);
     }
 
-    void resetSearchBox() {
+    /**
+     * Clears the text in the query text field, i.e., sets it to the emtpy
+     * string.
+     */
+    void clearSearchBox() {
         keywordTextField.setText("");
     }
 
+    /**
+     * Gets a single keyword list consisting of a single keyword encapsulating
+     * the input term(s)/phrase/substring/regex.
+     *
+     * @return The keyword list.
+     */
     @Override
     List<KeywordList> getKeywordLists() {
         List<Keyword> keywords = new ArrayList<>();
-        keywords.add(new Keyword(keywordTextField.getText(),
-                !regexRadioButton.isSelected(), exactRadioButton.isSelected()));
-
+        keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected()));
         List<KeywordList> keywordLists = new ArrayList<>();
         keywordLists.add(new KeywordList(keywords));
-
         return keywordLists;
     }
 
+    /**
+     * Not implemented.
+     */
     @Override
     protected void postFilesIndexedChange() {
-        //nothing to update
     }
 
     /**
@@ -142,20 +166,20 @@ private void initComponents() {
         substringRadioButton = new javax.swing.JRadioButton();
         regexRadioButton = new javax.swing.JRadioButton();
 
-        org.openide.awt.Mnemonics.setLocalizedText(cutMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.cutMenuItem.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(cutMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.cutMenuItem.text")); // NOI18N
         rightClickMenu.add(cutMenuItem);
 
-        org.openide.awt.Mnemonics.setLocalizedText(copyMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.copyMenuItem.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(copyMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.copyMenuItem.text")); // NOI18N
         rightClickMenu.add(copyMenuItem);
 
-        org.openide.awt.Mnemonics.setLocalizedText(pasteMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.pasteMenuItem.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(pasteMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.pasteMenuItem.text")); // NOI18N
         rightClickMenu.add(pasteMenuItem);
 
-        org.openide.awt.Mnemonics.setLocalizedText(selectAllMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.selectAllMenuItem.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(selectAllMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.selectAllMenuItem.text")); // NOI18N
         rightClickMenu.add(selectAllMenuItem);
 
-        keywordTextField.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N NON-NLS
-        keywordTextField.setText(org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.keywordTextField.text")); // NOI18N
+        keywordTextField.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N
+        keywordTextField.setText(org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.keywordTextField.text")); // NOI18N
         keywordTextField.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(192, 192, 192), 1, true));
         keywordTextField.setMinimumSize(new java.awt.Dimension(2, 25));
         keywordTextField.setPreferredSize(new java.awt.Dimension(2, 25));
@@ -170,8 +194,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
             }
         });
 
-        searchButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/search-icon.png"))); // NOI18N NON-NLS
-        org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.searchButton.text")); // NOI18N
+        searchButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/search-icon.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.searchButton.text")); // NOI18N
         searchButton.addActionListener(new java.awt.event.ActionListener() {
             public void actionPerformed(java.awt.event.ActionEvent evt) {
                 searchButtonActionPerformed(evt);
@@ -180,13 +204,13 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
 
         queryTypeButtonGroup.add(exactRadioButton);
         exactRadioButton.setSelected(true);
-        org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.exactRadioButton.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.exactRadioButton.text")); // NOI18N
 
         queryTypeButtonGroup.add(substringRadioButton);
-        org.openide.awt.Mnemonics.setLocalizedText(substringRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.substringRadioButton.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(substringRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.substringRadioButton.text")); // NOI18N
 
         queryTypeButtonGroup.add(regexRadioButton);
-        org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.regexRadioButton.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleKeywordSearchPanel.class, "DropdownSearchPanel.regexRadioButton.text")); // NOI18N
 
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
@@ -224,18 +248,33 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
         );
     }// </editor-fold>//GEN-END:initComponents
 
+    /**
+     * Action performed by the action listener for the search button.
+     *
+     * @param evt The action event.
+     */
     private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
         keywordTextFieldActionPerformed(evt);
     }//GEN-LAST:event_searchButtonActionPerformed
 
+    /**
+     * Action performed by the action listener for the keyword text field.
+     *
+     * @param evt The action event.
+     */
     private void keywordTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keywordTextFieldActionPerformed
         try {
             search();
         } catch (Exception e) {
-            logger.log(Level.SEVERE, "search() threw exception", e); //NON-NLS
+            LOGGER.log(Level.SEVERE, "Error performing ad hoc single keyword search", e); //NON-NLS
         }
     }//GEN-LAST:event_keywordTextFieldActionPerformed
 
+    /**
+     * Mouse event handler for the keyword text field.
+     *
+     * @param evt The mouse event.
+     */
     private void keywordTextFieldMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_keywordTextFieldMouseClicked
         if (evt.isPopupTrigger()) {
             rightClickMenu.show(evt.getComponent(), evt.getX(), evt.getY());
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java
index 7c24b99337d5d0be1ecc7c7d49a497df67c42911..883a1e8f3982ff4d5fbd7a2b8b8910d7cbd7958b 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java
@@ -46,7 +46,7 @@ class DropdownToolbar extends javax.swing.JPanel {
     private KeywordPropertyChangeListener listener;
     private boolean active = false;
     private static DropdownToolbar instance;
-    private DropdownSingleTermSearchPanel dropPanel = null;
+    private DropdownSingleKeywordSearchPanel dropPanel = null;
 
     private DropdownToolbar() {
         initComponents();
@@ -92,7 +92,7 @@ public void popupMenuCanceled(PopupMenuEvent e) {
             }
         });
 
-        dropPanel = DropdownSingleTermSearchPanel.getDefault();
+        dropPanel = DropdownSingleKeywordSearchPanel.getDefault();
         dropPanel.addSearchButtonActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
@@ -222,7 +222,7 @@ private class KeywordPropertyChangeListener implements PropertyChangeListener {
         public void propertyChange(PropertyChangeEvent evt) {
             String changed = evt.getPropertyName();
             if (changed.equals(Case.Events.CURRENT_CASE.toString())) {
-                dropPanel.resetSearchBox();
+                dropPanel.clearSearchBox();
                 setFields(null != evt.getNewValue() && RuntimeProperties.coreComponentsAreActive());
             } else if (changed.equals(Server.CORE_EVT)) {
                 final Server.CORE_EVT_STATES state = (Server.CORE_EVT_STATES) evt.getNewValue();
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/EnCaseKeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/EnCaseKeywordSearchList.java
index 791fdda642f78529785f30b717f2f692b416610c..ff5792e60f293d5756cc8a113ef5c00a7b780495 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/EnCaseKeywordSearchList.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/EnCaseKeywordSearchList.java
@@ -166,9 +166,9 @@ public boolean load() {
             return true;
 
         } catch (FileNotFoundException ex) {
-            logger.log(Level.INFO, "File at " + filePath + " does not exist!", ex); //NON-NLS
+            LOGGER.log(Level.INFO, "File at " + filePath + " does not exist!", ex); //NON-NLS
         } catch (IOException ex) {
-            logger.log(Level.INFO, "Failed to read file at " + filePath, ex); //NON-NLS
+            LOGGER.log(Level.INFO, "Failed to read file at " + filePath, ex); //NON-NLS
         }
         return false;
     }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java
index 5a52b542c4866ae56288a3994c7c8700903f0767..443c25aa5e88f8928adb46188b9c5fa96b708a7f 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-16 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -648,12 +648,7 @@ private void scrollToCurrentHit(final IndexedText source) {
         }
 
         //scrolling required invokeLater to enqueue in EDT
-        EventQueue.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                scrollToAnchor(source.getAnchorPrefix() + Integer.toString(source.currentItem()));
-            }
-        });
+        EventQueue.invokeLater(() -> scrollToAnchor(source.getAnchorPrefix() + source.currentItem()));
 
     }
 
@@ -672,10 +667,10 @@ private void setMarkup(IndexedText source) {
      * thread and then set the panel text in the EDT Helps not to block the UI
      * while content from Solr is retrieved.
      */
-    private final class SetMarkupWorker extends SwingWorker<Object, Void> {
+    private final class SetMarkupWorker extends SwingWorker<String, Void> {
+
+        private final IndexedText source;
 
-        private IndexedText source;
-        private String markup;
         private ProgressHandle progress;
 
         SetMarkupWorker(IndexedText source) {
@@ -683,38 +678,38 @@ private final class SetMarkupWorker extends SwingWorker<Object, Void> {
         }
 
         @Override
-        protected Object doInBackground() throws Exception {
-            progress = ProgressHandle.createHandle(
-                    NbBundle.getMessage(this.getClass(), "ExtractedContentPanel.SetMarkup.progress.loading"));
-            progress.setDisplayName(
-                    NbBundle.getMessage(this.getClass(), "ExtractedContentPanel.SetMarkup.progress.displayName"));
+        protected String doInBackground() throws Exception {
+            progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "ExtractedContentPanel.SetMarkup.progress.loading"));
+            progress.setDisplayName(NbBundle.getMessage(this.getClass(), "ExtractedContentPanel.SetMarkup.progress.displayName"));
             progress.start();
             progress.switchToIndeterminate();
 
-            markup = source.getText();
-            return null;
+            return source.getText();
         }
 
+        @NbBundle.Messages({
+            "ExtractedContentPanel.SetMarkup.error=There was an error getting the text for the selected source."})
         @Override
         protected void done() {
-            //super.done();
+            super.done();
             progress.finish();
 
             // see if there are any errors
-            // @@@ BC: Display the errors to the user somehow
             try {
-                get();
+                String markup = get();
+                if (markup != null) {
+                    setPanelText(markup, true);
+                } else {
+                    setPanelText("", false);
+                }
+
             } catch (InterruptedException | ExecutionException ex) {
-                logger.log(Level.SEVERE, "Error getting marked up text"); //NON-NLS
+                logger.log(Level.SEVERE, "Error getting marked up text", ex); //NON-NLS
+                setPanelText(Bundle.ExtractedContentPanel_SetMarkup_error(), true);
             } // catch and ignore if we were cancelled
             catch (java.util.concurrent.CancellationException ex) {
             }
 
-            if (markup != null) {
-                setPanelText(markup, true);
-            } else {
-                setPanelText("", false);
-            }
             updateControls(source);
 
             scrollToCurrentHit(source);
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java
index 01db29ab19b9c560dbf1b282a4755463c97eecf0..f147e161702556d3ddbc33cd58c11bee87fa9944 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java
@@ -24,20 +24,26 @@
 import java.awt.event.ActionListener;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.logging.Level;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
+import org.apache.commons.lang.StringUtils;
 import org.openide.nodes.Node;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
 import org.openide.util.lookup.ServiceProvider;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
+import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.datamodel.BlackboardArtifact;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
+import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
 import org.sleuthkit.datamodel.Content;
-import org.sleuthkit.datamodel.ContentVisitor;
-import org.sleuthkit.datamodel.Directory;
 import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.datamodel.BlackboardAttribute;
 
 /**
  * A content viewer that displays the indexed text associated with a file or an
@@ -47,7 +53,10 @@
 public class ExtractedContentViewer implements DataContentViewer {
 
     private static final Logger logger = Logger.getLogger(ExtractedContentViewer.class.getName());
+
     private static final long INVALID_DOCUMENT_ID = 0L;
+    private static final BlackboardAttribute.Type TSK_ASSOCIATED_ARTIFACT_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT);
+
     private ExtractedContentPanel panel;
     private volatile Node currentNode = null;
     private IndexedText currentSource = null;
@@ -67,9 +76,7 @@ public ExtractedContentViewer() {
      */
     @Override
     public void setNode(final Node node) {
-        /*
-         * Clear the viewer.
-         */
+        // Clear the viewer.
         if (node == null) {
             currentNode = null;
             resetComponent();
@@ -86,30 +93,86 @@ public void setNode(final Node node) {
             currentNode = node;
         }
 
+        Lookup nodeLookup = node.getLookup();
+        Content content = nodeLookup.lookup(Content.class);
+
+
         /*
          * Assemble a collection of all of the indexed text "sources" associated
          * with the node.
          */
+        List<IndexedText> sources = new ArrayList<>();
         IndexedText highlightedHitText = null;
         IndexedText rawContentText = null;
         IndexedText rawArtifactText = null;
-        List<IndexedText> sources = new ArrayList<>();
 
         /*
          * First add the text marked up with HTML to highlight keyword hits that
          * will be present in the selected node's lookup if the node is for a
-         * keyword hit artifact.
+         * keyword hit artifact or account.
          */
-        sources.addAll(node.getLookup().lookupAll(IndexedText.class));
+        sources.addAll(nodeLookup.lookupAll(IndexedText.class));
+
         if (!sources.isEmpty()) {
+            //if the look up had any sources use them and don't make a new one.
             highlightedHitText = sources.get(0);
+        } else if (null != content && solrHasContent(content.getId())) {//if the lookup didn't have any sources, and solr has indexed the content...
+            /*
+             * get all the credit card artifacts and make a AccountsText object
+             * that will highlight them.
+             */
+            String solrDocumentID = String.valueOf(content.getId()); //grab the object id as the solrDocumentID
+            Set<String> accountNumbers = new HashSet<>();
+            try {
+                //if the node had artifacts in the lookup use them, other wise look up all credit card artifacts for the content.
+                Collection<? extends BlackboardArtifact> artifacts = nodeLookup.lookupAll(BlackboardArtifact.class);
+                artifacts = (artifacts == null || artifacts.isEmpty())
+                        ? content.getArtifacts(TSK_ACCOUNT)
+                        : artifacts;
+
+                /*
+                 * For each artifact add the account number to the list of
+                 * accountNumbers to highlight, and use the solrDocumentId
+                 * attribute(in place of the content's object Id) if it exists
+                 *
+                 * NOTE: this assumes all the artifacts will be from the same
+                 * solrDocumentId
+                 */
+                for (BlackboardArtifact artifact : artifacts) {
+                    try {
+                        BlackboardAttribute solrIDAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID));
+                        if (solrIDAttr != null) {
+                            String valueString = solrIDAttr.getValueString();
+                            if (StringUtils.isNotBlank(valueString)) {
+                                solrDocumentID = valueString;
+                            }
+                        }
+
+                        BlackboardAttribute keyWordAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
+                        if (keyWordAttr != null) {
+                            String valueString = keyWordAttr.getValueString();
+                            if (StringUtils.isNotBlank(valueString)) {
+                                accountNumbers.add(valueString);
+                            }
+                        }
+
+                    } catch (TskCoreException ex) {
+                        logger.log(Level.SEVERE, "Failed to retrieve Blackboard Attributes", ex); //NON-NLS
+                    }
+                }
+                if (accountNumbers.isEmpty() == false) {
+                    highlightedHitText = new AccountsText(solrDocumentID, accountNumbers);
+                    sources.add(highlightedHitText);
+                }
+            } catch (TskCoreException ex) {
+                logger.log(Level.SEVERE, "Failed to retrieve Blackboard Artifacts", ex); //NON-NLS
+            }
         }
 
         /*
          * Next, add the "raw" (not highlighted) text, if any, for any content
          * associated with the node.
          */
-        Content content = currentNode.getLookup().lookup(Content.class);
         if (null != content && solrHasContent(content.getId())) {
             rawContentText = new RawText(content, content.getId());
             sources.add(rawContentText);
@@ -119,33 +182,32 @@ public void setNode(final Node node) {
          * Finally, add the "raw" (not highlighted) text, if any, for any
          * artifact associated with the node.
          */
-        BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
+        BlackboardArtifact artifact = nodeLookup.lookup(BlackboardArtifact.class);
         if (null != artifact) {
             /*
              * For keyword hit artifacts, add the text of the artifact that hit,
              * not the hit artifact; otherwise add the text for the artifact.
              */
-            if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
-                rawArtifactText = new RawText(artifact, artifact.getArtifactID());
-                sources.add(rawArtifactText);
-            } else {
+            if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID() || artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID()) {
                 try {
-                    BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
+                    BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE);
                     if (attribute != null) {
                         long artifactId = attribute.getValueLong();
                         BlackboardArtifact associatedArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(artifactId);
                         rawArtifactText = new RawText(associatedArtifact, associatedArtifact.getArtifactID());
                         sources.add(rawArtifactText);
-                    } 
+                    }
                 } catch (TskCoreException ex) {
                     logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS
                 }
+            } else {
+                rawArtifactText = new RawText(artifact, artifact.getArtifactID());
+                sources.add(rawArtifactText);
             }
+
         }
 
-        /*
-         * Now set the default source to be displayed.
-         */
+        // Now set the default source to be displayed.
         if (null != highlightedHitText) {
             currentSource = highlightedHitText;
         } else if (null != rawContentText) {
@@ -154,15 +216,13 @@ public void setNode(final Node node) {
             currentSource = rawArtifactText;
         }
 
-        /*
-         * Push the text sources into the panel.
-         */
+        // Push the text sources into the panel.
         for (IndexedText source : sources) {
             int currentPage = source.getCurrentPage();
             if (currentPage == 0 && source.hasNextPage()) {
                 source.nextPage();
-            }            
-        }        
+            }
+        }
         updatePageControls();
         setPanel(sources);
     }
@@ -207,7 +267,7 @@ public synchronized Component getComponent() {
 
     @Override
     public void resetComponent() {
-        setPanel(new ArrayList<IndexedText>());
+        setPanel(new ArrayList<>());
         panel.resetDisplay();
         currentNode = null;
         currentSource = null;
@@ -230,6 +290,18 @@ public boolean isSupported(Node node) {
             return true;
         }
 
+        /*
+         * Is there a credit card artifact in the lookup
+         */
+        Collection<? extends BlackboardArtifact> artifacts = node.getLookup().lookupAll(BlackboardArtifact.class);
+        if (artifacts != null) {
+            for (BlackboardArtifact art : artifacts) {
+                if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
+                    return true;
+                }
+            }
+        }
+
         /*
          * No highlighted text for a keyword hit, so is there any indexed text
          * at all for this node?
@@ -248,7 +320,8 @@ public int isPreferred(Node node) {
 
         if (art == null) {
             return 4;
-        } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
+        } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()
+                || art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
             return 6;
         } else {
             return 4;
@@ -267,19 +340,6 @@ private void setPanel(List<IndexedText> sources) {
         }
     }
 
-    private class IsDirVisitor extends ContentVisitor.Default<Boolean> {
-
-        @Override
-        protected Boolean defaultVisit(Content cntnt) {
-            return false;
-        }
-
-        @Override
-        public Boolean visit(Directory d) {
-            return true;
-        }
-    }
-
     /**
      * Check if Solr has extracted content for a given node
      *
@@ -321,8 +381,7 @@ private Long getDocumentId(Node node) {
             } else {
                 try {
                     // Get the associated artifact attribute and return its value as the ID
-                    BlackboardAttribute blackboardAttribute = artifact.getAttribute(
-                            new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
+                    BlackboardAttribute blackboardAttribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE);
                     if (blackboardAttribute != null) {
                         return blackboardAttribute.getValueLong();
                     }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java
index 49358618990ab20caa1a37d134b2f028260143c2..5ec5463d8edcc4e1d6e376c49206aa5d0d0889c9 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java
@@ -100,7 +100,7 @@ private void customizeComponents() {
         lsm.addListSelectionListener(new ListSelectionListener() {
             @Override
             public void valueChanged(ListSelectionEvent e) {
-                if (lsm.isSelectionEmpty() || currentKeywordList.isLocked() || IngestManager.getInstance().isIngestRunning()) {
+                if (lsm.isSelectionEmpty() || currentKeywordList.isEditable() || IngestManager.getInstance().isIngestRunning()) {
                     deleteWordButton.setEnabled(false);
                 } else {
                     deleteWordButton.setEnabled(true);
@@ -140,7 +140,7 @@ void setButtonStates() {
         listOptionsSeparator.setEnabled(canEditList);
 
         // items that need an unlocked list w/out ingest running
-        boolean isListLocked = ((isListSelected == false) || (currentKeywordList.isLocked()));
+        boolean isListLocked = ((isListSelected == false) || (currentKeywordList.isEditable()));
         boolean canAddWord = isListSelected && !isIngestRunning && !isListLocked;
         newWordButton.setEnabled(canAddWord);
         keywordOptionsLabel.setEnabled(canAddWord);
@@ -581,10 +581,10 @@ public Object getValueAt(int rowIndex, int columnIndex) {
             Keyword word = currentKeywordList.getKeywords().get(rowIndex);
             switch (columnIndex) {
                 case 0:
-                    ret = (Object) word.getQuery();
+                    ret = (Object) word.getSearchTerm();
                     break;
                 case 1:
-                    ret = (Object) !word.isLiteral();
+                    ret = (Object) !word.searchTermIsLiteral();
                     break;
                 default:
                     logger.log(Level.SEVERE, "Invalid table column index: {0}", columnIndex); //NON-NLS
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java
index 48893e213cdb525cf654ba732204011c1d3dcdb5..6bae3b666c1dfd2fa74990958a77f1696b950328 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java
@@ -82,7 +82,7 @@ public void actionPerformed(ActionEvent e) {
                 }
 
                 XmlKeywordSearchList writer = XmlKeywordSearchList.getCurrent();
-                if (writer.listExists(listName) && writer.getList(listName).isLocked()) {
+                if (writer.listExists(listName) && writer.getList(listName).isEditable()) {
                     KeywordSearchUtil.displayDialog(FEATURE_NAME, NbBundle.getMessage(this.getClass(), "KeywordSearchConfigurationPanel1.customizeComponents.noOwDefaultMsg"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
                     return;
                 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java
index 8d04e2a8f6154f5ce8d55975eb551e6456d2d26a..031ec3199f36c888b2d124a16756382d29d11214 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java
@@ -176,7 +176,7 @@ private void newListButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN
         }
         boolean shouldAdd = false;
         if (writer.listExists(listName)) {
-            if (writer.getList(listName).isLocked()) {
+            if (writer.getList(listName).isEditable()) {
                 boolean replace = KeywordSearchUtil.displayConfirmDialog(
                         NbBundle.getMessage(this.getClass(), "KeywordSearch.newKeywordListMsg"),
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsManagementPanel.newKeywordListDescription", listName),
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
index e7325f776c22e59e4076feaf6a24222c90251e52..169e9cb0ecfcda7b5ee52e2b1c9137c5d5b4c9e1 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2015 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -59,7 +59,7 @@ class Ingester {
     //for ingesting chunk as SolrInputDocument (non-content-streaming, by-pass tika)
     //TODO use a streaming way to add content to /update handler
     private static final int MAX_DOC_CHUNK_SIZE = 1024 * 1024;
-    private static final String docContentEncoding = "UTF-8"; //NON-NLS
+    private static final String ENCODING = "UTF-8"; //NON-NLS
 
     private Ingester() {
     }
@@ -134,7 +134,7 @@ void ingest(AbstractFileChunk fec, ByteContentStream bcs, int size) throws Inges
 
         //overwrite id with the chunk id
         params.put(Server.Schema.ID.toString(),
-                Server.getChunkIdString(sourceContent.getId(), fec.getChunkId()));
+                Server.getChunkIdString(sourceContent.getId(), fec.getChunkNumber()));
 
         ingest(bcs, params, size);
     }
@@ -298,7 +298,7 @@ void ingest(ContentStream cs, Map<String, String> fields, final long size) throw
             if (read != 0) {
                 String s = "";
                 try {
-                    s = new String(docChunkContentBuf, 0, read, docContentEncoding);
+                    s = new String(docChunkContentBuf, 0, read, ENCODING);
                     // Sanitize by replacing non-UTF-8 characters with caret '^' before adding to index
                     char[] chars = null;
                     for (int i = 0; i < s.length(); i++) {
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java
index 293244c6f19931ea602317a8e445f1fc161dfae5..93acd0ee784a1705ca010eff81d7719a8243ea99 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2014 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,81 +18,136 @@
  */
 package org.sleuthkit.autopsy.keywordsearch;
 
-import org.openide.util.NbBundle;
 import org.sleuthkit.datamodel.BlackboardAttribute;
 
 /**
- * Representation of single keyword to search for
+ * A representation of a keyword for which to search. The search term for the
+ * keyword may be either a literal term, to be treated as either a whole word or
+ * a substring, or a regex.
+ *
+ * It is currently possible to optionally associate an artifact attribute type
+ * with a keyword. This feature was added to support an initial implementation
+ * of account number search and may be removed in the future.
  */
 class Keyword {
 
-    private String keywordString;   // keyword to search for
-    private boolean isLiteral;  // false if reg exp
-    private boolean isWholeword; // false if match a substring
-    private BlackboardAttribute.ATTRIBUTE_TYPE keywordType = null;
+    private String searchTerm;
+    private boolean isLiteral;
+    private boolean isWholeWord;
+    private BlackboardAttribute.ATTRIBUTE_TYPE artifactAtrributeType;
 
     /**
+     * Constructs a representation of a keyword for which to search. The search
+     * term for the keyword may be either a literal term that will be treated as
+     * a whole word, or a regex.
      *
-     * @param query     Keyword to search for
-     * @param isLiteral false if reg exp
+     * @param searchTerm The search term for the keyword.
+     * @param isLiteral  Whether or not the search term is a literal term that
+     *                   will be treated as a whole word, instead of a regex.
      */
-    Keyword(String query, boolean isLiteral) {
-        this.keywordString = query;
+    Keyword(String searchTerm, boolean isLiteral) {
+        this.searchTerm = searchTerm;
         this.isLiteral = isLiteral;
-        this.isWholeword = true;
+        this.isWholeWord = true;
     }
 
     /**
+     * Constructs a representation of a keyword for which to search. The search
+     * term may be either a literal term, to be treated as either a whole word
+     * or as a substring, or a regex.
      *
-     * @param query       Keyword to search for
-     * @param isLiteral   false if reg exp
-     * @param isWholeword false to match substring (undefined behavior if regexp
-     *                    is true)
+     * @param searchTerm  The search term.
+     * @param isLiteral   Whether or not the search term is a literal term,
+     *                    instead of a regex.
+     * @param isWholeWord Whether or not the search term, if it is a literal
+     *                    search term, should be treated as a whole word rather
+     *                    than a substring.
      */
-    Keyword(String query, boolean isLiteral, boolean isWholeword) {
-        this.keywordString = query;
+    Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord) {
+        this.searchTerm = searchTerm;
         this.isLiteral = isLiteral;
-        this.isWholeword = isWholeword;
+        this.isWholeWord = isWholeWord;
     }
 
     /**
+     * Constructs a representation of a keyword for which to search, for the
+     * purpose of finding a specific artifact attribute. The search term may be
+     * either a literal term, to be treated as a whole word, or a regex.
+     *
+     * The association of an artifact attribute type with a keyword was added to
+     * support an initial implementation of account number search and may be
+     * removed in the future.
      *
-     * @param query       Keyword to search for
-     * @param isLiteral   false if reg exp
-     * @param keywordType
+     * @param searchTerm  The search term.
+     * @param isLiteral   Whether or not the search term is a literal term, to
+     *                    be treated as a whole word, instead of a regex.
+     * @param keywordType The artifact attribute type.
      */
-    Keyword(String query, boolean isLiteral, BlackboardAttribute.ATTRIBUTE_TYPE keywordType) {
-        this(query, isLiteral);
-        this.keywordType = keywordType;
+    Keyword(String searchTerm, boolean isLiteral, BlackboardAttribute.ATTRIBUTE_TYPE artifactAtrributeType) {
+        this(searchTerm, isLiteral);
+        this.artifactAtrributeType = artifactAtrributeType;
     }
 
-    void setType(BlackboardAttribute.ATTRIBUTE_TYPE keywordType) {
-        this.keywordType = keywordType;
+    /**
+     * Gets the search term for the keyword, which may be either a literal term
+     * or a regex.
+     *
+     * @return The search term.
+     */
+    String getSearchTerm() {
+        return searchTerm;
     }
 
-    BlackboardAttribute.ATTRIBUTE_TYPE getType() {
-        return this.keywordType;
+    /**
+     * Indicates whether the search term for the keyword is a literal term or a
+     * regex.
+     *
+     * @return True or false.
+     */
+    boolean searchTermIsLiteral() {
+        return isLiteral;
     }
 
     /**
+     * Indicates whether or not the search term for the keyword, if it is a
+     * literal term and not a regex, will be treated as a whole word or as a
+     * substring.
      *
-     * @return Keyword to search for
+     * @return True or false.
      */
-    String getQuery() {
-        return keywordString;
+    boolean searchTermIsWholeWord() {
+        return isWholeWord;
     }
 
-    boolean isLiteral() {
-        return isLiteral;
+    /**
+     * Sets the artifact attribute type associated with the keyword, if any.
+     *
+     * The association of an artifact attribute type with the keyword was added
+     * to support an initial implementation of account number search and may be
+     * removed in the future.
+     *
+     * @param artifactAtrributeType
+     */
+    void setArtifactAttributeType(BlackboardAttribute.ATTRIBUTE_TYPE artifactAtrributeType) {
+        this.artifactAtrributeType = artifactAtrributeType;
     }
 
-    boolean isWholeword() {
-        return isWholeword;
+    /**
+     * Gets the artifact attribute type associated with the keyword, if any.
+     *
+     * The association of an artifact attribute type with the keyword was added
+     * to support an initial implementation of account number search and may be
+     * removed in the future.
+     *
+     * @return A attribute type object or null.
+     */
+    BlackboardAttribute.ATTRIBUTE_TYPE getArtifactAttributeType() {
+        return this.artifactAtrributeType;
     }
 
     @Override
     public String toString() {
-        return NbBundle.getMessage(this.getClass(), "Keyword.toString.text", keywordString, isLiteral, keywordType);
+        return String.format("Keyword{searchTerm='%s', isLiteral=%s, isWholeWord=%s}", searchTerm, isLiteral, isWholeWord);
     }
 
     @Override
@@ -103,21 +158,19 @@ public boolean equals(Object obj) {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final Keyword other = (Keyword) obj;
-        if ((this.keywordString == null) ? (other.keywordString != null) : !this.keywordString.equals(other.keywordString)) {
-            return false;
-        }
-        if (this.isLiteral != other.isLiteral) {
-            return false;
-        }
-        return true;
+        Keyword other = (Keyword) obj;
+        return (this.searchTerm.equals(other.searchTerm)
+                && this.isLiteral == other.isLiteral
+                && this.isWholeWord == other.isWholeWord);
     }
 
     @Override
     public int hashCode() {
         int hash = 7;
-        hash = 17 * hash + (this.keywordString != null ? this.keywordString.hashCode() : 0);
+        hash = 17 * hash + this.searchTerm.hashCode();
         hash = 17 * hash + (this.isLiteral ? 1 : 0);
+        hash = 17 * hash + (this.isWholeWord ? 1 : 0);
         return hash;
     }
+
 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java
index cf86ce20c92f9819bc0e384806f6ef260a1c7c56..5425048ee4e37b6adebb8a1f837a3e8337da1399 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordHit.java
@@ -54,7 +54,7 @@ class KeywordHit {
          * documents. One contains object metadata (chunk #1) and the second and
          * subsequent documents contain chunks of the text.
          */
-        final int separatorIndex = solrDocumentId.indexOf(Server.ID_CHUNK_SEP);
+        final int separatorIndex = solrDocumentId.indexOf(Server.CHUNK_ID_SEPARATOR);
         if (-1 != separatorIndex) {
             this.solrObjectId = Long.parseLong(solrDocumentId.substring(0, separatorIndex));
             this.chunkId = Integer.parseInt(solrDocumentId.substring(separatorIndex + 1));
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java
index 2dd2933fa9abcac917214423c06d9eb042d2d888..13f094a89d7be078dcd66f6c1ac8c3e133a05d2d 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011-2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2016 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.
@@ -21,32 +21,75 @@
 import java.util.Date;
 import java.util.List;
 
+/**
+ * A list of keywords for which to search. Includes list creation and
+ * modification metadata and settings that determine how the list is to be used
+ * when ingesting a data source. Standard lists provided by Autopsy may be
+ * marked as not editable.
+ */
 public class KeywordList {
 
     private String name;
     private Date created;
     private Date modified;
     private Boolean useForIngest;
-    private Boolean ingestMessages;
+    private Boolean postIngestMessages;
     private List<Keyword> keywords;
-    private Boolean locked;
+    private Boolean isEditable;
 
-    KeywordList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords, boolean locked) {
+    /**
+     * Constructs a list of keywords for which to search. Includes list creation
+     * and modification metadata and settings that determine how the list is to
+     * be used when ingesting a data source. Standard lists provided by Autopsy
+     * may be marked as not editable.
+     *
+     * @param name               The name to asociate with the list.
+     * @param created            When the list was created.
+     * @param modified           When the list was last modified.
+     * @param useForIngest       Whether or not the list is to be used when
+     *                           ingesting a data source.
+     * @param postIngestMessages Whether or not to post ingest inbox messages
+     *                           when a keyword within the list is found while
+     *                           ingesting a data source.
+     * @param keywords           The keywords that make up the list.
+     * @param isEditable         Whether or not the list may be edited by a
+     *                           user; standard lists provided by Autopsy should
+     *                           not be edited.
+     */
+    KeywordList(String name, Date created, Date modified, Boolean useForIngest, Boolean postIngestMessages, List<Keyword> keywords, boolean isEditable) {
         this.name = name;
         this.created = created;
         this.modified = modified;
         this.useForIngest = useForIngest;
-        this.ingestMessages = ingestMessages;
+        this.postIngestMessages = postIngestMessages;
         this.keywords = keywords;
-        this.locked = locked;
+        this.isEditable = isEditable;
     }
 
+    /**
+     * Constructs a list of keywords for which to search. Includes list creation
+     * and modification metadata and settings that determine how the list is to
+     * be used when ingesting a data source. The list will be marked as a
+     * standard lists provided by Autopsy that should not be treated as
+     * editable.
+     *
+     * @param name               The name to asociate with the list.
+     * @param created            When the list was created.
+     * @param modified           When the list was last modified.
+     * @param useForIngest       Whether or not the list is to be used when
+     *                           ingesting a data source.
+     * @param postIngestMessages Whether or not to post ingest inbox messages
+     *                           when a keyword within the list is found while
+     *                           ingesting a data source.
+     * @param keywords           The keywords that make up the list.
+     */
     KeywordList(String name, Date created, Date modified, Boolean useForIngest, Boolean ingestMessages, List<Keyword> keywords) {
         this(name, created, modified, useForIngest, ingestMessages, keywords, false);
     }
 
     /**
-     * Create an unnamed list. Usually used for ad-hoc searches
+     * Constructs a temporary list of keywords to be used for ad hoc keyword
+     * search and then discarded.
      *
      * @param keywords
      */
@@ -54,74 +97,116 @@ public class KeywordList {
         this("", new Date(0), new Date(0), false, false, keywords, false);
     }
 
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final KeywordList other = (KeywordList) obj;
-        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 5;
-        return hash;
-    }
-
+    /**
+     * Get the name assigned to the keyword list.
+     *
+     * @return The list name.
+     */
     String getName() {
         return name;
     }
 
+    /**
+     * Gets the date the keyword list was created.
+     *
+     * @return The date.
+     */
     Date getDateCreated() {
         return created;
     }
 
+    /**
+     * Gets the date the keyword list was last modified.
+     *
+     * @return The date.
+     */
     Date getDateModified() {
         return modified;
     }
 
+    /**
+     * Gets whether or not the list should be used when ingesting a data source.
+     *
+     * @return True or false.
+     */
     Boolean getUseForIngest() {
         return useForIngest;
     }
 
-    void setUseForIngest(boolean use) {
-        this.useForIngest = use;
+    /**
+     * Sets whether or not the list should be used when ingesting a data source.
+     *
+     * @param useForIngest True or false.
+     */
+    void setUseForIngest(boolean useForIngest) {
+        this.useForIngest = useForIngest;
     }
 
+    /**
+     * Gets whether or not to post ingest inbox messages when a keyword within
+     * the list is found while ingesting a data source.
+     *
+     * @return true or false
+     */
     Boolean getIngestMessages() {
-        return ingestMessages;
+        return postIngestMessages;
     }
 
-    void setIngestMessages(boolean ingestMessages) {
-        this.ingestMessages = ingestMessages;
+    /**
+     * Sets whether or not to post ingest inbox messages when a keyword within
+     * the list is found while ingesting a data source.
+     *
+     * @param postIngestMessages True or false.
+     */
+    void setIngestMessages(boolean postIngestMessages) {
+        this.postIngestMessages = postIngestMessages;
     }
 
+    /**
+     * Gets the keywords included in the list
+     *
+     * @return A colleciton of Keyword objects.
+     */
     List<Keyword> getKeywords() {
         return keywords;
     }
 
+    /**
+     * Indicates whether or not a given keyword is included in the list.
+     *
+     * @param keyword The keyword of interest.
+     *
+     * @return
+     */
     boolean hasKeyword(Keyword keyword) {
         return keywords.contains(keyword);
     }
 
-    boolean hasKeyword(String keyword) {
-        //note, this ignores isLiteral
-        for (Keyword k : keywords) {
-            if (k.getQuery().equals(keyword)) {
+    /**
+     * Indicates whether or not a given search term is included in the list.
+     *
+     * @param searchTerm The search term.
+     *
+     * @return True or false.
+     */
+    boolean hasSearchTerm(String searchTerm) {
+        for (Keyword word : keywords) {
+            if (word.getSearchTerm().equals(searchTerm)) {
                 return true;
             }
         }
         return false;
     }
 
-    Boolean isLocked() {
-        return locked;
+    /**
+     * Indicates Whether or not the list should be editable by a user; standard
+     * lists provided by Autopsy should be marked as not editable when they are
+     * contructed.
+     *
+     * @return True or false.
+     */
+    Boolean isEditable() {
+        return isEditable;
     }
+
 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java
index 886a0617b058453fbd60f93f518aedabcce1f4c0..79e09cd7882f0c20a1b786f04dfcad12c90fd6d9 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2011-2014 Basis Technology Corp.
+ *
+ * Copyright 2011-2016 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.
@@ -26,21 +26,28 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-
+import java.util.logging.Level;
 import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.datamodel.BlackboardAttribute;
 import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
-import java.util.logging.Level;
+import org.sleuthkit.datamodel.BlackboardAttribute;
 
 /**
  * Keyword list saving, loading, and editing abstract class.
  */
 abstract class KeywordSearchList {
 
+    protected static final Logger LOGGER = Logger.getLogger(KeywordSearchList.class.getName());
+
+    private static final String PHONE_NUMBER_REGEX = "[(]{0,1}\\d\\d\\d[)]{0,1}[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d";  //NON-NLS
+    private static final String IP_ADDRESS_REGEX = "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])";  //NON-NLS
+    private static final String EMAIL_ADDRESS_REGEX = "(?=.{8})[a-z0-9%+_-]+(?:\\.[a-z0-9%+_-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,4}(?<!\\.txt|\\.exe|\\.dll|\\.jpg|\\.xml)";  //NON-NLS
+    private static final String URL_REGEX = "((((ht|f)tp(s?))\\://)|www\\.)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,5})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\+&amp;%\\$#\\=~_\\-]+))*";  //NON-NLS
+    private static final String CCN_REGEX = ".*[3456]([ -]?\\d){11,18}.*";  //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS
+
     protected String filePath;
     Map<String, KeywordList> theLists; //the keyword data 
-    protected static final Logger logger = Logger.getLogger(KeywordSearchList.class.getName());
+
     PropertyChangeSupport changeSupport;
     protected List<String> lockedLists;
 
@@ -58,19 +65,22 @@ abstract class KeywordSearchList {
      */
     enum ListsEvt {
 
-        LIST_ADDED, LIST_DELETED, LIST_UPDATED
+        LIST_ADDED,
+        LIST_DELETED,
+        LIST_UPDATED
     };
 
     enum LanguagesEvent {
 
-        LANGUAGES_CHANGED, ENCODINGS_CHANGED
+        LANGUAGES_CHANGED,
+        ENCODINGS_CHANGED
     }
 
     void fireLanguagesEvent(LanguagesEvent event) {
         try {
             changeSupport.firePropertyChange(event.toString(), null, null);
         } catch (Exception e) {
-            logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+            LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
         }
     }
 
@@ -88,41 +98,33 @@ private void prepopulateLists() {
         }
         //phone number
         List<Keyword> phones = new ArrayList<>();
-        phones.add(new Keyword("[(]{0,1}\\d\\d\\d[)]{0,1}[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)); //NON-NLS
-        //phones.add(new Keyword("\\d{8,10}", false));
+        phones.add(new Keyword(PHONE_NUMBER_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
+        lockedLists.add("Phone Numbers");
+        addList("Phone Numbers", phones, false, false, true);
+
         //IP address
         List<Keyword> ips = new ArrayList<>();
-        ips.add(new Keyword("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IP_ADDRESS));
+        ips.add(new Keyword(IP_ADDRESS_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IP_ADDRESS));
+        lockedLists.add("IP Addresses");
+        addList("IP Addresses", ips, false, false, true);
+
         //email
         List<Keyword> emails = new ArrayList<>();
-        emails.add(new Keyword("(?=.{8})[a-z0-9%+_-]+(?:\\.[a-z0-9%+_-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,4}(?<!\\.txt|\\.exe|\\.dll|\\.jpg|\\.xml)", //NON-NLS
-                false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
-        //emails.add(new Keyword("[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}", 
-        //                       false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
+        emails.add(new Keyword(EMAIL_ADDRESS_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
+        lockedLists.add("Email Addresses");
+        addList("Email Addresses", emails, true, false, true);
+
         //URL
         List<Keyword> urls = new ArrayList<>();
-        //urls.add(new Keyword("http://|https://|^www\\.", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
-        urls.add(new Keyword("((((ht|f)tp(s?))\\://)|www\\.)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,5})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\+&amp;%\\$#\\=~_\\-]+))*", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)); //NON-NLS
-
-        //urls.add(new Keyword("ssh://", false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
-        //disable messages for harcoded/locked lists
-        String name;
-
-        name = "Phone Numbers"; //NON-NLS
-        lockedLists.add(name);
-        addList(name, phones, false, false, true);
-
-        name = "IP Addresses"; //NON-NLS
-        lockedLists.add(name);
-        addList(name, ips, false, false, true);
-
-        name = "Email Addresses"; //NON-NLS
-        lockedLists.add(name);
-        addList(name, emails, true, false, true);
-
-        name = "URLs"; //NON-NLS
-        lockedLists.add(name);
-        addList(name, urls, false, false, true);
+        urls.add(new Keyword(URL_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL));
+        lockedLists.add("URLs");
+        addList("URLs", urls, false, false, true);
+
+        //CCN
+        List<Keyword> ccns = new ArrayList<>();
+        ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
+        lockedLists.add("Credit Card Numbers");
+        addList("Credit Card Numbers", ccns, false, false, true);
     }
 
     /**
@@ -139,7 +141,7 @@ public void reload() {
         //we want to preserve state of locked lists
         List<String> toClear = new ArrayList<>();
         for (String list : theLists.keySet()) {
-            if (theLists.get(list).isLocked() == false) {
+            if (theLists.get(list).isEditable() == false) {
                 toClear.add(list);
             }
         }
@@ -171,7 +173,7 @@ public List<KeywordList> getListsL() {
     public List<KeywordList> getListsL(boolean locked) {
         List<KeywordList> ret = new ArrayList<>();
         for (KeywordList list : theLists.values()) {
-            if (list.isLocked().equals(locked)) {
+            if (list.isEditable().equals(locked)) {
                 ret.add(list);
             }
         }
@@ -198,7 +200,7 @@ public List<String> getListNames(boolean locked) {
         ArrayList<String> lists = new ArrayList<>();
         for (String listName : theLists.keySet()) {
             KeywordList list = theLists.get(listName);
-            if (locked == list.isLocked()) {
+            if (locked == list.isEditable()) {
                 lists.add(listName);
             }
         }
@@ -216,7 +218,7 @@ public List<String> getListNames(boolean locked) {
     public KeywordList getListWithKeyword(String keyword) {
         KeywordList found = null;
         for (KeywordList list : theLists.values()) {
-            if (list.hasKeyword(keyword)) {
+            if (list.hasSearchTerm(keyword)) {
                 found = list;
                 break;
             }
@@ -244,7 +246,7 @@ public int getNumberLists(boolean locked) {
         int numLists = 0;
         for (String listName : theLists.keySet()) {
             KeywordList list = theLists.get(listName);
-            if (locked == list.isLocked()) {
+            if (locked == list.isEditable()) {
                 ++numLists;
             }
         }
@@ -293,7 +295,7 @@ boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolea
             try {
                 changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, name);
             } catch (Exception e) {
-                logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                 MessageNotifyUtil.Notify.show(
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.addList.errMsg1.msg"),
@@ -306,7 +308,7 @@ boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolea
             try {
                 changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, name);
             } catch (Exception e) {
-                logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                 MessageNotifyUtil.Notify.show(
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.addList.errMsg2.msg"),
@@ -328,7 +330,7 @@ boolean addList(String name, List<Keyword> newList) {
     }
 
     boolean addList(KeywordList list) {
-        return addList(list.getName(), list.getKeywords(), list.getUseForIngest(), list.getIngestMessages(), list.isLocked());
+        return addList(list.getName(), list.getKeywords(), list.getUseForIngest(), list.getIngestMessages(), list.isEditable());
     }
 
     /**
@@ -355,7 +357,7 @@ boolean saveLists(List<KeywordList> lists) {
                 try {
                     changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName());
                 } catch (Exception e) {
-                    logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                    LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                     MessageNotifyUtil.Notify.show(
                             NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                             NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.saveList.errMsg1.msg"),
@@ -366,7 +368,7 @@ boolean saveLists(List<KeywordList> lists) {
                 try {
                     changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName());
                 } catch (Exception e) {
-                    logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                    LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                     MessageNotifyUtil.Notify.show(
                             NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                             NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.saveList.errMsg2.msg"),
@@ -402,7 +404,7 @@ boolean writeLists(List<KeywordList> lists) {
             try {
                 changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName());
             } catch (Exception e) {
-                logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                 MessageNotifyUtil.Notify.show(
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.writeLists.errMsg1.msg"),
@@ -415,7 +417,7 @@ boolean writeLists(List<KeywordList> lists) {
             try {
                 changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName());
             } catch (Exception e) {
-                logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+                LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
                 MessageNotifyUtil.Notify.show(
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                         NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.writeLists.errMsg2.msg"),
@@ -435,14 +437,14 @@ boolean writeLists(List<KeywordList> lists) {
      */
     boolean deleteList(String name) {
         KeywordList delList = getList(name);
-        if (delList != null && !delList.isLocked()) {
+        if (delList != null && !delList.isEditable()) {
             theLists.remove(name);
         }
 
         try {
             changeSupport.firePropertyChange(ListsEvt.LIST_DELETED.toString(), null, name);
         } catch (Exception e) {
-            logger.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
+            LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS
             MessageNotifyUtil.Notify.show(
                     NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"),
                     NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.deleteList.errMsg1.msg"),
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchModuleFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchModuleFactory.java
index 0750fb86c833a5669ca4faa4ce47c0e2177b5b69..17c760323ad7a264fd6414e2e857074db0c6a0ed 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchModuleFactory.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchModuleFactory.java
@@ -39,7 +39,7 @@
 @ServiceProvider(service = IngestModuleFactory.class)
 public class KeywordSearchModuleFactory extends IngestModuleFactoryAdapter {
 
-    private static final HashSet<String> defaultDisabledKeywordListNames = new HashSet<>(Arrays.asList("Phone Numbers", "IP Addresses", "URLs")); //NON-NLS
+    private static final HashSet<String> defaultDisabledKeywordListNames = new HashSet<>(Arrays.asList("Phone Numbers", "IP Addresses", "URLs", "Credit Card Numbers")); //NON-NLS
     private KeywordSearchJobSettingsPanel jobSettingsPanel = null;
 
     @Override
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java
index 2073a25df3d1d178cedf48a5ee038a692eb50dad..f373c2d162e0da24cafc555585cce1597bb832dd 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,11 +18,8 @@
  */
 package org.sleuthkit.autopsy.keywordsearch;
 
-import org.sleuthkit.datamodel.AbstractFile;
-
 /**
- * Interface for a search query. Implemented by various engines or methods of
- * using the same engine. One of these is created for each query.
+ * Interface for kewyord search queries. 
  */
 interface KeywordSearchQuery {
 
@@ -33,7 +30,7 @@ interface KeywordSearchQuery {
      *
      * @return true if the query passed validation
      */
-    public boolean validate();
+     boolean validate();
 
     /**
      * execute query and return results without publishing them return results
@@ -43,7 +40,7 @@ interface KeywordSearchQuery {
      *                             could be a notification to stop processing
      * @return
      */
-    public QueryResults performQuery() throws NoOpenCoreException;
+     QueryResults performQuery() throws NoOpenCoreException;
 
     /**
      * Set an optional filter to narrow down the search Adding multiple filters
@@ -51,14 +48,14 @@ interface KeywordSearchQuery {
      *
      * @param filter filter to set on the query
      */
-    public void addFilter(KeywordQueryFilter filter);
+     void addFilter(KeywordQueryFilter filter);
 
     /**
      * Set an optional SOLR field to narrow down the search
      *
      * @param field field to set on the query
      */
-    public void setField(String field);
+     void setField(String field);
 
     /**
      * Modify the query string to be searched as a substring instead of a whole
@@ -66,39 +63,39 @@ interface KeywordSearchQuery {
      *
      * @param isSubstring
      */
-    public void setSubstringQuery();
+     void setSubstringQuery();
 
     /**
      * escape the query string and use the escaped string in the query
      */
-    public void escape();
+     void escape();
 
     /**
      *
      * @return true if query was escaped
      */
-    public boolean isEscaped();
+     boolean isEscaped();
 
     /**
      *
      * @return true if query is a literal query (non regex)
      */
-    public boolean isLiteral();
+     boolean isLiteral();
 
     /**
      * return original keyword/query string
      *
      * @return the query String supplied originally
      */
-    public String getQueryString();
+     String getQueryString();
 
     /**
      * return escaped keyword/query string if escaping was done
      *
      * @return the escaped query string, or original string if no escaping done
      */
-    public String getEscapedQueryString();
+     String getEscapedQueryString();
 
-    public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, KeywordHit hit, String snippet, String listName);
+     KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, KeywordHit hit, String snippet, String listName);
 
 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java
index a781b4a7bc2ddcbc248b9ff9872ec8bc15ec9efe..47e0f7145fd9882a926edc35de8f165efd9b1b7a 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java
@@ -24,12 +24,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
 import org.openide.nodes.AbstractNode;
 import org.openide.nodes.Children;
 import org.openide.nodes.Node;
+import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
+import org.sleuthkit.autopsy.coreutils.Logger;
 
 /**
  * Responsible for running a keyword search query and displaying the results.
@@ -55,20 +55,20 @@ private void init() {
         for (KeywordList keywordList : keywordLists) {
             for (Keyword keyword : keywordList.getKeywords()) {
                 KeywordSearchQuery query;
-                if (keyword.isLiteral()) {
+                if (keyword.searchTermIsLiteral()) {
                     // literal, exact match
-                    if (keyword.isWholeword()) {
+                    if (keyword.searchTermIsWholeWord()) {
                         query = new LuceneQuery(keywordList, keyword);
                         query.escape();
                     } // literal, substring match
                     else {
-                        query = new TermComponentQuery(keywordList, keyword);
+                        query = new TermsComponentQuery(keywordList, keyword);
                         query.escape();
                         query.setSubstringQuery();
                     }
                 } // regexp
                 else {
-                    query = new TermComponentQuery(keywordList, keyword);
+                    query = new TermsComponentQuery(keywordList, keyword);
                 }
                 queryDelegates.add(query);
             }
@@ -98,8 +98,8 @@ public void execute() {
 
         Node rootNode;
         if (queryRequests.size() > 0) {
-            Children childNodes
-                    = Children.create(new KeywordSearchResultFactory(queryRequests, searchResultWin), true);
+            Children childNodes =
+                    Children.create(new KeywordSearchResultFactory(queryRequests, searchResultWin), true);
 
             rootNode = new AbstractNode(childNodes);
         } else {
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java
index 835fe539ee4974cb8af93266cadd0b1fa3c178f9..97beb9e9c629d2af4cf06b0a50e801894e32c733 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java
@@ -255,8 +255,8 @@ private String getHighlightQuery(KeywordSearchQuery query, boolean literal_query
             //the query is executed later on demand
             if (queryResults.getKeywords().size() == 1) {
                 //simple case, no need to process subqueries and do special escaping
-                Keyword term = queryResults.getKeywords().iterator().next();
-                return constructEscapedSolrQuery(term.getQuery(), literal_query);
+                Keyword keyword = queryResults.getKeywords().iterator().next();
+                return constructEscapedSolrQuery(keyword.getSearchTerm(), literal_query);
             } else {
                 //find terms for this content hit
                 List<Keyword> hitTerms = new ArrayList<>();
@@ -274,7 +274,7 @@ private String getHighlightQuery(KeywordSearchQuery query, boolean literal_query
                 int curTerm = 0;
                 for (Keyword term : hitTerms) {
                     //escape subqueries, MAKE SURE they are not escaped again later
-                    highlightQuery.append(constructEscapedSolrQuery(term.getQuery(), literal_query));
+                    highlightQuery.append(constructEscapedSolrQuery(term.getSearchTerm(), literal_query));
                     if (lastTerm != curTerm) {
                         highlightQuery.append(" "); //acts as OR ||
                     }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java
index cda4f8692be862b72bbf9602e1803f8fee7c2702..5702a952f4491a00ccc9fe58bd81a43094e666fc 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java
@@ -27,7 +27,6 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest.METHOD;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -36,6 +35,7 @@
 import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.coreutils.EscapeUtil;
+import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
 import org.sleuthkit.autopsy.coreutils.Version;
 import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -55,7 +55,7 @@ class LuceneQuery implements KeywordSearchQuery {
     private final String keywordString; //original unescaped query
     private String keywordStringEscaped;
     private boolean isEscaped;
-    private Keyword keywordQuery = null;
+    private Keyword keyword = null;
     private KeywordList keywordList = null;
     private final List<KeywordQueryFilter> filters = new ArrayList<>();
     private String field = null;
@@ -72,15 +72,15 @@ class LuceneQuery implements KeywordSearchQuery {
     /**
      * Constructor with query to process.
      *
-     * @param keywordQuery
+     * @param keyword
      */
-    public LuceneQuery(KeywordList keywordList, Keyword keywordQuery) {
+    public LuceneQuery(KeywordList keywordList, Keyword keyword) {
         this.keywordList = keywordList;
-        this.keywordQuery = keywordQuery;
+        this.keyword = keyword;
 
         // @@@ BC: Long-term, we should try to get rid of this string and use only the
         // keyword object.  Refactoring did not make its way through this yet.
-        this.keywordString = keywordQuery.getQuery();
+        this.keywordString = keyword.getSearchTerm();
         this.keywordStringEscaped = this.keywordString;
     }
 
@@ -168,8 +168,8 @@ public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, Key
         //bogus - workaround the dir tree table issue
         //attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID(), MODULE_NAME, "", ""));
         //selector
-        if (keywordQuery != null) {
-            BlackboardAttribute.ATTRIBUTE_TYPE selType = keywordQuery.getType();
+        if (keyword != null) {
+            BlackboardAttribute.ATTRIBUTE_TYPE selType = keyword.getArtifactAttributeType();
             if (selType != null) {
                 attributes.add(new BlackboardAttribute(selType, MODULE_NAME, termHit));
             }
@@ -491,14 +491,14 @@ public int compare(SolrDocument left, SolrDocument right) {
 
             // get object id of left doc
             String leftID = left.getFieldValue(idName).toString();
-            int index = leftID.indexOf(Server.ID_CHUNK_SEP);
+            int index = leftID.indexOf(Server.CHUNK_ID_SEPARATOR);
             if (index != -1) {
                 leftID = leftID.substring(0, index);
             }
 
             // get object id of right doc
             String rightID = right.getFieldValue(idName).toString();
-            index = rightID.indexOf(Server.ID_CHUNK_SEP);
+            index = rightID.indexOf(Server.CHUNK_ID_SEPARATOR);
             if (index != -1) {
                 rightID = rightID.substring(0, index);
             }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java
index b10ec9c3d0adb8a2c2bf102ebb92fa4b6f4a2815..316e4f37175f5fdd2bb4e944208364eb4782ea62 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.logging.Level;
+import java.util.stream.Collectors;
 import javax.swing.SwingWorker;
 import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
@@ -47,7 +48,7 @@
 class QueryResults {
 
     private static final Logger logger = Logger.getLogger(QueryResults.class.getName());
-
+    private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
     /**
      * The query that generated the results.
      */
@@ -114,7 +115,7 @@ Collection<BlackboardArtifact> writeAllHitsToBlackBoard(ProgressHandle progress,
 
         for (final Keyword keyword : getKeywords()) {
             if (worker.isCancelled()) {
-                logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getQuery()); //NON-NLS
+                logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS
                 break;
             }
 
@@ -123,7 +124,7 @@ Collection<BlackboardArtifact> writeAllHitsToBlackBoard(ProgressHandle progress,
                 progress.progress(keyword.toString(), unitProgress);
             }
             if (subProgress != null) {
-                String hitDisplayStr = keyword.getQuery();
+                String hitDisplayStr = keyword.getSearchTerm();
                 if (hitDisplayStr.length() > 50) {
                     hitDisplayStr = hitDisplayStr.substring(0, 49) + "...";
                 }
@@ -131,7 +132,7 @@ Collection<BlackboardArtifact> writeAllHitsToBlackBoard(ProgressHandle progress,
             }
 
             for (KeywordHit hit : getOneHitPerObject(keyword)) {
-                String termString = keyword.getQuery();
+                String termString = keyword.getSearchTerm();
                 final String snippetQuery = KeywordSearchUtil.escapeLuceneQuery(termString);
                 String snippet;
                 try {
@@ -161,7 +162,13 @@ Collection<BlackboardArtifact> writeAllHitsToBlackBoard(ProgressHandle progress,
 
         // Update artifact browser
         if (!newArtifacts.isEmpty()) {
-            IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(KeywordSearchModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT, newArtifacts));
+            newArtifacts.stream()
+                    //group artifacts by type
+                    .collect(Collectors.groupingBy(BlackboardArtifact::getArtifactTypeID))
+                    //for each type send an event
+                    .forEach((typeID, artifacts) ->
+                            IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.fromID(typeID), artifacts)));
+
         }
 
         return newArtifacts;
@@ -177,16 +184,14 @@ Collection<BlackboardArtifact> writeAllHitsToBlackBoard(ProgressHandle progress,
      */
     private Collection<KeywordHit> getOneHitPerObject(Keyword keyword) {
 
-        HashMap<Long, KeywordHit> hits = new HashMap<Long, KeywordHit>();
+        HashMap<Long, KeywordHit> hits = new HashMap<>();
 
         // create a list of KeywordHits. KeywordHits with lowest chunkID is added the the list.
         for (KeywordHit hit : getResults(keyword)) {
             if (!hits.containsKey(hit.getSolrObjectId())) {
                 hits.put(hit.getSolrObjectId(), hit);
-            } else {
-                if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
-                    hits.put(hit.getSolrObjectId(), hit);
-                }
+            } else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
+                hits.put(hit.getSolrObjectId(), hit);
             }
         }
         return hits.values();
@@ -245,11 +250,12 @@ private void writeSingleFileInboxMessage(KeywordCachedArtifact written, Content
 
         //list
         attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID());
-        detailsSb.append("<tr>"); //NON-NLS
-        detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.listThLbl"));
-        detailsSb.append("<td>").append(attr.getValueString()).append("</td>"); //NON-NLS
-        detailsSb.append("</tr>"); //NON-NLS
-
+        if (attr != null) {
+            detailsSb.append("<tr>"); //NON-NLS
+            detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.listThLbl"));
+            detailsSb.append("<td>").append(attr.getValueString()).append("</td>"); //NON-NLS
+            detailsSb.append("</tr>"); //NON-NLS
+        }
         //regex
         if (!keywordSearchQuery.isLiteral()) {
             attr = written.getAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID());
@@ -262,7 +268,7 @@ private void writeSingleFileInboxMessage(KeywordCachedArtifact written, Content
         }
         detailsSb.append("</table>"); //NON-NLS
 
-        IngestServices.getInstance().postMessage(IngestMessage.createDataMessage(KeywordSearchModuleFactory.getModuleName(), subjectSb.toString(), detailsSb.toString(), uniqueKey, written.getArtifact()));
+        IngestServices.getInstance().postMessage(IngestMessage.createDataMessage(MODULE_NAME, subjectSb.toString(), detailsSb.toString(), uniqueKey, written.getArtifact()));
     }
 
 }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
index d91eb4cbbd8dc67bd14bce099c89ba3e820c3c64..00fd1db2a7c189131f23f9a110e357e835e97edf 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
@@ -24,14 +24,14 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.logging.Level;
 import javax.swing.SwingUtilities;
 import javax.swing.SwingWorker;
-import java.util.Timer;
-import java.util.TimerTask;
 import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
@@ -403,7 +403,7 @@ public boolean cancel() {
             ProgressContributor[] subProgresses = new ProgressContributor[keywords.size()];
             int i = 0;
             for (Keyword keywordQuery : keywords) {
-                subProgresses[i] = AggregateProgressFactory.createProgressContributor(keywordQuery.getQuery());
+                subProgresses[i] = AggregateProgressFactory.createProgressContributor(keywordQuery.getSearchTerm());
                 progressGroup.addContributor(subProgresses[i]);
                 i++;
             }
@@ -419,11 +419,11 @@ public boolean cancel() {
 
                 for (Keyword keywordQuery : keywords) {
                     if (this.isCancelled()) {
-                        logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keywordQuery.getQuery()); //NON-NLS
+                        logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keywordQuery.getSearchTerm()); //NON-NLS
                         return null;
                     }
 
-                    final String queryStr = keywordQuery.getQuery();
+                    final String queryStr = keywordQuery.getSearchTerm();
                     final KeywordList list = keywordToList.get(queryStr);
 
                     //new subProgress will be active after the initial query
@@ -434,9 +434,9 @@ public boolean cancel() {
 
                     KeywordSearchQuery keywordSearchQuery = null;
 
-                    boolean isRegex = !keywordQuery.isLiteral();
+                    boolean isRegex = !keywordQuery.searchTermIsLiteral();
                     if (isRegex) {
-                        keywordSearchQuery = new TermComponentQuery(list, keywordQuery);
+                        keywordSearchQuery = new TermsComponentQuery(list, keywordQuery);
                     } else {
                         keywordSearchQuery = new LuceneQuery(list, keywordQuery);
                         keywordSearchQuery.escape();
@@ -454,16 +454,16 @@ public boolean cancel() {
                     try {
                         queryResults = keywordSearchQuery.performQuery();
                     } catch (NoOpenCoreException ex) {
-                        logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getQuery(), ex); //NON-NLS
+                        logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS
                         //no reason to continue with next query if recovery failed
                         //or wait for recovery to kick in and run again later
                         //likely case has closed and threads are being interrupted
                         return null;
                     } catch (CancellationException e) {
-                        logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getQuery()); //NON-NLS
+                        logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getSearchTerm()); //NON-NLS
                         return null;
                     } catch (Exception e) {
-                        logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getQuery(), e); //NON-NLS
+                        logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getSearchTerm(), e); //NON-NLS
                         continue;
                     }
 
@@ -481,7 +481,7 @@ public boolean cancel() {
                         int totalUnits = newResults.getKeywords().size();
                         subProgresses[keywordsSearched].start(totalUnits);
                         int unitProgress = 0;
-                        String queryDisplayStr = keywordQuery.getQuery();
+                        String queryDisplayStr = keywordQuery.getSearchTerm();
                         if (queryDisplayStr.length() > 50) {
                             queryDisplayStr = queryDisplayStr.substring(0, 49) + "...";
                         }
@@ -547,7 +547,7 @@ private void updateKeywords() {
                 keywordLists.add(list);
                 for (Keyword k : list.getKeywords()) {
                     keywords.add(k);
-                    keywordToList.put(k.getQuery(), list);
+                    keywordToList.put(k.getSearchTerm(), list);
                 }
             }
         }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
index 1f440971022ee34bf9aedc98c9e9eb8da2da2bac..03f01e3cf29528c8497fe93703ef408f79f73e72 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
@@ -40,8 +40,6 @@
 import java.util.List;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Level;
-import org.openide.util.NbBundle;
-import org.sleuthkit.autopsy.coreutils.Logger;
 import javax.swing.AbstractAction;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -51,22 +49,24 @@
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder;
-import org.apache.solr.common.util.NamedList;
-import org.openide.modules.InstalledFileLocator;
-import org.openide.modules.Places;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.coreutils.ModuleSettings;
-import org.sleuthkit.autopsy.coreutils.PlatformUtil;
-import org.sleuthkit.datamodel.Content;
-import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.client.solrj.impl.XMLResponseParser;
 import org.apache.solr.client.solrj.response.CoreAdminResponse;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.util.NamedList;
+import org.openide.modules.InstalledFileLocator;
+import org.openide.modules.Places;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.casemodule.Case.CaseType;
-import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
 import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
+import org.sleuthkit.datamodel.Content;
 
 /**
  * Handles management of a either a local or centralized Solr server and its
@@ -158,7 +158,9 @@ public String toString() {
     private static final Logger logger = Logger.getLogger(Server.class.getName());
     private static final String DEFAULT_CORE_NAME = "coreCase"; //NON-NLS
     public static final String CORE_EVT = "CORE_EVT"; //NON-NLS
+    @Deprecated
     public static final char ID_CHUNK_SEP = '_';
+    public static final String CHUNK_ID_SEPARATOR = "_";
     private String javaPath = "java"; //NON-NLS
     public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8"); ///< default Charset to index text as
     private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
@@ -1060,7 +1062,7 @@ public static Ingester getIngester() {
      * @return formatted string id
      */
     public static String getChunkIdString(long parentID, int childID) {
-        return Long.toString(parentID) + Server.ID_CHUNK_SEP + Integer.toString(childID);
+        return Long.toString(parentID) + Server.CHUNK_ID_SEPARATOR + Integer.toString(childID);
     }
 
     /**
@@ -1279,7 +1281,7 @@ private String getSolrContent(long contentID, int chunkID) {
             q.setQuery("*:*");
             String filterQuery = Schema.ID.toString() + ":" + KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
             if (chunkID != 0) {
-                filterQuery = filterQuery + Server.ID_CHUNK_SEP + chunkID;
+                filterQuery = filterQuery + Server.CHUNK_ID_SEPARATOR + chunkID;
             }
             q.addFilterQuery(filterQuery);
             q.setFields(Schema.TEXT.toString());
@@ -1348,7 +1350,7 @@ private int queryNumIndexedFiles() throws SolrServerException, IOException {
          * @throws SolrServerException
          */
         private int queryNumIndexedChunks() throws SolrServerException, IOException {
-            SolrQuery q = new SolrQuery(Server.Schema.ID + ":*" + Server.ID_CHUNK_SEP + "*");
+            SolrQuery q = new SolrQuery(Server.Schema.ID + ":*" + Server.CHUNK_ID_SEPARATOR + "*");
             q.setRows(0);
             int numChunks = (int) query(q).getResults().getNumFound();
             return numChunks;
@@ -1402,7 +1404,7 @@ private boolean queryIsIndexed(long contentID) throws SolrServerException, IOExc
         private int queryNumFileChunks(long contentID) throws SolrServerException, IOException {
             String id = KeywordSearchUtil.escapeLuceneQuery(Long.toString(contentID));
             final SolrQuery q
-                    = new SolrQuery(Server.Schema.ID + ":" + id + Server.ID_CHUNK_SEP + "*");
+                    = new SolrQuery(Server.Schema.ID + ":" + id + Server.CHUNK_ID_SEPARATOR + "*");
             q.setRows(0);
             return (int) query(q).getResults().getNumFound();
         }
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java
deleted file mode 100644
index d49aad59089585f30b4f9b11fe67420a2ff2c0a6..0000000000000000000000000000000000000000
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2011-2014 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.keywordsearch;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.client.solrj.response.TermsResponse;
-import org.apache.solr.client.solrj.response.TermsResponse.Term;
-import org.sleuthkit.autopsy.coreutils.Version;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.BlackboardArtifact;
-import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
-import org.sleuthkit.datamodel.BlackboardAttribute;
-import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
-import org.sleuthkit.datamodel.TskException;
-
-/**
- * Performs a regular expression query to the SOLR/Lucene instance.
- */
-class TermComponentQuery implements KeywordSearchQuery {
-
-    private static final int TERMS_UNLIMITED = -1;
-    //corresponds to field in Solr schema, analyzed with white-space tokenizer only
-    private static final String TERMS_SEARCH_FIELD = Server.Schema.CONTENT_WS.toString();
-    private static final String TERMS_HANDLER = "/terms"; //NON-NLS
-    private static final int TERMS_TIMEOUT = 90 * 1000; //in ms
-    private static final Logger logger = Logger.getLogger(TermComponentQuery.class.getName());
-    private String queryEscaped;
-    private final KeywordList keywordList;
-    private final Keyword keyword;
-    private boolean isEscaped;
-    private List<Term> terms;
-    private final List<KeywordQueryFilter> filters = new ArrayList<>();
-    private String field;
-    private static final int MAX_TERMS_RESULTS = 20000;
-
-    private static final boolean DEBUG = (Version.getBuildType() == Version.Type.DEVELOPMENT);
-
-    public TermComponentQuery(KeywordList keywordList, Keyword keyword) {
-        this.field = null;
-        this.keyword = keyword;
-        this.keywordList = keywordList;
-        this.queryEscaped = keyword.getQuery();
-        isEscaped = false;
-        terms = null;
-    }
-
-    @Override
-    public void addFilter(KeywordQueryFilter filter) {
-        this.filters.add(filter);
-    }
-
-    @Override
-    public void setField(String field) {
-        this.field = field;
-    }
-
-    @Override
-    public void setSubstringQuery() {
-        queryEscaped = ".*" + queryEscaped + ".*";
-    }
-
-    @Override
-    public void escape() {
-        queryEscaped = Pattern.quote(keyword.getQuery());
-        isEscaped = true;
-    }
-
-    @Override
-    public boolean validate() {
-        if (queryEscaped.equals("")) {
-            return false;
-        }
-
-        boolean valid = true;
-        try {
-            Pattern.compile(queryEscaped);
-        } catch (PatternSyntaxException ex1) {
-            valid = false;
-        } catch (IllegalArgumentException ex2) {
-            valid = false;
-        }
-        return valid;
-    }
-
-    @Override
-    public boolean isEscaped() {
-        return isEscaped;
-    }
-
-    @Override
-    public boolean isLiteral() {
-        return false;
-    }
-
-    /*
-     * helper method to create a Solr terms component query
-     */
-    protected SolrQuery createQuery() {
-        final SolrQuery q = new SolrQuery();
-        q.setRequestHandler(TERMS_HANDLER);
-        q.setTerms(true);
-        q.setTermsLimit(TERMS_UNLIMITED);
-        q.setTermsRegexFlag("case_insensitive"); //NON-NLS
-        //q.setTermsLimit(200);
-        //q.setTermsRegexFlag(regexFlag);
-        //q.setTermsRaw(true);
-        q.setTermsRegex(queryEscaped);
-        q.addTermsField(TERMS_SEARCH_FIELD);
-        q.setTimeAllowed(TERMS_TIMEOUT);
-
-        return q;
-
-    }
-
-    /*
-     * execute query and return terms, helper method
-     */
-    protected List<Term> executeQuery(SolrQuery q) throws NoOpenCoreException {
-        try {
-            Server solrServer = KeywordSearch.getServer();
-            TermsResponse tr = solrServer.queryTerms(q);
-            List<Term> termsCol = tr.getTerms(TERMS_SEARCH_FIELD);
-            return termsCol;
-        } catch (KeywordSearchModuleException ex) {
-            logger.log(Level.WARNING, "Error executing the regex terms query: " + keyword.getQuery(), ex); //NON-NLS
-            return null;  //no need to create result view, just display error dialog
-        }
-    }
-
-    @Override
-    public String getEscapedQueryString() {
-        return this.queryEscaped;
-    }
-
-    @Override
-    public String getQueryString() {
-        return keyword.getQuery();
-    }
-
-    @Override
-    public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, KeywordHit hit, String snippet, String listName) {
-        final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
-
-        //there is match actually in this file, create artifact only then
-        BlackboardArtifact bba;
-        KeywordCachedArtifact writeResult;
-        Collection<BlackboardAttribute> attributes = new ArrayList<>();
-        try {
-            bba = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
-            writeResult = new KeywordCachedArtifact(bba);
-        } catch (Exception e) {
-            logger.log(Level.WARNING, "Error adding bb artifact for keyword hit", e); //NON-NLS
-            return null;
-        }
-
-        //regex match
-        attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, termHit));
-
-        if ((listName != null) && (listName.equals("") == false)) {
-            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
-        }
-
-        //preview
-        if (snippet != null) {
-            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
-        }
-        //regex keyword
-        attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, keyword.getQuery()));
-
-        if (hit.isArtifactHit()) {
-            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
-        }
-
-        try {
-            bba.addAttributes(attributes);
-            writeResult.add(attributes);
-            return writeResult;
-        } catch (TskException e) {
-            logger.log(Level.WARNING, "Error adding bb attributes for terms search artifact", e); //NON-NLS
-        }
-
-        return null;
-    }
-
-    @Override
-    public QueryResults performQuery() throws NoOpenCoreException {
-        /*
-         * Execute the regex query to get a list of terms that match the regex.
-         * Note that the field that is being searched is tokenized based on
-         * whitespace.
-         */ 
-        final SolrQuery q = createQuery();
-        q.setShowDebugInfo(DEBUG);
-        q.setTermsLimit(MAX_TERMS_RESULTS);
-        logger.log(Level.INFO, "Query: {0}", q.toString()); //NON-NLS
-        terms = executeQuery(q);
-
-        /*
-         * For each term that matched the regex, query for the term to get the
-         * full set of document hits.
-         */
-        QueryResults results = new QueryResults(this, keywordList);
-        int resultSize = 0;
-        for (Term term : terms) {
-            final String termStr = KeywordSearchUtil.escapeLuceneQuery(term.getTerm());
-
-            LuceneQuery filesQuery = new LuceneQuery(keywordList, new Keyword(termStr, true));
-
-            //filesQuery.setField(TERMS_SEARCH_FIELD);
-            for (KeywordQueryFilter filter : filters) {
-                //set filter
-                //note: we can't set filter query on terms query
-                //but setting filter query on terms results query will yield the same result
-                filesQuery.addFilter(filter);
-            }
-            try {
-                QueryResults subResults = filesQuery.performQuery();
-                Set<KeywordHit> filesResults = new HashSet<>();
-                for (Keyword key : subResults.getKeywords()) {
-                    List<KeywordHit> keyRes = subResults.getResults(key);
-                    resultSize += keyRes.size();
-                    filesResults.addAll(keyRes);
-                }
-                results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(filesResults));
-            } catch (NoOpenCoreException e) {
-                logger.log(Level.WARNING, "Error executing Solr query,", e); //NON-NLS
-                throw e;
-            } catch (RuntimeException e) {
-                logger.log(Level.WARNING, "Error executing Solr query,", e); //NON-NLS
-            }
-
-        }
-
-        //TODO limit how many results we store, not to hit memory limits
-        logger.log(Level.INFO, "Regex # results: {0}", resultSize); //NON-NLS
-
-        return results;
-    }
-
-    @Override
-    public KeywordList getKeywordList() {
-        return keywordList;
-    }
-}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..49343798b7d2e13720b5cb61b4fb6108f946a406
--- /dev/null
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java
@@ -0,0 +1,517 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2016 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.keywordsearch;
+
+import com.google.common.base.CharMatcher;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.response.TermsResponse.Term;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.Version;
+import org.sleuthkit.autopsy.datamodel.CreditCards;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.TskData;
+
+/**
+ * Implements a regex query that will be performed as a two step operation. In
+ * the first step, the Solr terms component is used to find any terms in the
+ * index that match the regex. In the second step, term queries are executed for
+ * each matched term to produce the set of keyword hits for the regex.
+ */
+final class TermsComponentQuery implements KeywordSearchQuery {
+
+    private static final Logger LOGGER = Logger.getLogger(TermsComponentQuery.class.getName());
+    private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
+    private static final String SEARCH_HANDLER = "/terms"; //NON-NLS
+    private static final String SEARCH_FIELD = Server.Schema.CONTENT_WS.toString();
+    private static final int TERMS_SEARCH_TIMEOUT = 90 * 1000; // Milliseconds
+    private static final String CASE_INSENSITIVE = "case_insensitive"; //NON-NLS
+    private static final boolean DEBUG_FLAG = Version.Type.DEVELOPMENT.equals(Version.getBuildType());
+    private static final int MAX_TERMS_QUERY_RESULTS = 20000;
+    private final KeywordList keywordList;
+    private final Keyword keyword;
+    private String searchTerm;
+    private boolean searchTermIsEscaped;
+    private final List<KeywordQueryFilter> filters = new ArrayList<>(); // THIS APPEARS TO BE UNUSED
+
+    /*
+     * The following fields are part of the initial implementation of credit
+     * card account search and should be factored into another class when time
+     * permits.
+     */
+    private static final Pattern CREDIT_CARD_NUM_PATTERN = Pattern.compile("(?<ccn>[3456]([ -]?\\d){11,18})");   //12-19 digits, with possible single spaces or dashes in between. First digit is 3,4,5, or 6 //NON-NLS
+    private static final LuhnCheckDigit CREDIT_CARD_NUM_LUHN_CHECK = new LuhnCheckDigit();
+    private static final Pattern CREDIT_CARD_TRACK1_PATTERN = Pattern.compile(
+            /*
+             * Track 1 is alphanumeric.
+             *
+             * This regex matches 12-19 digit ccns embeded in a track 1 formated
+             * string. This regex matches (and extracts groups) even if the
+             * entire track is not present as long as the part that is conforms
+             * to the track format.
+             */
+            "(?:" //begin nested optinal group //NON-NLS
+            + "%?" //optional start sentinal: % //NON-NLS
+            + "B)?" //format code  //NON-NLS
+            + "(?<accountNumber>[3456]([ -]?\\d){11,18})" //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS
+            + "\\^" //separator //NON-NLS
+            + "(?<name>[^^]{2,26})" //2-26 charachter name, not containing ^ //NON-NLS
+            + "(?:\\^" //separator //NON-NLS
+            + "(?:(?:\\^|(?<expiration>\\d{4}))" //separator or 4 digit expiration YYMM //NON-NLS
+            + "(?:(?:\\^|(?<serviceCode>\\d{3}))"//separator or 3 digit service code //NON-NLS
+            + "(?:(?<discretionary>[^?]*)" // discretionary data not containing separator //NON-NLS
+            + "(?:\\?" // end sentinal: ? //NON-NLS
+            + "(?<LRC>.)" //longitudinal redundancy check //NON-NLS
+            + "?)?)?)?)?)?");//close nested optional groups //NON-NLS
+    private static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile(
+            /*
+             * Track 2 is numeric plus six punctuation symbolls :;<=>?
+             *
+             * This regex matches 12-19 digit ccns embeded in a track 2 formated
+             * string. This regex matches (and extracts groups) even if the
+             * entire track is not present as long as the part that is conforms
+             * to the track format.
+             *
+             */
+            "[:;<=>?]?" //(optional)start sentinel //NON-NLS
+            + "(?<accountNumber>[3456]([ -]?\\d){11,18})" //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS
+            + "(?:[:;<=>?]" //separator //NON-NLS
+            + "(?:(?<expiration>\\d{4})" //4 digit expiration date YYMM //NON-NLS
+            + "(?:(?<serviceCode>\\d{3})" //3 digit service code //NON-NLS
+            + "(?:(?<discretionary>[^:;<=>?]*)" //discretionary data, not containing punctuation marks //NON-NLS
+            + "(?:[:;<=>?]" //end sentinel //NON-NLS
+            + "(?<LRC>.)" //longitudinal redundancy check //NON-NLS
+            + "?)?)?)?)?)?"); //close nested optional groups //NON-NLS
+    private static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID);
+
+    /**
+     * Constructs an object that implements a regex query that will be performed
+     * as a two step operation. In the first step, the Solr terms component is
+     * used to find any terms in the index that match the regex. In the second
+     * step, term queries are executed for each matched term to produce the set
+     * of keyword hits for the regex.
+     *
+     * @param keywordList A keyword list that contains the keyword that provides
+     *                    the regex search term for the query.
+     * @param keyword     The keyword that provides the regex search term for
+     *                    the query.
+     */
+    // TODO: Why is both the list and the keyword added to the state of this
+    // object?
+    // TODO: Why is the search term not escaped and given substring wildcards,
+    // if needed, here in the constructor?
+    TermsComponentQuery(KeywordList keywordList, Keyword keyword) {
+        this.keywordList = keywordList;
+        this.keyword = keyword;
+        this.searchTerm = keyword.getSearchTerm();
+    }
+
+    /**
+     * Gets the keyword list that contains the keyword that provides the regex
+     * search term for the query.
+     *
+     * @return The keyword list.
+     */
+    @Override
+    public KeywordList getKeywordList() {
+        return keywordList;
+    }
+
+    /**
+     * Gets the original search term for the query, without any escaping or, if
+     * it is a literal term, the addition of wildcards for a substring search.
+     *
+     * @return The original search term.
+     */
+    @Override
+    public String getQueryString() {
+        return keyword.getSearchTerm();
+    }
+
+    /**
+     * Indicates whether or not the search term for the query is a literal term
+     * that needs have wildcards added to it to make the query a substring
+     * search.
+     *
+     * @return True or false.
+     */
+    @Override
+    public boolean isLiteral() {
+        return false;
+    }
+
+    /**
+     * Adds wild cards to the search term for the query, which makes the query a
+     * substring search, if it is a literal search term.
+     */
+    @Override
+    public void setSubstringQuery() {
+        searchTerm = ".*" + searchTerm + ".*";
+    }
+
+    /**
+     * Escapes the search term for the query.
+     */
+    @Override
+    public void escape() {
+        searchTerm = Pattern.quote(keyword.getSearchTerm());
+        searchTermIsEscaped = true;
+    }
+
+    /**
+     * Indicates whether or not the search term has been escaped yet.
+     *
+     * @return True or false.
+     */
+    @Override
+    public boolean isEscaped() {
+        return searchTermIsEscaped;
+    }
+
+    /**
+     * Gets the escaped search term for the query, assuming it has been escaped
+     * by a call to TermsComponentQuery.escape.
+     *
+     * @return The search term, possibly escaped.
+     */
+    @Override
+    public String getEscapedQueryString() {
+        return this.searchTerm;
+    }
+
+    /**
+     * Indicates whether or not the search term is a valid regex.
+     *
+     * @return True or false.
+     */
+    @Override
+    public boolean validate() {
+        if (searchTerm.isEmpty()) {
+            return false;
+        }
+        try {
+            Pattern.compile(searchTerm);
+            return true;
+        } catch (IllegalArgumentException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Does nothing, not applicable to a regex query, which always searches a
+     * field created specifically for regex sesarches.
+     *
+     * @param field The name of a Solr document field to search.
+     */
+    @Override
+    public void setField(String field) {
+    }
+
+    /**
+     * Adds a filter to the query.
+     *
+     * @param filter The filter.
+     */
+    // TODO: Document this better.
+    @Override
+    public void addFilter(KeywordQueryFilter filter) {
+        this.filters.add(filter);
+    }
+
+    /**
+     * Executes the regex query as a two step operation. In the first step, the
+     * Solr terms component is used to find any terms in the index that match
+     * the regex. In the second step, term queries are executed for each matched
+     * term to produce the set of keyword hits for the regex.
+     *
+     * @return A QueryResult object or null.
+     *
+     * @throws NoOpenCoreException
+     */
+    // TODO: Make it so this cannot cause NPEs; this method should throw 
+    // exceptions instead of logging them and returning null.
+    @Override
+    public QueryResults performQuery() throws NoOpenCoreException {
+        /*
+         * Do a query using the Solr terms component to find any terms in the
+         * index that match the regex.
+         */
+        final SolrQuery termsQuery = new SolrQuery();
+        termsQuery.setRequestHandler(SEARCH_HANDLER);
+        termsQuery.setTerms(true);
+        termsQuery.setTermsRegexFlag(CASE_INSENSITIVE);
+        termsQuery.setTermsRegex(searchTerm);
+        termsQuery.addTermsField(SEARCH_FIELD);
+        termsQuery.setTimeAllowed(TERMS_SEARCH_TIMEOUT);
+        termsQuery.setShowDebugInfo(DEBUG_FLAG);
+        termsQuery.setTermsLimit(MAX_TERMS_QUERY_RESULTS);
+        List<Term> terms = null;
+        try {
+            terms = KeywordSearch.getServer().queryTerms(termsQuery).getTerms(SEARCH_FIELD);
+        } catch (KeywordSearchModuleException ex) {
+            LOGGER.log(Level.SEVERE, "Error executing the regex terms query: " + keyword.getSearchTerm(), ex); //NON-NLS
+            //TODO: this is almost certainly wrong and guaranteed to throw a NPE at some point!!!!
+        }
+
+        /*
+         * Do a term query for each term that matched the regex.
+         */
+        QueryResults results = new QueryResults(this, keywordList);
+        for (Term term : terms) {
+            /*
+             * If searching for credit card account numbers, do a Luhn check on
+             * the term and discard it if it does not pass.
+             */
+            if (keyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
+                Matcher matcher = CREDIT_CARD_NUM_PATTERN.matcher(term.getTerm());
+                matcher.find();
+                final String ccn = CharMatcher.anyOf(" -").removeFrom(matcher.group("ccn"));
+                if (false == CREDIT_CARD_NUM_LUHN_CHECK.isValid(ccn)) {
+                    continue;
+                }
+            }
+
+            /*
+             * Do an ordinary query with the escaped term and convert the query
+             * results into a single list of keyword hits without duplicates.
+             *
+             * Note that the filters field appears to be unused. There is an old
+             * comment here, what does it mean? "Note: we can't set filter query
+             * on terms query but setting filter query on fileResults query will
+             * yield the same result." The filter is NOT being added to the term
+             * query.
+             */
+            String escapedTerm = KeywordSearchUtil.escapeLuceneQuery(term.getTerm());
+            LuceneQuery termQuery = new LuceneQuery(keywordList, new Keyword(escapedTerm, true));
+            filters.forEach(termQuery::addFilter); // This appears to be unused
+            QueryResults termQueryResult = termQuery.performQuery();
+            Set<KeywordHit> termHits = new HashSet<>();
+            for (Keyword word : termQueryResult.getKeywords()) {
+                termHits.addAll(termQueryResult.getResults(word));
+            }
+            results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(termHits));
+        }
+        return results;
+    }
+
+    /**
+     * Converts the keyword hits for a given search term into artifacts.
+     *
+     * @param searchTerm The search term.
+     * @param hit        The keyword hit.
+     * @param snippet    The document snippet that contains the hit
+     * @param listName   The name of the keyword list that contained the keyword
+     *                   for which the hit was found.
+     *
+     * 
+     *
+     * @return An object that wraps an artifact and a mapping by id of its
+     *         attributes.
+     */
+    // TODO: Are we actually making meaningful use of the KeywordCachedArtifact
+    // class?
+    @Override
+    public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String searchTerm, KeywordHit hit, String snippet, String listName) {
+        /*
+         * Create either a "plain vanilla" keyword hit artifact with keyword and
+         * regex attributes, or a credit card account artifact with attributes
+         * parsed from from the snippet for the hit and looked up based on the
+         * parsed bank identifcation number.
+         */
+        BlackboardArtifact newArtifact;
+        Collection<BlackboardAttribute> attributes = new ArrayList<>();
+        if (keyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, searchTerm));
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, keyword.getSearchTerm()));
+            try {
+                newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
+
+            } catch (TskCoreException ex) {
+                LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS
+                return null;
+            }
+        } else {
+            /*
+             * Parse the credit card account attributes from the snippet for the
+             * hit.
+             */
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, MODULE_NAME, Account.Type.CREDIT_CARD.name()));
+            Map<BlackboardAttribute.Type, BlackboardAttribute> parsedTrackAttributeMap = new HashMap<>();
+            Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet());
+            if (matcher.find()) {
+                parseTrack1Data(parsedTrackAttributeMap, matcher);
+            }
+            matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet());
+            if (matcher.find()) {
+                parseTrack2Data(parsedTrackAttributeMap, matcher);
+            }
+            final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
+            if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
+                if (hit.isArtifactHit()) {
+                    LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS
+                } else {
+                    LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getContent().getId())); //NON-NLS
+                }
+                return null;
+            }
+            attributes.addAll(parsedTrackAttributeMap.values());
+
+            /*
+             * Look up the bank name, scheme, etc. attributes for the bank
+             * indentification number (BIN).
+             */
+            final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8));
+            CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin);
+            if (binInfo != null) {
+                binInfo.getScheme().ifPresent(scheme
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme)));
+                binInfo.getCardType().ifPresent(cardType
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType)));
+                binInfo.getBrand().ifPresent(brand
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand)));
+                binInfo.getBankName().ifPresent(bankName
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName)));
+                binInfo.getBankPhoneNumber().ifPresent(phoneNumber
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber)));
+                binInfo.getBankURL().ifPresent(url
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url)));
+                binInfo.getCountry().ifPresent(country
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country)));
+                binInfo.getBankCity().ifPresent(city
+                        -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city)));
+            }
+
+            /*
+             * If the hit is from unused or unallocated space, record the Solr
+             * document id to support showing just the chunk that contained the
+             * hit.
+             */
+            if (hit.getContent() instanceof AbstractFile) {
+                AbstractFile file = (AbstractFile) hit.getContent();
+                if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS
+                        || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
+                    attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId()));
+                }
+            }
+
+            /*
+             * Create an account artifact.
+             */
+            try {
+                newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT);
+            } catch (TskCoreException ex) {
+                LOGGER.log(Level.SEVERE, "Error adding artifact for account to blackboard", ex); //NON-NLS
+                return null;
+            }
+        }
+
+        if (StringUtils.isNotBlank(listName)) {
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
+        }
+        if (snippet != null) {
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
+        }
+        if (hit.isArtifactHit()) {
+            attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
+        }
+
+        try {
+            newArtifact.addAttributes(attributes);
+            KeywordCachedArtifact writeResult = new KeywordCachedArtifact(newArtifact);
+            writeResult.add(attributes);
+            return writeResult;
+        } catch (TskCoreException e) {
+            LOGGER.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS
+            return null;
+        }
+    }
+
+    /**
+     * Parses the track 2 data from the snippet for a credit card account number
+     * hit and turns them into artifact attributes.
+     *
+     * @param attributesMap A map of artifact attribute objects, used to avoid
+     *                      creating duplicate attributes.
+     * @param matcher       A matcher for the snippet.
+     */
+    static private void parseTrack2Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributesMap, Matcher matcher) {
+        addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_NUMBER, "accountNumber", matcher);
+        addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION, "expiration", matcher);
+        addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE, "serviceCode", matcher);
+        addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY, "discretionary", matcher);
+        addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_LRC, "LRC", matcher);
+    }
+
+    /**
+     * Parses the track 1 data from the snippet for a credit card account number
+     * hit and turns them into artifact attributes. The track 1 data has the
+     * same fields as the track two data, plus the account holder's name.
+     *
+     * @param attributesMap A map of artifact attribute objects, used to avoid
+     *                      creating duplicate attributes.
+     * @param matcher       A matcher for the snippet.
+     */
+    static private void parseTrack1Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, Matcher matcher) {
+        parseTrack2Data(attributeMap, matcher);
+        addAttributeIfNotAlreadyCaptured(attributeMap, ATTRIBUTE_TYPE.TSK_NAME_PERSON, "name", matcher);
+    }
+
+    /**
+     * Creates an attribute of the the given type to the given artifact with a
+     * value parsed from the snippet for a credit account number hit.
+     *
+     * @param attributesMap A map of artifact attribute objects, used to avoid
+     *                      creating duplicate attributes.
+     * @param attrType      The type of attribute to create.
+     * @param groupName     The group name of the regular expression that was
+     *                      used to parse the attribute data.
+     * @param matcher       A matcher for the snippet.
+     */
+    static private void addAttributeIfNotAlreadyCaptured(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) {
+        BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType);
+        attributeMap.computeIfAbsent(type, (BlackboardAttribute.Type t) -> {
+            String value = matcher.group(groupName);
+            if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) {
+                value = CharMatcher.anyOf(" -").removeFrom(value);
+            }
+            if (StringUtils.isNotBlank(value)) {
+                return new BlackboardAttribute(attrType, MODULE_NAME, value);
+            }
+            return null;
+        });
+    }
+
+}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/XmlKeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/XmlKeywordSearchList.java
index 2bef2852e97279f435f6270c435fd9c6ed0786c4..87d792557fff27b3188b2d6d3ad0678eb349b66b 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/XmlKeywordSearchList.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/XmlKeywordSearchList.java
@@ -99,7 +99,7 @@ public boolean save(boolean isExport) {
             doc.appendChild(rootEl);
 
             for (String listName : theLists.keySet()) {
-                if (theLists.get(listName).isLocked() == true) {
+                if (theLists.get(listName).isEditable() == true) {
                     continue;
                 }
                 KeywordList list = theLists.get(listName);
@@ -123,13 +123,13 @@ public boolean save(boolean isExport) {
 
                 for (Keyword keyword : keywords) {
                     Element keywordEl = doc.createElement(KEYWORD_EL);
-                    String literal = keyword.isLiteral() ? "true" : "false"; //NON-NLS
+                    String literal = keyword.searchTermIsLiteral() ? "true" : "false"; //NON-NLS
                     keywordEl.setAttribute(KEYWORD_LITERAL_ATTR, literal);
-                    BlackboardAttribute.ATTRIBUTE_TYPE selectorType = keyword.getType();
+                    BlackboardAttribute.ATTRIBUTE_TYPE selectorType = keyword.getArtifactAttributeType();
                     if (selectorType != null) {
                         keywordEl.setAttribute(KEYWORD_SELECTOR_ATTR, selectorType.getLabel());
                     }
-                    keywordEl.setTextContent(keyword.getQuery());
+                    keywordEl.setTextContent(keyword.getSearchTerm());
                     listEl.appendChild(keywordEl);
                 }
                 rootEl.appendChild(listEl);
@@ -199,7 +199,7 @@ public boolean load() {
                     String selector = wordEl.getAttribute(KEYWORD_SELECTOR_ATTR);
                     if (!selector.equals("")) {
                         BlackboardAttribute.ATTRIBUTE_TYPE selectorType = BlackboardAttribute.ATTRIBUTE_TYPE.fromLabel(selector);
-                        keyword.setType(selectorType);
+                        keyword.setArtifactAttributeType(selectorType);
                     }
                     words.add(keyword);
 
diff --git a/NEWS.txt b/NEWS.txt
index 76610329ec8bdfc53875efcaf23478c3f978aa9b..598ef1d70762aa01f5921c44b86c3de40e6a0e79 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,17 @@
+---------------- VERSION 4.2.0  --------------
+Improvements:
+- Credit card account search.
+- Encoding/decoding of extracted files to avoid anti-virus alerts/quarantine.
+- Ingest history (start time, end time, status, which versions of which ingest modules were run). 
+- Ingest history used to warn before doing redundant analysis. 
+- Options panel for managing custom tag names.
+- Options panel for setting external viewer associations.
+- Keyboard shortcut for applying Bookmark tags.
+- Improved PhotoRec carver ingest module cancellation responsiveness.
+- Results content viewer formats dates.
+- Update to PostgreSQL 9.5.
+- Assorted bug fixes and minor enhancements. 
+
 ---------------- VERSION 4.1.1  --------------
 Bug Fixes:
 - Restored ability of Python modules to import standard Python libraries. 
diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml
index 7973da715abf57707dd97a352dc7db037f7d7ac0..a470d5ea0da34c594620b72a3780ee8ced98c29d 100644
--- a/RecentActivity/nbproject/project.xml
+++ b/RecentActivity/nbproject/project.xml
@@ -60,7 +60,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>10</release-version>
-                        <specification-version>10.5</specification-version>
+                        <specification-version>10.6</specification-version>
                     </run-dependency>
                 </dependency>
             </module-dependencies>
diff --git a/Testing/nbproject/project.xml b/Testing/nbproject/project.xml
index 8d39122411406b8e7750041d0fdb38cc3d705c9c..81fc62996403254d79df74bdf311499e897b933b 100644
--- a/Testing/nbproject/project.xml
+++ b/Testing/nbproject/project.xml
@@ -12,7 +12,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>10</release-version>
-                        <specification-version>10.5</specification-version>
+                        <specification-version>10.6</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
index 2eb10546c98561da4f788e0644770451a52fd650..e67d04fa1725933bc83e639ae34655994fde5b1e 100644
--- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
+++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
@@ -1,5 +1,5 @@
 #Updated by build script
-#Mon, 18 Jul 2016 17:58:06 -0400
+#Sat, 22 Oct 2016 14:27:47 -0400
 LBL_splash_window_title=Starting Autopsy
 SPLASH_HEIGHT=314
 SPLASH_WIDTH=538
@@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
 SplashRunningTextColor=0x0
 SplashRunningTextFontSize=19
 
-currentVersion=Autopsy 4.1.0
+currentVersion=Autopsy 4.2.0
diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
index 468b2496733ccf188fc625b00e207b2a8d816b66..c59004aab4863b18de2354053e6abfcfcafb9816 100644
--- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
+++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
@@ -1,4 +1,4 @@
 #Updated by build script
-#Mon, 18 Jul 2016 17:58:06 -0400
-CTL_MainWindow_Title=Autopsy 4.1.0
-CTL_MainWindow_Title_No_Project=Autopsy 4.1.0
+#Sat, 22 Oct 2016 14:27:47 -0400
+CTL_MainWindow_Title=Autopsy 4.2.0
+CTL_MainWindow_Title_No_Project=Autopsy 4.2.0
diff --git a/build.xml b/build.xml
index 3fcfb0ebab4ca9945646d6d3b63ea14b4c4156d7..b1e2fdb14ac87fcd5fd23aab1a0f975d49e8a244 100755
--- a/build.xml
+++ b/build.xml
@@ -15,7 +15,7 @@
         <or>
             <matches string="${java.version}" pattern="1\.8\.0_6[6-9]"/>
             <matches string="${java.version}" pattern="1\.8\.0_[7-9][0-9]"/>
-         <matches string="${java.version}" pattern="1\.8\.0_[1-9][0-9][0-9]"/>
+            <matches string="${java.version}" pattern="1\.8\.0_[1-9][0-9][0-9]"/>
             <matches string="${java.version}" pattern="1\.8\.[1-9]_[0-9][0-9]"/>
             <equals arg1="${ant.java.version}" arg2="1.9"/>
         </or>
diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile
index d86bbf0ac2cd6f0ebdf68ca6c11db78986d70671..bce35afa6753b6fc08ac5fb0860ec8067b711887 100755
--- a/docs/doxygen-user/Doxyfile
+++ b/docs/doxygen-user/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = "Autopsy User Documentation"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 4.1
+PROJECT_NUMBER         = 4.2
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -1025,7 +1025,7 @@ GENERATE_HTML          = YES
 # The default directory is: html.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_OUTPUT            = 4.1
+HTML_OUTPUT            = 4.2
 
 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
 # generated HTML page (for example: .htm, .php, .asp).
diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile
index f6fb7a7038c82160c29fc54d83db98562e04498f..e44aee3a5187f542f77e43993c49d9d28a805512 100755
--- a/docs/doxygen/Doxyfile
+++ b/docs/doxygen/Doxyfile
@@ -1063,7 +1063,7 @@ GENERATE_HTML          = YES
 # The default directory is: html.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_OUTPUT            = api-docs/4.1/
+HTML_OUTPUT            = api-docs/4.2/
 
 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
 # generated HTML page (for example: .htm, .php, .asp).
diff --git a/docs/doxygen/modAdvanced.dox b/docs/doxygen/modAdvanced.dox
index 55efb4f8726dd6f8fe71624bfd3151d07f52ad88..ac24ddbad9884b34133ae8ba6aab7f150b671f2e 100755
--- a/docs/doxygen/modAdvanced.dox
+++ b/docs/doxygen/modAdvanced.dox
@@ -24,7 +24,7 @@ Second, in the source code of the panel, there are two important methods: \c loa
 
 If one wishes to make any additional panels within the original options panel, or panels which the original opens, Autopsy provides the org.sleuthkit.autopsy.corecomponents.OptionsPanel interface to help. This interface requires the \c store() and \c load() functions also be provided in the separate panels, allowing for easier child storing and loading.
 
-Any storing or loading of settings or properties should be done in the \c store() and \c load() methods. The next section, \ref mod_dev_adv_properties, has more details on doing this.
+Any storing or loading of settings or properties should be done in the \c store() and \c load() methods.
 
 
 \subsection mod_dev_adv_events Registering for Events
diff --git a/docs/doxygen/modDev.dox b/docs/doxygen/modDev.dox
index 0eb970d9728a2d40ef1d7a643109364b9c77c97f..e79b6ee19376c1c5b896ebf8047418436f0d357f 100755
--- a/docs/doxygen/modDev.dox
+++ b/docs/doxygen/modDev.dox
@@ -59,7 +59,7 @@ After the module is created, you will need to do some further configuration.
 - Note, you will also need to come back to this section if you update the platform.  You may need to add a new dependency for the version of the Autopsy-core that comes with the updated platform. 
 - Autopsy requires that all modules restart Autopsy after they are installed. Configure your module this way under Build -> Packaging.  Check the box that says Needs Restart on Install.
 
-You now have a NetBeans module that is using Autopsy as its build platform.  That means you will have access to all of the services and utilities that Autopsy provides (such as \ref services_page). 
+You now have a NetBeans module that is using Autopsy as its build platform.  That means you will have access to all of the services and utilities that Autopsy provides. 
 
 
 \subsubsection mod_dev_mod_config_other Optional Settings
diff --git a/docs/doxygen/modDevPython.dox b/docs/doxygen/modDevPython.dox
index 2e459d69395bebc796d34506407cd7f617b905e6..0e124b3b0270789935a6965fdfaeda5633a0f8b3 100755
--- a/docs/doxygen/modDevPython.dox
+++ b/docs/doxygen/modDevPython.dox
@@ -31,7 +31,7 @@ To install NetBeans' plug-in:
 -# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
 -# Unpack the content (.nbm files) of the zip file to the desired location.
 -# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
--# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
+-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:\\Program files\\Jython2.7\\bin)
 
 To install IntelliJ IDEA + Python plug-in:
 -# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
@@ -43,9 +43,9 @@ To install IntelliJ IDEA + Python plug-in:
 -# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
 -# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
 -# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
--# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
+-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's location automatically. (Usually in C:\\Program Files (x86)\\JetBrains\\IntelliJ IDEA Community Edition 14.1.5)
 -# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
--# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
+-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\\Program Files\\Autopsy-3.1.3\\autopsy\\modules if you have executable version).
 
 \section mod_dev_py_create Creating a Basic Python Module
 
@@ -73,7 +73,7 @@ That's it. Autopsy will find the module each time it needs it and you can make u
 
 If you need to bring in a library that is not part of the standard Jython distribution, then do the following:
 
--# Copy the library file or folder into the folder that you made in \ref mod_dev_py.  For example, you may copy in folder called 'neededLib' that has a file named mylib.py in it.  The end result be a folder structure such as myModuleFolder/neededLib/mylib.py.
+-# Copy the library file or folder into the folder that you made in \ref mod_dev_py_create_dir.  For example, you may copy in folder called 'neededLib' that has a file named mylib.py in it.  The end result be a folder structure such as myModuleFolder/neededLib/mylib.py.
 -# In your Python code, if you needed a class from mylib, then you'd have a line such as:
 \code{.py}
 from neededLib.mylib import neededClass
diff --git a/docs/doxygen/platformConcepts.dox b/docs/doxygen/platformConcepts.dox
index 9fe5417377d33b212108e44df6ad3407e7ea15f4..a4759ab7a0b18491a758837ad5a81f525e6e9a47 100755
--- a/docs/doxygen/platformConcepts.dox
+++ b/docs/doxygen/platformConcepts.dox
@@ -69,6 +69,6 @@ Autopsy provides services and utilities to make it easier to write modules. Unfo
  - PlatformUtil.extractResourceToUserConfigDir()
  - PlatformUtil.isWindowsOS()
 - File Utilities: The org.sleuthkit.autopsy.coreutils.FileUtil class assists with creating and deleting folders, etc.
-- IngestModules also have a class that provides additional services unique to their needs.  See \ref ingest_modules_services_ingest. 
+- Ingest modules also have access to an IngestServices class that provides additional services unique to their needs. 
 
 */
diff --git a/nbproject/project.properties b/nbproject/project.properties
index 0bf89ac793b440edf11f67261fb595ea3e51f597..023ce3ea6a00fb70ef341dd02b616b30a1b047d4 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -4,7 +4,7 @@ app.title=Autopsy
 ### lowercase version of above
 app.name=${branding.token}
 ### if left unset, version will default to today's date
-app.version=4.1.1
+app.version=4.2.0
 ### build.type must be one of: DEVELOPMENT, RELEASE
 #build.type=RELEASE
 build.type=DEVELOPMENT
diff --git a/pythonExamples/README.txt b/pythonExamples/README.txt
index fb17a7c1016c30f101230621e6692c3d42440918..43eb78f6619516e82c0e1cd91d0ffc585efebc37 100755
--- a/pythonExamples/README.txt
+++ b/pythonExamples/README.txt
@@ -5,7 +5,7 @@ your needs.
 See the developer guide for more details and how to use and load
 the modules.
 
-    http://sleuthkit.org/autopsy/docs/api-docs/3.1/index.html
+    http://sleuthkit.org/autopsy/docs/api-docs/4.2/index.html
 
 Each module in this folder should have a brief description about what they 
 can do. 
diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml
index 3818c70735b7e2e410b608b04dbce2fc0a6b1afc..f108aa7e5a95a79d1522ea6de7db977066db272e 100644
--- a/thunderbirdparser/nbproject/project.xml
+++ b/thunderbirdparser/nbproject/project.xml
@@ -36,7 +36,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>10</release-version>
-                        <specification-version>10.5</specification-version>
+                        <specification-version>10.6</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
index 58d0050edc378eb7b4b17242d62e10f9ca7aae05..ea5827d49e9c68bb1a22ec3724baaa0f619eb113 100644
--- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
+++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
@@ -198,7 +198,6 @@ private ProcessResult processPst(AbstractFile abstractFile) {
      * Parse and extract email messages and attachments from an MBox file.
      *
      * @param abstractFile
-     * @param ingestContext
      *
      * @return
      */
@@ -292,7 +291,6 @@ public static String getRelModuleOutputPath() {
      *
      * @param emails
      * @param abstractFile
-     * @param ingestContext
      */
     private void processEmails(List<EmailMessage> emails, AbstractFile abstractFile) {
         List<AbstractFile> derivedFiles = new ArrayList<>();