diff --git a/bindings/java/doxygen/artifact_catalog.dox b/bindings/java/doxygen/artifact_catalog.dox
index e7db3f5d1b74229120a32bcb25728c89f53b3b2b..8bf2fcb6997187159772dbd7070012e7dc56784d 100644
--- a/bindings/java/doxygen/artifact_catalog.dox
+++ b/bindings/java/doxygen/artifact_catalog.dox
@@ -373,7 +373,7 @@ An email message found in an application file or database.
 - TSK_EMAIL_TO (Email addresses the email message was sent to, multiple emails should be in a comma separated string)
 - TSK_HEADERS (Transport message headers)
 - TSK_MSG_ID (Message ID supplied by the email application)
-- TSK_PATH (Path of email folders prefixed and separated by backslashes like '\Drafts\Work')
+- TSK_PATH (Path in the data source to the file containing the email message)
 - TSK_SUBJECT (Subject of the email message)
 - TSK_THREAD_ID (ID specified by the analysis module to group emails into threads for display purposes)
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
index 40360f7209611841183578d2d1b071cc3df3ffc0..1826e40793702e3b59858f4fb321fdbe79b86b5f 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
@@ -443,10 +443,10 @@ public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardAr
 		CaseDbConnection connection = null;
 		Statement statement = null;
 		ResultSet rs = null;
-
+		
 		String rowId;
 		switch (caseDb.getDatabaseType()) {
-			case POSTGRESQL:
+			case POSTGRESQL: 
 				rowId = "attrs.CTID";
 				break;
 			case SQLITE:
@@ -455,7 +455,7 @@ public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardAr
 			default:
 				throw new TskCoreException("Unknown database type: " + caseDb.getDatabaseType());
 		}
-
+		
 		caseDb.acquireSingleUserCaseReadLock();
 		try {
 			connection = caseDb.getConnection();
@@ -467,7 +467,7 @@ public ArrayList<BlackboardAttribute> getBlackboardAttributes(final BlackboardAr
 					+ "attrs.value_int64 AS value_int64, attrs.value_double AS value_double, "
 					+ "types.type_name AS type_name, types.display_name AS display_name "
 					+ "FROM blackboard_attributes AS attrs, blackboard_attribute_types AS types WHERE attrs.artifact_id = " + artifact.getArtifactID()
-					+ " AND attrs.attribute_type_id = types.attribute_type_id "
+					+ " AND attrs.attribute_type_id = types.attribute_type_id " 
 					+ " ORDER BY " + rowId);
 			ArrayList<BlackboardAttribute> attributes = new ArrayList<>();
 			while (rs.next()) {
@@ -956,14 +956,11 @@ public Score deleteAnalysisResult(long artifactObjId, CaseDbTransaction transact
 	 */
 	private Score deleteAnalysisResult(AnalysisResult analysisResult, CaseDbTransaction transaction) throws TskCoreException {
 
-		try {			
+		try {
 			CaseDbConnection connection = transaction.getConnection();
-			
-			// Delete the BlackboardArtifactTags for the analysisResult
-			caseDb.getTaggingManager().deleteBlackboardArtifactTags(analysisResult, transaction);
 
 			// delete the blackboard artifacts row. This will also delete the tsk_analysis_result row
-			String deleteSQL = "DELETE FROM tsk_objects WHERE obj_id = ?";
+			String deleteSQL = "DELETE FROM blackboard_artifacts WHERE artifact_obj_id = ?";
 
 			PreparedStatement deleteStatement = connection.getPreparedStatement(deleteSQL, Statement.RETURN_GENERATED_KEYS);
 			deleteStatement.clearParameters();
@@ -981,134 +978,6 @@ private Score deleteAnalysisResult(AnalysisResult analysisResult, CaseDbTransact
 		}
 	}
 
-	/**
-	 * Deletes all analysis results of certain type and (optionally) data
-	 * source.
-	 *
-	 * @param type	        Type of analysis results to delete
-	 *                     (BlackboardArtifact.Type)
-	 * @param dataSourceId Data source ID to delete only analysis results from
-	 *                     specific data source. If null, then delete analysis
-	 *                     results from all data sources.
-	 *
-	 * @throws TskCoreException
-	 */
-	public void deleteAnalysisResults(BlackboardArtifact.Type type, Long dataSourceId) throws TskCoreException {
-		deleteAnalysisResults(type, dataSourceId, "");
-	}
-
-	/**
-	 * Deletes all analysis results of certain type and (optionally) data
-	 * source.
-	 *
-	 * @param type	         Type of analysis results to delete
-	 *                      (BlackboardArtifact.Type)
-	 * @param dataSourceId  Data source ID to delete only analysis results from
-	 *                      specific data source. If null, then delete analysis
-	 *                      results from all data sources.
-	 * @param configuration Name of the analysis result configuration to delete.
-	 *                      Can be empty if there is no configuration for this
-	 *                      analysis result type.
-	 *
-	 * @throws TskCoreException
-	 */
-	public void deleteAnalysisResults(BlackboardArtifact.Type type, Long dataSourceId, String configuration) throws TskCoreException {
-
-		String dataSourceClause = dataSourceId == null
-				? ""
-				: " AND artifacts.data_source_obj_id = ? "; // dataSourceId
-
-		String configurationClause = (configuration == null || configuration.isEmpty()
-				? " AND (configuration IS NULL OR configuration = '' )"
-				: " AND configuration = ? ");
-
-		String getIdsQuery = "SELECT artifacts.artifact_obj_id AS artifact_obj_id, artifacts.obj_id AS obj_id, artifacts.data_source_obj_id AS data_source_obj_id "
-				+ " FROM blackboard_artifacts artifacts "
-				+ " JOIN blackboard_artifact_types AS types  ON artifacts.artifact_type_id = types.artifact_type_id "
-				+ " LEFT JOIN tsk_analysis_results AS results  ON artifacts.artifact_obj_id = results.artifact_obj_id "
-				+ " WHERE types.category_type = " + BlackboardArtifact.Category.ANALYSIS_RESULT.getID()
-				+ " AND artifacts.artifact_type_id = ? "
-				+ dataSourceClause
-				+ configurationClause;
-
-		List<Long> artifactObjIdsToDelete = new ArrayList<>();
-		Map<Long, Long> objIdsToDsIds = new HashMap<>();
-		CaseDbTransaction transaction = this.caseDb.beginTransaction();
-		String currentQuery = "";
-		try (CaseDbConnection connection = transaction.getConnection()) {
-
-			try {
-				// get obj_ids of the artifacts that need to be deleted
-				currentQuery = getIdsQuery;
-				PreparedStatement preparedStatement = connection.getPreparedStatement(currentQuery, Statement.RETURN_GENERATED_KEYS);
-				preparedStatement.clearParameters();
-				int paramIdx = 0;
-
-				preparedStatement.setInt(++paramIdx, type.getTypeID());
-
-				if (dataSourceId != null) {
-					preparedStatement.setLong(++paramIdx, dataSourceId);
-				}
-
-				if (!(configuration == null || configuration.isEmpty())) {
-					preparedStatement.setString(++paramIdx, configuration);
-				}
-
-				try (ResultSet resultSet = connection.executeQuery(preparedStatement)) {
-					while (resultSet.next()) {
-						artifactObjIdsToDelete.add(resultSet.getLong("artifact_obj_id"));
-						
-						objIdsToDsIds.put(resultSet.getLong("obj_id"), resultSet.getLong("data_source_obj_id"));
-					}
-				}
-
-				if (artifactObjIdsToDelete.isEmpty()) {
-					transaction.close();
-					transaction = null;
-					return;
-				}
-
-				// delete the identified artifacts by obj_id. the number of results 
-				// could be very large so we should split deletion into batches to limit the
-				// size of the SQL string
-				int maxArtifactsToDeleteAtOnce = 50;
-				for (int startId = 0; startId < artifactObjIdsToDelete.size(); startId += maxArtifactsToDeleteAtOnce) {
-
-					List<String> newList = artifactObjIdsToDelete.subList(startId, Math.min(artifactObjIdsToDelete.size(), startId + maxArtifactsToDeleteAtOnce)).stream()
-							.map(String::valueOf)
-							.collect(Collectors.toList());
-					
-					currentQuery = "DELETE FROM tsk_objects WHERE obj_id IN ("
-							+ String.join(",", newList)
-							+ ")";
-
-					Statement statement = connection.createStatement();
-					connection.executeUpdate(statement, currentQuery);
-				}
-
-				for (Map.Entry<Long, Long> entry : objIdsToDsIds.entrySet()) {
-					Long objId = entry.getKey();
-					Long dsId = entry.getValue();
-					// register the deleted result with the transaction so an event can be fired for it. 
-					transaction.registerDeletedAnalysisResult(objId);
-
-					// update score of the source file
-					caseDb.getScoringManager().updateAggregateScoreAfterDeletion(objId, dsId, transaction);
-				}
-
-				transaction.commit();
-				transaction = null;
-
-			} catch (SQLException ex) {
-				throw new TskCoreException(String.format("Error while performing query = '%s'", currentQuery), ex);
-			} 
-		} finally {
-			if (transaction != null) {
-				transaction.rollback();
-			}
-		}
-	}
-
 	private final static String ANALYSIS_RESULT_QUERY_STRING_GENERIC = "SELECT DISTINCT artifacts.artifact_id AS artifact_id, " //NON-NLS
 			+ " artifacts.obj_id AS obj_id, artifacts.artifact_obj_id AS artifact_obj_id, artifacts.data_source_obj_id AS data_source_obj_id, artifacts.artifact_type_id AS artifact_type_id, "
 			+ " types.type_name AS type_name, types.display_name AS display_name, types.category_type as category_type,"//NON-NLS
@@ -1978,7 +1847,9 @@ public List<BlackboardArtifact> getExactMatchKeywordSearchResults(String keyword
 	 *                     well as the keyword. It should be empty for literal
 	 *                     exact match keyword search types.
 	 * @param searchType   Type of keyword search query.
-	 * @param configuration  Configuration of keyword hits.
+	 * @param kwsListName  (Optional) Name of the keyword list for which the
+	 *                     search results are for. If not specified, then the
+	 *                     results will be for ad-hoc keyword searches.
 	 * @param dataSourceId (Optional) Data source id of the target data source.
 	 *                     If null, then the results will be for all data
 	 *                     sources.
@@ -1988,18 +1859,15 @@ public List<BlackboardArtifact> getExactMatchKeywordSearchResults(String keyword
 	 * @throws TskCoreException If an exception is encountered while running
 	 *                          database query to obtain the keyword hits.
 	 */
-	@Beta
-	public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String regex, TskData.KeywordSearchQueryType searchType, String configuration, Long dataSourceId) throws TskCoreException {
-
+	public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String regex, TskData.KeywordSearchQueryType searchType, String kwsListName, Long dataSourceId) throws TskCoreException {
+		
 		String dataSourceClause = dataSourceId == null
 				? ""
 				: " AND artifacts.data_source_obj_id = ? "; // dataSourceId
 
-		// allow for current way configuration is used for keyword sets (i.e. configuration is list name)
-		// as well as previous way configuration was used for keyword sets (i.e. configuration was null or empty and TSK_SET_NAME was used)
-		String configurationClause = (configuration == null || configuration.isEmpty()
-				? " WHERE ((r.configuration IS NULL OR LENGTH(r.configuration) = 0) AND (r.set_name IS NULL OR LENGTH(r.set_name) = 0))"
-				: " WHERE (r.configuration = ? OR ((r.configuration IS NULL OR LENGTH(r.configuration) = 0) AND r.set_name = ?))");
+		String kwsListClause = (kwsListName == null || kwsListName.isEmpty()
+				? " WHERE r.set_name IS NULL "
+				: " WHERE r.set_name = ? ");
 
 		String keywordClause = (keyword == null || keyword.isEmpty()
 				? ""
@@ -2044,7 +1912,7 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 				+ " WHERE types.category_type = " + BlackboardArtifact.Category.ANALYSIS_RESULT.getID()
 				+ " AND artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " "
 				+ dataSourceClause + " ) r "
-				+ configurationClause
+				+ kwsListClause
 				+ keywordClause
 				+ searchTypeClause
 				+ regexClause;
@@ -2060,10 +1928,9 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 				if (dataSourceId != null) {
 					preparedStatement.setLong(++paramIdx, dataSourceId);
 				}
-
-				if (!(configuration == null || configuration.isEmpty())) {
-					preparedStatement.setString(++paramIdx, configuration);
-					preparedStatement.setString(++paramIdx, configuration);
+								
+				if (!(kwsListName == null || kwsListName.isEmpty())) {
+					preparedStatement.setString(++paramIdx, kwsListName);
 				}
 
 				if (!(keyword == null || keyword.isEmpty())) {
@@ -2077,7 +1944,7 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 				if (!(regex == null || regex.isEmpty())) {
 					preparedStatement.setString(++paramIdx, regex);
 				}
-
+				
 				try (ResultSet resultSet = connection.executeQuery(preparedStatement)) {
 					artifacts.addAll(resultSetToAnalysisResults(resultSet));
 				}
@@ -2090,7 +1957,7 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 		}
 		return artifacts;
 	}
-
+	
 	/**
 	 * Gets count of blackboard artifacts of given type that match a given WHERE
 	 * clause. Uses a SELECT COUNT(*) FROM blackboard_artifacts statement
@@ -2468,7 +2335,7 @@ final public class ArtifactsPostedEvent {
 		 * artifacts are posted. Posted artifacts should be complete (all
 		 * attributes have been added) and ready for further analysis.
 		 *
-		 * @param artifacts   The artifacts.
+		 * @param artifacts   The artifacts. 
 		 * @param moduleName  The display name of the module posting the
 		 *                    artifacts.
 		 * @param ingestJobId The numeric identifier of the ingest job within
diff --git a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
index b3990eb92e6f87470f3a4d7decf4a45ffa228aca..fba83e40ebd9c4d3d87817773af9ba14ae281ccd 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/BlackboardArtifact.java
@@ -900,6 +900,21 @@ public String toString() {
 		return "BlackboardArtifact{" + "artifactID=" + artifactId + ", objID=" + getObjectID() + ", artifactObjID=" + artifactObjId + ", artifactTypeID=" + artifactTypeId + ", artifactTypeName=" + artifactTypeName + ", displayName=" + displayName + ", Case=" + getSleuthkitCase() + '}'; //NON-NLS
 	}
 
+	/**
+	 * Accepts a visitor SleuthkitItemVisitor that will perform an operation on
+	 * this artifact type and return some object as the result of the operation.
+	 *
+	 * @param visitor The visitor, where the type parameter of the visitor is
+	 *                the type of the object that will be returned as the result
+	 *                of the visit operation.
+	 *
+	 * @return An object of type T.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Get the (reported) size of the content object. Artifact content is a
 	 * string dump of all its attributes.
@@ -1579,7 +1594,7 @@ public int hashCode() {
 	 * http://sleuthkit.org/sleuthkit/docs/jni-docs/latest/artifact_catalog_page.html
 	 * for details on the standard attributes for each artifact type.
 	 */
-	public enum ARTIFACT_TYPE {
+	public enum ARTIFACT_TYPE implements SleuthkitVisitableItem {
 
 		/**
 		 * A generic information artifact.
@@ -2101,6 +2116,23 @@ static public ARTIFACT_TYPE fromID(int id) {
 		public String getDisplayName() {
 			return displayName;
 		}
+
+		/**
+		 * Accepts a visitor SleuthkitItemVisitor that will perform an operation
+		 * on this artifact type and return some object as the result of the
+		 * operation.
+		 *
+		 * @param visitor The visitor, where the type parameter of the visitor
+		 *                is the type of the object that will be returned as the
+		 *                result of the visit operation.
+		 *
+		 * @return An object of type T.
+		 */
+		@Override
+		public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+			return visitor.visit(this);
+		}
+
 	}
 
 	/**
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
index 9d168aa0b29391cf093bf4c4c152bf6de5e2be0a..f06ceeef19ac6743527a963603a4c5f418932bd2 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
+++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED
@@ -394,7 +394,6 @@ OsAccountStatus.Unknown.text=Unknown
 OsAccountStatus.Active.text=Active
 OsAccountStatus.Disabled.text=Disabled
 OsAccountStatus.Deleted.text=Deleted
-OsAccountStatus.NonExistent.text=Non Existent
 OsAccountType.Unknown.text=Unknown
 OsAccountType.Service.text=Service
 OsAccountType.Interactive.text=Interactive
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Content.java b/bindings/java/src/org/sleuthkit/datamodel/Content.java
index a8779ad1e6cd6f8abb451e840e316f54c2fa0a6e..5993ac460165046abba1e0ec0cf1603f181e397a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Content.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Content.java
@@ -30,7 +30,7 @@
  * interface defines the basic methods for reading the content associated with
  * this object, the parent and children, and adding artifacts.
  */
-public interface Content {
+public interface Content extends SleuthkitVisitableItem {
 
 	/**
 	 * Reads data that this content object is associated with (file contents,
diff --git a/bindings/java/src/org/sleuthkit/datamodel/ContentVisitor.java b/bindings/java/src/org/sleuthkit/datamodel/ContentVisitor.java
index 875f9bad1345c4f950b256fee791e071e9f295d3..7d922b9f126955745f0717dcdfcaccbde3728d71 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/ContentVisitor.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/ContentVisitor.java
@@ -66,7 +66,7 @@ public interface ContentVisitor<T> {
 	 * @return result of the visit
 	 */
 	T visit(Image i);
-
+	
 	/**
 	 * Act on (visit) a Pool content object
 	 *
@@ -74,7 +74,7 @@ public interface ContentVisitor<T> {
 	 *
 	 * @return result of the visit
 	 */
-	T visit(Pool p);
+	T visit(Pool p);	
 
 	/**
 	 * Act on (visit) a Volume content object
@@ -111,7 +111,7 @@ public interface ContentVisitor<T> {
 	 * @return result of the visit
 	 */
 	T visit(VirtualDirectory vd);
-
+	
 	/**
 	 * Act on (visit) a LocalDirectory content object
 	 *
@@ -119,7 +119,7 @@ public interface ContentVisitor<T> {
 	 *
 	 * @return result of the visit
 	 */
-	T visit(LocalDirectory ld);
+	T visit(LocalDirectory ld);	
 
 	/**
 	 * Act on (visit) a DerivedFile content object
@@ -138,7 +138,7 @@ public interface ContentVisitor<T> {
 	 * @return result of the visit
 	 */
 	T visit(LocalFile df);
-
+	
 	/**
 	 * Act on (visit) a SlackFile content object
 	 *
@@ -146,7 +146,7 @@ public interface ContentVisitor<T> {
 	 *
 	 * @return result of the visit
 	 */
-	T visit(SlackFile sf);
+	T visit(SlackFile sf);	
 
 	/**
 	 * Act on (visit) a blackboard artifact object
@@ -155,17 +155,8 @@ public interface ContentVisitor<T> {
 	 *
 	 * @return result of the visit
 	 */
-	T visit(BlackboardArtifact ba);
-
-	/**
-	 * Act on (visit) a local files data source object
-	 *
-	 * @param ds The local files data source object
-	 *
-	 * @return result of the visit
-	 */
-	T visit(LocalFilesDataSource lfds);
-
+	T visit(BlackboardArtifact ba);	
+	
 	/**
 	 * Act on (visit) a Report object
 	 *
@@ -174,7 +165,7 @@ public interface ContentVisitor<T> {
 	 * @return result of the visit
 	 */
 	T visit(Report r);
-
+	
 	/**
 	 * Act on (visit) a OsAccount object
 	 *
@@ -183,7 +174,7 @@ public interface ContentVisitor<T> {
 	 * @return result of the visit
 	 */
 	T visit(OsAccount act);
-
+	
 	/**
 	 * Act on (visit) an UnsupportedContent object
 	 *
@@ -231,7 +222,7 @@ public T visit(Image i) {
 		public T visit(Volume v) {
 			return defaultVisit(v);
 		}
-
+		
 		@Override
 		public T visit(Pool p) {
 			return defaultVisit(p);
@@ -251,7 +242,7 @@ public T visit(LayoutFile lf) {
 		public T visit(VirtualDirectory ld) {
 			return defaultVisit(ld);
 		}
-
+		
 		@Override
 		public T visit(LocalDirectory ld) {
 			return defaultVisit(ld);
@@ -266,32 +257,27 @@ public T visit(DerivedFile df) {
 		public T visit(LocalFile lf) {
 			return defaultVisit(lf);
 		}
-
+		
 		@Override
 		public T visit(SlackFile sf) {
 			return defaultVisit(sf);
 		}
-
+		
 		@Override
 		public T visit(BlackboardArtifact ba) {
 			return defaultVisit(ba);
 		}
 
-		@Override
-		public T visit(LocalFilesDataSource lfds) {
-			return defaultVisit(lfds);
-		}
-
 		@Override
 		public T visit(Report r) {
 			return defaultVisit(r);
 		}
-
+		
 		@Override
 		public T visit(OsAccount act) {
 			return defaultVisit(act);
 		}
-
+		
 		@Override
 		public T visit(UnsupportedContent uc) {
 			return defaultVisit(uc);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java b/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java
index 05676b3ed38bafdf194c08f569756c0bb228fdfb..22cbba2660f29ffbef4ac96d0afcfba3f42467d7 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java
@@ -153,6 +153,19 @@ public synchronized DerivedMethod getDerivedMethod() throws TskCoreException {
 		return derivedMethod;
 	}
 
+	/**
+	 * Accepts a content visitor (Visitor design pattern).
+	 *
+	 * @param visitor A ContentVisitor supplying an algorithm to run using this
+	 *                derived file as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	/**
 	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Directory.java b/bindings/java/src/org/sleuthkit/datamodel/Directory.java
index 24612b577adc985b5bf14f08caaf3d4622215071..877a626bedfc0912d9162d0afc215f78adc17e89 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Directory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Directory.java
@@ -97,6 +97,19 @@ public class Directory extends FsContent {
 		super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, sha1Hash, knownState, parentPath, null, null, ownerUid, osAccountObjId, TskData.CollectedStatus.UNKNOWN, Collections.emptyList());
 	}
 
+	/**
+	 * Accepts a content visitor (Visitor design pattern).
+	 *
+	 * @param visitor A ContentVisitor supplying an algorithm to run using this
+	 *                directory as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	/**
 	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/File.java b/bindings/java/src/org/sleuthkit/datamodel/File.java
index 6fefc6ae807b11093d7e98865a6dfe60665a9d8c..5ee548df86a5a7532b83fb844328a8250e8b1af6 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/File.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/File.java
@@ -109,6 +109,19 @@ public class File extends FsContent {
 				ownerUid, osAccountObjId, collected, fileAttributes);
 	}
 
+	/**
+	 * Accepts a content visitor (Visitor design pattern).
+	 *
+	 * @param visitor A ContentVisitor supplying an algorithm to run using this
+	 *                file as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java b/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java
index 646b09c9d44ce540e0488a3b97e29910b73a54d3..45a21b58e30e55f5c554c47d6489f17eee56401a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java
@@ -208,6 +208,11 @@ public void finalize() throws Throwable {
 		}
 	}
 	
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	@Override
 	public <T> T accept(ContentVisitor<T> v) {
 		return v.visit(this);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/HostAddress.java b/bindings/java/src/org/sleuthkit/datamodel/HostAddress.java
index 191c759dec969d1b20ebac8b6b5a774471e77a01..4b545baee03e78c3acffffa4bc79432bcdafb160 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/HostAddress.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/HostAddress.java
@@ -128,6 +128,12 @@ public <T> T accept(ContentVisitor<T> v) {
 		throw new UnsupportedOperationException("Not supported yet.");
 	}
 
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		// TODO
+		throw new UnsupportedOperationException("Not supported yet.");
+	}
+
 	/**
 	 * A host may have different types of addresses at a given point in time.
 	 */
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Image.java b/bindings/java/src/org/sleuthkit/datamodel/Image.java
index 8f1af1fe0cea9651f433b24f91547db795bc8ba7..f51eac30a399d09f063ceae1688eff564f1cb82d 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Image.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Image.java
@@ -276,6 +276,11 @@ public String getTimeZone() {
 		return timezone;
 	}
 
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	@Override
 	public <T> T accept(ContentVisitor<T> v) {
 		return v.visit(this);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java b/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java
index 7a19041120f70a7f5f9768002ff39b8b246f5f14..938f57124be052ef9873731edc5365dfc1c53c8e 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java
@@ -233,6 +233,19 @@ public <T> T accept(ContentVisitor<T> visitor) {
 		return visitor.visit(this);
 	}
 
+	/**
+	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
+	 *
+	 * @param visitor A SleuthkitItemVisitor supplying an algorithm to run using
+	 *                this file as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Provides a string representation of this file.
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java b/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java
index 699f41de196d142ad2c325cfd084fc22a2cda69e..b3eed95ed10f0c91f772f87ce4016afe5cf61491 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java
@@ -112,6 +112,19 @@ public <T> T accept(ContentVisitor<T> visitor) {
 		return visitor.visit(this);
 	}
 
+	/**
+	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
+	 *
+	 * @param visitor A SleuthkitItemVisitor supplying an algorithm to run using
+	 *                this local directory as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Provides a string representation of this local directory.
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java b/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java
index 8c22a2e25b45722fe228c5372aefb3d75dac25c5..8a7948ce64ed12f7a9a9741a7ee5d2f5cfbd682b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java
@@ -149,6 +149,20 @@ public <T> T accept(ContentVisitor<T> visitor) {
 		return visitor.visit(this);
 	}
 
+	/**
+	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
+	 *
+	 * @param <T>     The type returned by the visitor.
+	 * @param visitor A SleuthkitItemVisitor supplying an algorithm to run using
+	 *                this local file as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Provides a string representation of this local file.
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java b/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java
index ee172f188a107e8fdf2d0226c5934b9762d6f2d9..3107c4b7108a8220bcaabf59451fd3f6cf8a3839 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java
@@ -334,6 +334,20 @@ public <T> T accept(ContentVisitor<T> visitor) {
 		return visitor.visit(this);
 	}
 	
+	/**
+	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
+	 *
+	 * @param <T>     The type returned by the visitor.
+	 * @param visitor A SleuthkitItemVisitor supplying an algorithm to run using
+	 *                this virtual directory as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+	
 	/**
 	 * Constructs a local/logical files and/or directories data source.
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java
index 42f758a0e2addc4befb3d25a9ec7f2b568c678a2..3549228c49e287e24f8cbd2c3517a204eda73947 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java
@@ -401,6 +401,11 @@ public <T> T accept(ContentVisitor<T> v) {
 		throw new UnsupportedOperationException("Not supported yet.");
 	}
 
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	/**
 	 * Abstracts attributes of an OS account. An attribute may be specific to a
 	 * host, or applicable across all hosts.
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Pool.java b/bindings/java/src/org/sleuthkit/datamodel/Pool.java
index 9059f95bbcc88a87c20dca7fe166e74bf5197d16..98681759fd86791e7152ca2de1a0247113d7b737 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Pool.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Pool.java
@@ -140,6 +140,11 @@ protected void finalize() throws Throwable {
 		}
 	}
 	
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	@Override
 	public <T> T accept(ContentVisitor<T> v) {
 		return v.visit(this);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Report.java b/bindings/java/src/org/sleuthkit/datamodel/Report.java
index 476cc3d1e2a0bbb89a9dbece2a849c6663838958..afc07dfacab807eb7cb53bf95444dc33279e396a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Report.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Report.java
@@ -404,4 +404,9 @@ public long getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE type) throws TskC
 	public long getAllArtifactsCount() throws TskCoreException {
 		return db.getBlackboardArtifactsCount(objectId);
 	}
+
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/ScoreChange.java b/bindings/java/src/org/sleuthkit/datamodel/ScoreChange.java
index 4b8fe82ea36bcb5b57b4397ee778d5d511d13700..cf051b71dd02b7fef7840e4864eb37c8addee96c 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/ScoreChange.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/ScoreChange.java
@@ -18,6 +18,8 @@
  */
 package org.sleuthkit.datamodel;
 
+import java.util.Optional;
+
 /**
  * This class encapsulates a score change.
  */
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
index e457191577171806c3f9223e86aafa69c1edcff8..e6bf7c4e0d5c2ff98fc2efdaa90c9d08e5907630 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
@@ -127,6 +127,19 @@ protected int readInt(byte[] buf, long offset, long len) throws TskCoreException
 		return SleuthkitJNI.readFileSlack(fileHandle, buf, offset, len);
 	}
 
+	/**
+	 * Accepts a content visitor (Visitor design pattern).
+	 *
+	 * @param v A ContentVisitor supplying an algorithm to run using this file
+	 *          as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	/**
 	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index d76f85de22ea6a8b8af4e45669868167a3a8d2de..c04094367e8b6e2164ad3896bffb941361514893 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -11742,8 +11742,6 @@ public void deleteContentTag(ContentTag tag) throws TskCoreException {
 
 			trans.commit();
 			trans = null;
-			
-			fireTSKEvent(new TskEvent.ContentTagsDeletedTskEvent(Collections.singletonList(tag)));
 		} catch (SQLException ex) {
 			throw new TskCoreException("Error deleting row from content_tags table (id = " + tag.getId() + ")", ex);
 		} finally {
@@ -12089,14 +12087,10 @@ public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifac
 		return taggingMgr.addArtifactTag(artifact, tagName, comment).getAddedTag();
 	}
 
-	/**
+	/*
 	 * Deletes a row from the blackboard_artifact_tags table in the case
-	 * database. 
-	 * 
-	 * @param tag A BlackboardArtifactTag data transfer object (DTO)
-	 * representing the row to delete. 
-	 * 
-	 * @throws TskCoreException
+	 * database. @param tag A BlackboardArtifactTag data transfer object (DTO)
+	 * representing the row to delete. @throws TskCoreException
 	 */
 	public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException {
 		CaseDbTransaction trans = beginTransaction();
@@ -12117,9 +12111,6 @@ public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCor
 
 			trans.commit();
 			trans = null;
-			
-			fireTSKEvent(new TskEvent.BlackboardArtifactTagsDeletedTskEvent(Collections.singletonList(tag)));
-			
 		} catch (SQLException ex) {
 			throw new TskCoreException("Error deleting row from blackboard_artifact_tags table (id = " + tag.getId() + ")", ex);
 		} finally {
@@ -13972,7 +13963,6 @@ public static final class CaseDbTransaction {
 
 		private List<Long> deletedOsAccountObjectIds = new ArrayList<>();
 		private List<Long> deletedResultObjectIds = new ArrayList<>();
-		private List<BlackboardArtifactTag> deletedBlackboardArtifactTagIds = new ArrayList<>();
 
     // Keep track of which threads have connections to debug deadlocks
     private static Set<Long> threadsWithOpenTransaction = new HashSet<>();
@@ -13992,6 +13982,7 @@ private CaseDbTransaction(SleuthkitCase sleuthkitCase) throws TskCoreException {
 				sleuthkitCase.releaseSingleUserCaseWriteLock();
 				throw new TskCoreException("Failed to create transaction on case database", ex);
 			}
+
 		}
 
 		/**
@@ -14085,16 +14076,6 @@ void registerMergedOsAccount(long sourceOsAccountObjId, long destinationOsAccoun
 		void registerDeletedAnalysisResult(long analysisResultObjId) {
 			this.deletedResultObjectIds.add(analysisResultObjId);
 		}
-		
-		/**
-		 * Saves a list of BlackboardArtifactTags that have been deleted as 
-		 * a parent of this transaction;
-		 * 
-		 * @param tagIds Deleted tags.
-		 */
-		void registerDeletedBlackboardArtifactTags(List<BlackboardArtifactTag> tags) {
-			deletedBlackboardArtifactTagIds.addAll(tags);
-		}
 
 		/**
 		 * Check if the given thread has an open transaction.
@@ -14125,24 +14106,12 @@ public void commit() throws TskCoreException {
 				close();
 
 				if (!scoreChangeMap.isEmpty()) {
-					// NOTE: data source ID can be NULL so we can't use Collectors.groupingBy(ScoreChange::getDataSourceObjectId)
-					// because that doesn't support NULL and throws an NPE
-					Map<Long, List<ScoreChange>> changesByDataSource = new HashMap<>();
-					for (Map.Entry<Long, ScoreChange> entry : scoreChangeMap.entrySet()) {						
-						List<ScoreChange> scoreChanges = changesByDataSource.get(entry.getValue().getDataSourceObjectId());
-						if (scoreChanges == null) {
-							changesByDataSource.put(entry.getValue().getDataSourceObjectId(), new ArrayList<>(Arrays.asList(entry.getValue())));
-						} else {
-							scoreChanges.add(entry.getValue());
-						}
-					}					
+					Map<Long, List<ScoreChange>> changesByDataSource = scoreChangeMap.values().stream()
+							.collect(Collectors.groupingBy(ScoreChange::getDataSourceObjectId));
 					for (Map.Entry<Long, List<ScoreChange>> entry : changesByDataSource.entrySet()) {
 						sleuthkitCase.fireTSKEvent(new TskEvent.AggregateScoresChangedEvent(entry.getKey(), ImmutableSet.copyOf(entry.getValue())));
 					}
 				}
-				if(!deletedBlackboardArtifactTagIds.isEmpty()) {
-					sleuthkitCase.fireTSKEvent(new TskEvent.BlackboardArtifactTagsDeletedTskEvent(deletedBlackboardArtifactTagIds));
-				}
 				if (!timelineEvents.isEmpty()) {
 					for (TimelineEventAddedEvent evt : timelineEvents) {
 						sleuthkitCase.fireTSKEvent(evt);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitItemVisitor.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitItemVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e90113f99b0647e451fb2d5cef4c80cca79a0b2
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitItemVisitor.java
@@ -0,0 +1,316 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2011-2021 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.datamodel;
+
+/**
+ * Interface for implementing a visitor pattern on all displayable items:
+ * Content implementations and blackboard artifacts.
+ *
+ * Visitor implements an algorithm on the content and blackboard artifacts
+ * objects. The algorithm is completely decoupled from the data object. The
+ * visitor pattern emulates double dispatch mechanism. It allows to act
+ * differently depending on the instance type, without need to test what the
+ * actual type is. E.g. it allows for processing an object hierarchy without
+ * using instanceof statements. Generic type parameter T is a return type from
+ * the visit methods.
+ *
+ * @param <T> return type of visit methods
+ */
+public interface SleuthkitItemVisitor<T> {
+
+	/**
+	 * Act on (visit) a Directory content object
+	 *
+	 * @param d the directory to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(Directory d);
+
+	/**
+	 * Act on (visit) a File content object
+	 *
+	 * @param f the file to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(File f);
+
+	/**
+	 * Act on (visit) a FileSystem content object
+	 *
+	 * @param fs the filesystem to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(FileSystem fs);
+
+	/**
+	 * Act on (visit) an Image content object
+	 *
+	 * @param i the image to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(Image i);
+
+	/**
+	 * Act on (visit) a Volume content object
+	 *
+	 * @param v the volume to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(Volume v);
+
+	/**
+	 * Act on (visit) a VolumeSystem content object
+	 *
+	 * @param vs the volume system to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(VolumeSystem vs);
+	
+	/**
+	 * Act on (visit) a Pool content object
+	 *
+	 * @param pool the volume system to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(Pool pool);
+
+	/**
+	 * Act on (visit) a blackboard artifact object
+	 *
+	 * @param ba blackboard artifact object to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(BlackboardArtifact ba);
+
+	/**
+	 * Act on (visit) a blackboard artifact type
+	 *
+	 * @param tw blackboard artifact type to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(BlackboardArtifact.ARTIFACT_TYPE tw);
+
+	/**
+	 * Act on (visit) a layout file content object
+	 *
+	 * @param lf layout file to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(LayoutFile lf);
+
+	/**
+	 * Act on (visit) a VirtualDirectory content object
+	 *
+	 * @param ld layout dir to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(VirtualDirectory ld);
+
+	/**
+	 * Act on (visit) a LocalDirectory content object
+	 *
+	 * @param ld layout dir to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(LocalDirectory ld);
+
+	/**
+	 * Act on (visit) a DerivedFile content object
+	 *
+	 * @param df derived file to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(DerivedFile df);
+
+	/**
+	 * Act on (visit) a LocalFile content object
+	 *
+	 * @param lf local file to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(LocalFile lf);
+
+	/**
+	 * Act on (visit) a SlackFile content object
+	 *
+	 * @param sf slack file to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(SlackFile sf);
+
+	/**
+	 * Act on (visit) a Report content object
+	 *
+	 * @param report report to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(Report report);
+	
+	/**
+	 * Act on (visit) a OsAccount content object
+	 *
+	 * @param account report to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(OsAccount account);
+	
+	/**
+	 * Act on (visit) an UnsupportedContent object
+	 *
+	 * @param unsupportedContent content to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(UnsupportedContent unsupportedContent);
+	
+	/**
+	 * Act on (visit) a LocalFilesDataSource content object
+	 *
+	 * @param localFilesDataSource report to visit / act on
+	 *
+	 * @return result of the visit
+	 */
+	T visit(LocalFilesDataSource localFilesDataSource);
+
+	/**
+	 * The default visitor - quickest method for implementing a custom visitor.
+	 * Every visit method delegates to the defaultVisit method, the only
+	 * required method to be implemented. Then, implement the specific visit
+	 * methods for the objects on which the algorithm needs to act differently.
+	 *
+	 * @param <T> generic type, signifies the object type to be returned from
+	 *            visit()
+	 */
+	static abstract public class Default<T> implements SleuthkitItemVisitor<T> {
+
+		protected abstract T defaultVisit(SleuthkitVisitableItem s);
+
+		@Override
+		public T visit(Directory d) {
+			return defaultVisit(d);
+		}
+
+		@Override
+		public T visit(File f) {
+			return defaultVisit(f);
+		}
+
+		@Override
+		public T visit(FileSystem fs) {
+			return defaultVisit(fs);
+		}
+
+		@Override
+		public T visit(Image i) {
+			return defaultVisit(i);
+		}
+
+		@Override
+		public T visit(Volume v) {
+			return defaultVisit(v);
+		}
+
+		@Override
+		public T visit(VolumeSystem vs) {
+			return defaultVisit(vs);
+		}
+		
+		@Override
+		public T visit(Pool p) {
+			return defaultVisit(p);
+		}
+
+		@Override
+		public T visit(BlackboardArtifact ba) {
+			return defaultVisit(ba);
+		}
+
+		@Override
+		public T visit(BlackboardArtifact.ARTIFACT_TYPE tw) {
+			return defaultVisit(tw);
+		}
+
+		@Override
+		public T visit(LayoutFile lf) {
+			return defaultVisit(lf);
+		}
+
+		@Override
+		public T visit(VirtualDirectory vd) {
+			return defaultVisit(vd);
+		}
+
+		@Override
+		public T visit(LocalDirectory ld) {
+			return defaultVisit(ld);
+		}
+
+		@Override
+		public T visit(DerivedFile df) {
+			return defaultVisit(df);
+		}
+
+		@Override
+		public T visit(LocalFile lf) {
+			return defaultVisit(lf);
+		}
+
+		@Override
+		public T visit(SlackFile sf) {
+			return defaultVisit(sf);
+		}
+
+		@Override
+		public T visit(Report report) {
+			return defaultVisit(report);
+		}
+		
+		@Override
+		public T visit(OsAccount account) {
+			return defaultVisit(account);
+		}
+		
+		@Override
+		public T visit(UnsupportedContent unsupportedContent) {
+			return defaultVisit(unsupportedContent);
+		}
+		
+		@Override
+		public T visit(LocalFilesDataSource localFilesDataSource) {
+			return defaultVisit(localFilesDataSource);
+		}
+	}
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitVisitableItem.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitVisitableItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed17d61d71d567db2bea40745c95888fceaf41e0
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitVisitableItem.java
@@ -0,0 +1,35 @@
+/*
+ * Sleuth Kit Data Model
+ * 
+ * Copyright 2011 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.datamodel;
+
+/**
+ * Interface for all visitable datatypes that can be found in the tsk database
+ */
+public interface SleuthkitVisitableItem {
+
+	/**
+	 * visitor pattern support
+	 *
+	 * @param v visitor
+	 *
+	 * @return visitor return value
+	 */
+	public <T> T accept(SleuthkitItemVisitor<T> v);
+
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
index b63b88f4e4f778dd41decaf6387c1e94114bca74..58cf8d2001274dfff169c73bcc6fa4268d1e4f47 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TaggingManager.java
@@ -26,7 +26,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
 import static org.sleuthkit.datamodel.TskData.DbType.POSTGRESQL;
@@ -272,7 +271,7 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 		CaseDbTransaction trans = null;
 		try {
 			// If a TagName is part of a TagSet remove any existing tags from the
-			// set that are currently on the artifact
+			// set that are currenctly on the artifact
 			long tagSetId = tagName.getTagSetId();
 			if (tagSetId > 0) {
 				// Get the list of all of the blackboardArtifactTags that use
@@ -349,10 +348,6 @@ public BlackboardArtifactTagChange addArtifactTag(BlackboardArtifact artifact, T
 
 			trans.commit();
 
-			skCase.fireTSKEvent(new TskEvent.BlackboardArtifactTagsDeletedTskEvent(removedTags));
-
-			skCase.fireTSKEvent(new TskEvent.BlackboardArtifactTagsAddedTskEvent(Collections.singletonList(artifactTag)));
-			
 			return new BlackboardArtifactTagChange(artifactTag, removedTags);
 		} catch (SQLException ex) {
 			if (trans != null) {
@@ -521,10 +516,6 @@ public ContentTagChange addContentTag(Content content, TagName tagName, String c
 					content.getId(), dataSourceId, getTagScore(tagName.getKnownStatus()), trans);
 
 			trans.commit();
-			
-			skCase.fireTSKEvent(new TskEvent.ContentTagsDeletedTskEvent(Collections.singletonList(contentTag)));
-			skCase.fireTSKEvent(new TskEvent.ContentTagsAddedTskEvent(Collections.singletonList(contentTag)));
-			
 			return new ContentTagChange(contentTag, removedTags);
 		} catch (SQLException ex) {
 			trans.rollback();
@@ -707,44 +698,6 @@ private List<TagName> getTagNamesByTagSetID(int tagSetId) throws TskCoreExceptio
 
 		return tagNameList;
 	}
-	
-	/**
-	 * Delete any BlackboardArtifactTags (Result Tag) associated with the given
-	 * artifact.
-	 * 
-	 * @param artifact 
-	 * @param transaction
-	 * 
-	 * @throws TskCoreException 
-	 */
-	public void deleteBlackboardArtifactTags(BlackboardArtifact artifact, CaseDbTransaction transaction) throws TskCoreException {
-		List<BlackboardArtifactTag> tags = skCase.getBlackboardArtifactTagsByArtifact(artifact);
-		if (tags.isEmpty()) {
-			return;
-		}
-
-		List<Long> tagIds = new ArrayList<>();
-		for (BlackboardArtifactTag tag : tags) {
-			tagIds.add(tag.getId());
-		}
-
-		String query = String.format("DELETE FROM blackboard_artifact_tags WHERE tag_id IN (%s)",
-				tagIds.stream().
-						map(String::valueOf)
-						.collect(Collectors.joining(",")));
-
-		CaseDbConnection connection = transaction.getConnection();
-
-		try (Statement statement = connection.createStatement()) {
-			statement.executeUpdate(query);
-		} catch (SQLException ex) {
-			throw new TskCoreException("Error deleting row from blackboard_artifact_tags table (ids = " + tagIds.stream().
-					map(String::valueOf)
-					.collect(Collectors.joining(",")) + ")", ex);
-		}
-		
-		transaction.registerDeletedBlackboardArtifactTags(tags);
-	}
 
 	/**
 	 * Object to store the tag change from a call to addArtifactTag.
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java b/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
index da6842036cf6546fc219e4eea2c2a24587b9ff7c..723a7bfff7b4c8ddd686077fa5a25e7ecb35506a 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TskEvent.java
@@ -22,7 +22,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 
 /**
@@ -93,7 +92,7 @@ public final static class AggregateScoresChangedEvent extends TskObjectsEvent<Sc
 			super(scoreChanges.asList());
 			this.dataSourceObjectId = dataSourceObjectId;
 			scoreChanges.stream().forEach(chg -> {
-				if (!Objects.equals(chg.getDataSourceObjectId(), dataSourceObjectId)) {
+				if (!chg.getDataSourceObjectId().equals(dataSourceObjectId)) {
 					throw new IllegalArgumentException("All data source object IDs in List<ScoreChange> must match dataSourceObjectId");
 				}
 			});
@@ -670,105 +669,4 @@ public List<Long> getTagSetIds() {
 			return getDataModelObjects();
 		}
 	}
-
-	/**
-	 * Class for BlackboardArtifactTag (Result Tag) events.
-	 */
-	static final class BlackboardArtifactTagsAddedTskEvent extends TskObjectsEvent<BlackboardArtifactTag> {
-
-		/**
-		 * Constructs the BlackboardArtifactTag event.
-		 *
-		 * @param tags The BlackboardArtifactTag that are the subjects of the
-		 *             event.
-		 */
-		BlackboardArtifactTagsAddedTskEvent(List<BlackboardArtifactTag> tags) {
-			super(tags);
-		}
-
-		/**
-		 * Gets the tags.
-		 *
-		 * @return The tags.
-		 */
-		public List<BlackboardArtifactTag> getTags() {
-			return getDataModelObjects();
-		}
-
-	}
-
-	/**
-	 * An event published when one or more BlackboardArtifactTag (Result Tag)
-	 * have been deleted.
-	 */
-	public final static class BlackboardArtifactTagsDeletedTskEvent extends TskObjectsEvent<BlackboardArtifactTag> {
-
-		/**
-		 * Constructs a deleted event for one or more BlackboardArtifactTag.
-		 *
-		 * @param tags The ids of the deleted tags.
-		 */
-		public BlackboardArtifactTagsDeletedTskEvent(List<BlackboardArtifactTag> tags) {
-			super(tags);
-		}
-
-		/**
-		 * Returns the list of deleted TagSet ids.
-		 *
-		 * @return The list of deleted tag ids.
-		 */
-		public List<BlackboardArtifactTag> getTags() {
-			return getDataModelObjects();
-		}
-	}
-
-	/**
-	 * Class for BlackboardArtifactTag (File Tag) events.
-	 */
-	static class ContentTagsAddedTskEvent extends TskObjectsEvent<ContentTag> {
-
-		/**
-		 * Constructs the ContentTag event.
-		 *
-		 * @param tags The ContentTag that are the subjects of the event.
-		 */
-		ContentTagsAddedTskEvent(List<ContentTag> tags) {
-			super(tags);
-		}
-
-		/**
-		 * Gets the tags.
-		 *
-		 * @return The tags.
-		 */
-		public List<ContentTag> getTags() {
-			return getDataModelObjects();
-		}
-
-	}
-
-	/**
-	 * An event published when one or more ContentTags (File Tag) have been
-	 * deleted.
-	 */
-	public final static class ContentTagsDeletedTskEvent extends TskObjectsEvent<ContentTag> {
-
-		/**
-		 * Constructs a deleted event for one or more ContentTag.
-		 *
-		 * @param tags The deleted tags.
-		 */
-		public ContentTagsDeletedTskEvent(List<ContentTag> tags) {
-			super(tags);
-		}
-
-		/**
-		 * Returns the list of deleted ContentTags.
-		 *
-		 * @return The list of deleted tag ids.
-		 */
-		public List<ContentTag> getTags() {
-			return getDataModelObjects();
-		}
-	}
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/UnsupportedContent.java b/bindings/java/src/org/sleuthkit/datamodel/UnsupportedContent.java
index 120a4988c75a89cf8d11476501c3338585c40492..d9bbca1bdd7b59ada02ad29b51d762789b4ceedd 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/UnsupportedContent.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/UnsupportedContent.java
@@ -55,4 +55,9 @@ public long getSize() {
 	public <T> T accept(ContentVisitor<T> v) {
 		return v.visit(this);
 	}
+
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java b/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java
index a349e623ab4d8e8581856655f2d224a25bd9444e..df34ec23bca9df59956859758828afc36c3b9b56 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java
@@ -120,6 +120,20 @@ public <T> T accept(ContentVisitor<T> visitor) {
 		return visitor.visit(this);
 	}
 
+	/**
+	 * Accepts a Sleuthkit item visitor (Visitor design pattern).
+	 *
+	 * @param <T>     The type returned by the visitor.
+	 * @param visitor A SleuthkitItemVisitor supplying an algorithm to run using
+	 *                this virtual directory as input.
+	 *
+	 * @return The output of the algorithm.
+	 */
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
 	/**
 	 * Provides a string representation of this virtual directory.
 	 *
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Volume.java b/bindings/java/src/org/sleuthkit/datamodel/Volume.java
index 2b10c357ad7571f768328e2e2a48eb3522f440c0..48df129195736a44c39f573bf76cf93cadcaf796 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Volume.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Volume.java
@@ -256,6 +256,11 @@ public static String vsFlagToString(long vsFlag) {
 		return result;
 	}
 
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
+
 	@Override
 	public <T> T accept(ContentVisitor<T> v) {
 		return v.visit(this);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java b/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java
index 00154ab49f3c79e554dca36e6da366bb89f30fab..acb315595c333cce4947c39bfcf56e6e05e28913 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java
@@ -132,6 +132,11 @@ public void finalize() throws Throwable {
 			super.finalize();
 		}
 	}
+	
+	@Override
+	public <T> T accept(SleuthkitItemVisitor<T> v) {
+		return v.visit(this);
+	}
 
 	@Override
 	public <T> T accept(ContentVisitor<T> v) {