diff --git a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
index f305846b33c68e2da51994200c1289aae448ba61..932f0890ab520bca2ce337b51678d719c9dd1525 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java
@@ -1835,61 +1835,62 @@ public List<BlackboardArtifact> getExactMatchKeywordSearchResults(String keyword
 	 *                          database query to obtain the keyword hits.
 	 */
 	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
+				? ""
+				: " AND artifacts.data_source_obj_id = ? "; // dataSourceId
 
 		String kwsListClause = (kwsListName == null || kwsListName.isEmpty()
-				? " AND set_name IS NULL "
-				: " AND set_name = ? ");
+				? " WHERE r.set_name IS NULL "
+				: " WHERE r.set_name = ? ");
 
 		String keywordClause = (keyword == null || keyword.isEmpty()
-			? ""
-			: " AND keyword = ? ");
+				? ""
+				: " AND r.keyword = ? ");
 
 		String searchTypeClause = (searchType == null
 				? ""
-				: " AND search_type = ? ");		
-		
+				: " AND r.search_type = ? ");
+
 		String regexClause = (regex == null || regex.isEmpty()
-			? ""
-			: " AND regexp_str = ? ");
-		
-		String query =  "SELECT DISTINCT artifacts.artifact_id AS artifact_id, "
-        + " 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," 
-        + " artifacts.review_status_id AS review_status_id, "
-        + " results.conclusion AS conclusion, "
-        + " results.significance AS significance, "
-        + " results.priority AS priority, "
-        + " results.configuration AS configuration, "
-        + " results.justification AS justification, "
-        + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
-                + BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " LIMIT 1) AS set_name, "
-        + " (SELECT value_int32 FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = " 
+				? ""
+				: " AND r.regexp_str = ? ");
+
+		String query = "SELECT r.* FROM ( "
+				+ " SELECT DISTINCT artifacts.artifact_id AS artifact_id, "
+				+ " 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,"
+				+ " artifacts.review_status_id AS review_status_id, "
+				+ " results.conclusion AS conclusion, "
+				+ " results.significance AS significance, "
+				+ " results.priority AS priority, "
+				+ " results.configuration AS configuration, "
+				+ " results.justification AS justification, "
+				+ " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
+				+ BlackboardAttribute.Type.TSK_SET_NAME.getTypeID() + " LIMIT 1) AS set_name, "
+				+ " (SELECT value_int32 FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
 				+ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " LIMIT 1) AS search_type, "
-        + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
-                + BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " LIMIT 1) AS regexp_str, "
-        + " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
-                + BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " LIMIT 1) AS keyword "
-        + " 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 = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " "
-		+ dataSourceClause
-		+ searchTypeClause
-		+ kwsListClause
-		+ keywordClause
-		+ regexClause;
+				+ " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
+				+ BlackboardAttribute.Type.TSK_KEYWORD_REGEXP.getTypeID() + " LIMIT 1) AS regexp_str, "
+				+ " (SELECT value_text FROM blackboard_attributes attr WHERE attr.artifact_id = artifacts.artifact_id AND attr.attribute_type_id = "
+				+ BlackboardAttribute.Type.TSK_KEYWORD.getTypeID() + " LIMIT 1) AS keyword "
+				+ " 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 = " + BlackboardArtifact.Type.TSK_KEYWORD_HIT.getTypeID() + " "
+				+ dataSourceClause + " ) r "
+				+ kwsListClause
+				+ keywordClause
+				+ searchTypeClause
+				+ regexClause;
 
 		List<BlackboardArtifact> artifacts = new ArrayList<>();
 		caseDb.acquireSingleUserCaseReadLock();
@@ -1902,11 +1903,7 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 				if (dataSourceId != null) {
 					preparedStatement.setLong(++paramIdx, dataSourceId);
 				}
-				
-				if (searchType != null) {
-					preparedStatement.setInt(++paramIdx, searchType.getType());
-				}
-				
+								
 				if (!(kwsListName == null || kwsListName.isEmpty())) {
 					preparedStatement.setString(++paramIdx, kwsListName);
 				}
@@ -1915,6 +1912,10 @@ public List<BlackboardArtifact> getKeywordSearchResults(String keyword, String r
 					preparedStatement.setString(++paramIdx, keyword);
 				}
 
+				if (searchType != null) {
+					preparedStatement.setInt(++paramIdx, searchType.getType());
+				}
+
 				if (!(regex == null || regex.isEmpty())) {
 					preparedStatement.setString(++paramIdx, regex);
 				}
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/CaseDbAccessManager.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDbAccessManager.java
index c009c77b00224cc11fd3ec8ed151d54583555ec7..8e417fdf39c81a99bf83d0cc593153db0626925a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CaseDbAccessManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDbAccessManager.java
@@ -629,7 +629,7 @@ public void select(final String sql, final CaseDbAccessQueryCallback queryCallba
 	public CaseDbPreparedStatement prepareSelect(String sql) throws TskCoreException {
 		String selectSQL = "SELECT " + sql; // NON-NLS
 		try {
-			return new CaseDbPreparedStatement(selectSQL, false);
+			return new CaseDbPreparedStatement(StatementType.SELECT, selectSQL, false);
 		} catch (SQLException ex) {
 			throw new TskCoreException("Error creating select prepared statement for query:\n" + selectSQL, ex);
 		}
@@ -645,12 +645,70 @@ public CaseDbPreparedStatement prepareSelect(String sql) throws TskCoreException
 	 */
 	@Beta
 	public void select(CaseDbPreparedStatement preparedStatement, CaseDbAccessQueryCallback queryCallback) throws TskCoreException {
+		if (!preparedStatement.getType().equals(StatementType.SELECT)) {
+			throw new TskCoreException("CaseDbPreparedStatement has incorrect type for select operation");
+		}
+		
 		try (ResultSet resultSet = preparedStatement.getStatement().executeQuery()) {
 			queryCallback.process(resultSet);
 		} catch (SQLException ex) {
 			throw new TskCoreException(MessageFormat.format("Error running SELECT query:\n{0}", preparedStatement.getOriginalSql()), ex);
 		}
 	}
+	
+	/**
+	 * Creates a prepared statement object for the purposes of running an insert
+	 * statement. The given SQL should not include the starting "INSERT INTO" 
+	 * or the name of the table.
+	 * 
+	 * For PostGreSQL, the caller must include the ON CONFLICT DO NOTHING clause
+	 *
+	 * @param tableName The name of the table being updated.
+	 * @param sql       The insert statement without the starting "INSERT INTO (table name)" part.
+	 * @param trans     The open transaction.
+	 *
+	 * @return The prepared statement object.
+	 *
+	 * @throws TskCoreException
+	 */
+	@Beta
+	public CaseDbPreparedStatement prepareInsert(String tableName, String sql, CaseDbTransaction trans) throws TskCoreException {
+		validateTableName(tableName);
+		validateSQL(sql);
+		
+		String insertSQL = "INSERT";
+		if (DbType.SQLITE == tskDB.getDatabaseType()) {
+			insertSQL += " OR IGNORE";
+		}
+		insertSQL = insertSQL + " INTO " + tableName + " " + sql; // NON-NLS
+	
+		try {
+			return new CaseDbPreparedStatement(StatementType.INSERT, insertSQL, trans);
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error creating insert prepared statement for query:\n" + insertSQL, ex);
+		}
+	}
+	
+	/**
+	 * Performs a insert statement query with the given case prepared statement.
+	 *
+	 * @param preparedStatement The case prepared statement.
+	 * 
+	 * @throws TskCoreException
+	 */
+	@Beta
+	public void insert(CaseDbPreparedStatement preparedStatement) throws TskCoreException {
+		
+		if (!preparedStatement.getType().equals(StatementType.INSERT)) {
+			throw new TskCoreException("CaseDbPreparedStatement has incorrect type for insert operation");
+		}
+		
+		try {
+			preparedStatement.getStatement().executeUpdate();
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error inserting row in table " + "" + " with sql = "+ "", ex);
+		}
+	}
 
 	/**
 	 * Deletes a row in the specified table.
@@ -726,6 +784,22 @@ private void validateSQL(String sql) throws TskCoreException {
 		 */
 	}
 	
+	/**
+	 * Enum to track which type of lock the CaseDbPreparedStatement holds.
+	 */
+	private enum LockType {
+		READ,
+		WRITE,
+		NONE;
+	}
+	
+	/**
+	 * Enum to track which type of statement the CaseDbPreparedStatement holds.
+	 */
+	private enum StatementType {
+		SELECT,
+		INSERT;
+	}
 	
 	/**
 	 * A wrapper around a PreparedStatement to execute queries against the
@@ -737,10 +811,12 @@ public class CaseDbPreparedStatement implements AutoCloseable {
 		private final CaseDbConnection connection;
 		private final PreparedStatement preparedStatement;
 		private final String originalSql;
-		private final boolean hasWriteLock;
-
+		private final LockType lockType;
+		private final StatementType type;
+		
 		/**
-		 * Main constructor.
+		 * Construct a prepared statement. This should not be used if a transaction
+		 * is already open.
 		 *
 		 * NOTE: Creating the CaseDbPreparedStatement opens a connection and
 		 * acquires a read lock on the case database. For this reason, it is
@@ -750,6 +826,7 @@ public class CaseDbPreparedStatement implements AutoCloseable {
 		 * the database should be avoided while the prepared statement is open
 		 * to prevent possible deadlocks.
 		 *
+		 * @param type                The type of statement.
 		 * @param query               The query string.
 		 * @param isWriteLockRequired Whether or not a write lock is required.
 		 *                            If a write lock is not required, just a
@@ -758,18 +835,36 @@ public class CaseDbPreparedStatement implements AutoCloseable {
 		 * @throws SQLException
 		 * @throws TskCoreException
 		 */
-		private CaseDbPreparedStatement(String query, boolean isWriteLockRequired) throws SQLException, TskCoreException {		
+		private CaseDbPreparedStatement(StatementType type, String query, boolean isWriteLockRequired) throws SQLException, TskCoreException {		
 			if (isWriteLockRequired) {
-				CaseDbAccessManager.this.tskDB.acquireSingleUserCaseWriteLock();	
+				CaseDbAccessManager.this.tskDB.acquireSingleUserCaseWriteLock();
+				this.lockType = LockType.WRITE;
 			} else {
-				CaseDbAccessManager.this.tskDB.acquireSingleUserCaseReadLock();	
+				CaseDbAccessManager.this.tskDB.acquireSingleUserCaseReadLock();
+				this.lockType = LockType.READ;
 			}
-			
-			this.hasWriteLock = isWriteLockRequired;
 			this.connection = tskDB.getConnection();
 			this.preparedStatement = connection.getPreparedStatement(query, Statement.NO_GENERATED_KEYS);
 			this.originalSql = query;
-			
+			this.type = type;
+		}
+		
+		/**
+		 * Construct a prepared statement using an already open transaction.
+		 *
+		 * @param type                The type of statement.
+		 * @param query               The query string.
+		 * @param trans               The open transaction.
+		 *
+		 * @throws SQLException
+		 * @throws TskCoreException
+		 */
+		private CaseDbPreparedStatement(StatementType type, String query, CaseDbTransaction trans) throws SQLException, TskCoreException {		
+			this.lockType = LockType.NONE;
+			this.connection = trans.getConnection();
+			this.preparedStatement = connection.getPreparedStatement(query, Statement.NO_GENERATED_KEYS);
+			this.originalSql = query;
+			this.type = type;
 		}
 
 		/**
@@ -780,6 +875,15 @@ private CaseDbPreparedStatement(String query, boolean isWriteLockRequired) throw
 		private PreparedStatement getStatement() {
 			return preparedStatement;
 		}
+		
+		/**
+		 * Get the type of statement.
+		 * 
+		 * @return The statement type (select or insert).
+		 */
+		private StatementType getType() {
+			return type;
+		}
 
 		/**
 		 * Returns the original sql query.
@@ -967,11 +1071,15 @@ public void setObject(int parameterIndex, Object x) throws TskCoreException {
 
 		@Override
 		public void close() throws SQLException {
-
-			preparedStatement.close();
+			
+			// Don't close the statement/connection or release a lock if we were supplied a transaction.
+			// Everything will be handled when the transaction is closed.
+			if (lockType.equals(LockType.NONE)) {
+				return;
+			}
+			
 			connection.close();
-
-			if (hasWriteLock) {
+			if (lockType.equals(LockType.WRITE)) {
 				CaseDbAccessManager.this.tskDB.releaseSingleUserCaseWriteLock();
 			} else {
 				CaseDbAccessManager.this.tskDB.releaseSingleUserCaseReadLock();
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 c06dab73054d4c161d5816d1f692d30e3675ab5e..274100654d9bd2a3787e447f96822a6def9f8172 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java
@@ -150,6 +150,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 ffebd6b2ce59d7859936b889f6bad51f45d016bb..f0d376b381093971ca36fde7624365117502bda0 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Directory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Directory.java
@@ -94,6 +94,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, knownState, parentPath, null, null, ownerUid, osAccountObjId, 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 f3da36861ea3eaf75bfced09b56fb68d50238d4c..659ed70acdee84c323b760f6a1a7f7915be6196b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/File.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/File.java
@@ -102,6 +102,19 @@ public class File 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, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, 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 25ce946a4ae0ccacd1a0a4703b98115beadcddac..d20f8b7df1b3dc5c8a62bef7677201a5badfca50 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/FileSystem.java
@@ -207,6 +207,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 670449a00b34979270bc8f34b58b25782b395b04..7e2d793a29e1749b0d4b4aa192334d73fa4def6b 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Image.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Image.java
@@ -275,6 +275,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 39a4709c42457df76017af04db46ccea745c5256..311dc8318c13ede3efd642e1a5ba462b760a35f1 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java
@@ -230,6 +230,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 1183899ead5d5c68e832dd2fc54a54f53277052e..2b90de8309f22a8eb2bb955eed25fcad127cdb94 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalDirectory.java
@@ -109,6 +109,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 405e2e19959e0acd2c43b15e2eff8a34779f37d7..1e5dcf6bfebe504e85e335473b01821de1e1c99d 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java
@@ -146,6 +146,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 96a99de8d63c403cc464c95d4bde2c4f92c82c35..871d292c908370887f1e28fd124a4beebfc03b9a 100755
--- a/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/LocalFilesDataSource.java
@@ -333,6 +333,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 feb54bfc48ae340f088e78bd70f665325531db41..d78434f4c3e7efcc660b4b4df9ca32d33b6c770a 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java
@@ -399,6 +399,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 436d58a215db637f435a5aaf0e04271a5a54d8e4..2a6e712e73b78ab596db12640e3b52d01e6f2c2e 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Pool.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Pool.java
@@ -133,6 +133,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 089363d4acfac0999b1cc9cc1ca0b3a20d28c4e6..3b6928cfa7a6f8338fceca799502a1d28efce196 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Report.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Report.java
@@ -403,4 +403,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/SlackFile.java b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
index e0350d5a91f17856519c4b76134f6d61cd67d327..8cf9407e5c3209787c76fbf27684b59824041c33 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java
@@ -124,6 +124,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 16d0cfc4562ba31f2eba51fcbcf4093078e8bfab..5e14f322bb27afb25740363badf0eecda9738934 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -13228,7 +13228,7 @@ PreparedStatement getPreparedStatement(PREPARED_STATEMENT statementKey, int gene
 		PreparedStatement getPreparedStatement(String sqlStatement, int generateKeys) throws SQLException {
 			PreparedStatement statement;
 			String statementKey = "SQL:" + sqlStatement + " Key:" + generateKeys;
-			if (adHocPreparedStatements.containsKey(statementKey)) {
+			if (adHocPreparedStatements.containsKey(statementKey) && !adHocPreparedStatements.get(statementKey).isClosed()) {
 				statement = this.adHocPreparedStatements.get(statementKey);
 			} else {
 				statement = prepareStatement(sqlStatement, generateKeys);
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/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 4c22cc7288557c4da8f49331160b02f81cb10747..7d2c4519e411cc90be76db10743113d220046501 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/VirtualDirectory.java
@@ -117,6 +117,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 6d07dd9f433a9089c38cf280959de24ee815dd78..f63557cda0ff82367d2a5a5cc83984def62236e1 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Volume.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Volume.java
@@ -255,6 +255,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 7b1f74d7dd606b157f6052106a65408a00804d25..e403576142eb9d389bcf627e1395b82cdf9b396f 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/VolumeSystem.java
@@ -131,6 +131,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) {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8921e125815af1e56bc84a92a3a4c3653996a379..b0590939feed42af837222a5d96b4708b4ffb0f1 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -3,7 +3,7 @@ AM_CFLAGS += $(PTHREAD_CFLAGS)
 AM_CXXFLAGS += -Wno-unused-command-line-argument $(PTHREAD_CFLAGS)
 LDADD = ../tsk/libtsk.la
 LDFLAGS += -static $(PTHREAD_LIBS)
-EXTRA_DIST = .indent.pro runtests.sh
+EXTRA_DIST = .indent.pro runtests.sh test_libraries.sh
 
 check_SCRIPTS = runtests.sh test_libraries.sh
 
diff --git a/tsk/auto/auto_db.cpp b/tsk/auto/auto_db.cpp
index 6e4864702196ce3495a2876a93933a76fb41c204..bac4d7f88a3e8917c4de961eba6c431ad7d098c8 100755
--- a/tsk/auto/auto_db.cpp
+++ b/tsk/auto/auto_db.cpp
@@ -209,9 +209,9 @@ TskAutoDb::openImage(const char* a_deviceId)
 uint8_t
 TskAutoDb::addImageDetails(const char* deviceId)
 {
-   string md5 = "";
-   string sha1 = "";
-   string collectionDetails = "";
+   std::string md5 = "";
+   std::string sha1 = "";
+   std::string collectionDetails = "";
 #if HAVE_LIBEWF 
    if (m_img_info->itype == TSK_IMG_TYPE_EWF_EWF) {
      // @@@ This should really probably be inside of a tsk_img_ method
@@ -227,7 +227,7 @@ TskAutoDb::addImageDetails(const char* deviceId)
    }
 #endif
 
-    string devId;
+    std::string devId;
     if (NULL != deviceId) {
         devId = deviceId; 
     } else {
@@ -824,7 +824,7 @@ TskAutoDb::commitAddImage()
  * Set the current image's timezone
  */
 void
-TskAutoDb::setTz(string tzone)
+TskAutoDb::setTz(std::string tzone)
 {
     m_curImgTZone = tzone;
 }
@@ -849,7 +849,7 @@ TskAutoDb::processFile(TSK_FS_FILE * fs_file, const char *path)
     if (isDir(fs_file)) {
         m_curDirAddr = fs_file->name->meta_addr;
         tsk_take_lock(&m_curDirPathLock);
-        m_curDirPath = string(path) + fs_file->name->name;
+        m_curDirPath = std::string(path) + fs_file->name->name;
         tsk_release_lock(&m_curDirPathLock);
     }
     else if (m_curDirAddr != fs_file->name->par_addr) {
@@ -1406,7 +1406,7 @@ TSK_RETVAL_ENUM TskAutoDb::addUnallocBlockFileInChunks(uint64_t byteStart, TSK_O
 * @returns curDirPath string representing currently analyzed directory
 */
 const std::string TskAutoDb::getCurDir() {
-    string curDirPath;
+    std::string curDirPath;
     tsk_take_lock(&m_curDirPathLock);
     curDirPath = m_curDirPath;
     tsk_release_lock(&m_curDirPathLock);
diff --git a/tsk/auto/tsk_case_db.h b/tsk/auto/tsk_case_db.h
index 4b29e156e68360371d212f111be9b21eb22e8b3a..18d98fc46f01a7a1281f69b26f6dc76e939933ab 100644
--- a/tsk/auto/tsk_case_db.h
+++ b/tsk/auto/tsk_case_db.h
@@ -40,7 +40,7 @@ class TskAutoDb:public TskAuto {
     virtual uint8_t openImageUtf8(int, const char *const images[],
         TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL);
     virtual void closeImage();
-    virtual void setTz(string tzone);
+    virtual void setTz(std::string tzone);
 
     virtual TSK_FILTER_ENUM filterVs(const TSK_VS_INFO * vs_info);
     virtual TSK_FILTER_ENUM filterVol(const TSK_VS_PART_INFO * vs_part);
@@ -133,9 +133,9 @@ class TskAutoDb:public TskAuto {
     int64_t m_curFileId;    ///< Object ID of file currently being processed
     TSK_INUM_T m_curDirAddr;		///< Meta address the directory currently being processed
     int64_t m_curUnallocDirId;	
-    string m_curDirPath;		//< Path of the current directory being processed
+    std::string m_curDirPath;		//< Path of the current directory being processed
     tsk_lock_t m_curDirPathLock; //< protects concurrent access to m_curDirPath
-    string m_curImgTZone;
+    std::string m_curImgTZone;
     bool m_blkMapFlag;
     bool m_fileHashFlag;
     bool m_vsFound;
diff --git a/tsk/fs/fs_dir.c b/tsk/fs/fs_dir.c
index 873994712636d436572cd780dc27739fef5a8f24..1739792420af849797d97cb8c312cbced5f24b91 100644
--- a/tsk/fs/fs_dir.c
+++ b/tsk/fs/fs_dir.c
@@ -1401,6 +1401,12 @@ tsk_fs_dir_find_orphans(TSK_FS_INFO * a_fs, TSK_FS_DIR * a_fs_dir)
     for (i = 0; i < a_fs_dir->names_used; i++) {
         if (tsk_list_find(data.orphan_subdir_list,
                 a_fs_dir->names[i].meta_addr)) {
+
+            // Unclear what should happen in this situation, but it can happen,
+            // So skipping over this situation for now.
+            if (a_fs_dir->names_used == i + 1) {
+                continue;
+            }
             if (a_fs_dir->names_used > 1) {
                 tsk_fs_name_copy(&a_fs_dir->names[i],
                     &a_fs_dir->names[a_fs_dir->names_used - 1]);
diff --git a/tsk/fs/hfs_dent.c b/tsk/fs/hfs_dent.c
index 8399ebeb8b66a1f99960cad97e643d4ade0d2867..9a69dd7138bb0c0d87844403419692f3755357f3 100644
--- a/tsk/fs/hfs_dent.c
+++ b/tsk/fs/hfs_dent.c
@@ -258,6 +258,11 @@ hfs_dir_open_meta_cb(HFS_INFO * hfs, int8_t level_type,
 
         /* This will link the folder to its parent, which is the ".." entry */
         else if (rec_type == HFS_FOLDER_THREAD) {
+            if ((nodesize < sizeof(hfs_thread)) || (rec_off2 > nodesize - sizeof(hfs_thread))) {
+                tsk_error_set_errno(TSK_ERR_FS_GENFS);
+                tsk_error_set_errstr("hfs_dir_open_meta: nodesize value out of bounds");
+                return HFS_BTREE_CB_ERR;
+            }
             hfs_thread *thread = (hfs_thread *) & rec_buf[rec_off2];
             strcpy(info->fs_name->name, "..");
             info->fs_name->meta_addr =
diff --git a/tsk/fs/iso9660.c b/tsk/fs/iso9660.c
index 6fa2c2c1e1373ac8ad67e345f5e2b5c190181690..64a142c3f8842113cab0d3f5036dbe4bb8aae7fe 100755
--- a/tsk/fs/iso9660.c
+++ b/tsk/fs/iso9660.c
@@ -641,14 +641,13 @@ iso9660_load_inodes_dir(TSK_FS_INFO * fs, TSK_OFF_T a_offs, int count,
                     file_ver = NULL;
                 }
 
-                // if no extension, remove the final '.'
                 size_t name8_len = strnlen(in_node->inode.fn, ISO9660_MAXNAMLEN);
                 if (name8_len > 0 && in_node->inode.fn[name8_len - 1] == '.') {
+                    // if no extension, remove the final '.'
                     in_node->inode.fn[name8_len - 1] = '\0';
+                    name8_len -= 1;
                 }
-                
-                
-                if (strlen(in_node->inode.fn) == 0) {
+                if (name8_len == 0) {
                     if (tsk_verbose)
                         tsk_fprintf(stderr,
                                     "iso9660_load_inodes_dir: length of name after processing is 0. bailing\n");
diff --git a/tsk/fs/ntfs.c b/tsk/fs/ntfs.c
index 236b0c559830c2725dadae2495cc03dd2d077488..f87712c6c2ebf1546a02dbedcd4a07cfd3861c61 100644
--- a/tsk/fs/ntfs.c
+++ b/tsk/fs/ntfs.c
@@ -574,6 +574,7 @@ is_clustalloc(NTFS_INFO * ntfs, TSK_DADDR_T addr)
  * @param ntfs File system that attribute is located in.
  * @param start_vcn The starting VCN for this run.
  * @param runlist The raw runlist data from the MFT entry.
+ * @param runlist_size The size of the raw runlist data from the MFT entry.
  * @param a_data_run_head [out] Pointer to pointer of run that is created. (NULL on error and for $BadClust - special case because it is a sparse file for the entire FS).
  * @param totlen [out] Pointer to location where total length of run (in bytes) can be returned (or NULL)
  * @param mnum MFT entry address
@@ -582,7 +583,7 @@ is_clustalloc(NTFS_INFO * ntfs, TSK_DADDR_T addr)
  */
 static TSK_RETVAL_ENUM
 ntfs_make_data_run(NTFS_INFO * ntfs, TSK_OFF_T start_vcn,
-    ntfs_runlist * runlist_head, TSK_FS_ATTR_RUN ** a_data_run_head,
+    ntfs_runlist * runlist_head, uint32_t runlist_size, TSK_FS_ATTR_RUN ** a_data_run_head,
     TSK_OFF_T * totlen, TSK_INUM_T mnum)
 {
     TSK_FS_INFO *fs = (TSK_FS_INFO *) ntfs;
@@ -591,6 +592,7 @@ ntfs_make_data_run(NTFS_INFO * ntfs, TSK_OFF_T start_vcn,
     unsigned int i, idx;
     TSK_DADDR_T prev_addr = 0;
     TSK_OFF_T file_offset = start_vcn;
+    uint32_t runlist_offset = 0;
 
     run = runlist_head;
     *a_data_run_head = NULL;
@@ -599,11 +601,15 @@ ntfs_make_data_run(NTFS_INFO * ntfs, TSK_OFF_T start_vcn,
     if (totlen)
         *totlen = 0;
 
+    if (runlist_size < 1) {
+        return TSK_ERR;
+    }
+
     /* Cycle through each run in the runlist
      * We go until we find an entry with no length
      * An entry with offset of 0 is for a sparse run
      */
-    while (NTFS_RUNL_LENSZ(run) != 0) {
+    while ((runlist_offset < runlist_size) && NTFS_RUNL_LENSZ(run) != 0) {
         int64_t addr_offset = 0;
 
         /* allocate a new tsk_fs_attr_run */
@@ -630,7 +636,7 @@ ntfs_make_data_run(NTFS_INFO * ntfs, TSK_OFF_T start_vcn,
          * A length of more than eight bytes will not fit in the
          * 64-bit length field (and is likely corrupt)
          */
-        if (NTFS_RUNL_LENSZ(run) > 8) {
+        if (NTFS_RUNL_LENSZ(run) > 8 || NTFS_RUNL_LENSZ(run) > runlist_size - runlist_offset - 1) {
             tsk_error_reset();
             tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
             tsk_error_set_errstr
@@ -758,8 +764,14 @@ ntfs_make_data_run(NTFS_INFO * ntfs, TSK_OFF_T start_vcn,
         }
 
         /* Advance run */
-        run = (ntfs_runlist *) ((uintptr_t) run + (1 + NTFS_RUNL_LENSZ(run)
-                + NTFS_RUNL_OFFSZ(run)));
+        uint32_t run_size = 1 + NTFS_RUNL_LENSZ(run) + NTFS_RUNL_OFFSZ(run);
+        run = (ntfs_runlist *) ((uintptr_t) run + run_size);
+
+        // Abritrary limit runlist_offset at INT32_MAX ((1 << 31) - 1)
+        if (run_size > (((uint32_t) 1UL << 31 ) -1) - runlist_offset) {
+            return TSK_ERR;
+        }
+        runlist_offset += run_size;
     }
 
     /* special case for $BADCLUST, which is a sparse file whose size is
@@ -1446,7 +1458,7 @@ ntfs_attr_walk_special(const TSK_FS_ATTR * fs_attr,
 
                         /* if we've passed the initialized size while reading this block, 
                          * zero out the buffer beyond the initialized size. */
-                        if (has_init_size) {
+                        if (has_init_size && (off < fs_attr->nrd.initsize)) {
                             const int64_t prev_remanining_init_size = fs_attr->nrd.initsize - off;
                             if (prev_remanining_init_size < (int64_t)comp.buf_size_b) {
                                 memset(&comp.uncomp_buf[prev_remanining_init_size], 0, comp.buf_size_b - prev_remanining_init_size);
@@ -2048,19 +2060,23 @@ ntfs_proc_attrseq(NTFS_INFO * ntfs,
                 return TSK_COR;
             }
 
+            uint32_t attr_len = tsk_getu32(fs->endian, attr->len);
+            uint64_t run_start_vcn = tsk_getu64(fs->endian, attr->c.nr.start_vcn);
+            uint16_t run_off = tsk_getu16(fs->endian, attr->c.nr.run_off);
+
             // sanity check
-            if (tsk_getu16(fs->endian, attr->c.nr.run_off) > tsk_getu32(fs->endian, attr->len)) {
+            if ((run_off < 48) || (run_off >= attr_len)) {
                 if (tsk_verbose)
-                    tsk_fprintf(stderr, "ntfs_proc_attrseq: run offset too big\n");
+                    tsk_fprintf(stderr, "ntfs_proc_attrseq: run offset out of bounds\n");
                 break;
             }
 
             /* convert the run to generic form */
             retval = ntfs_make_data_run(ntfs,
-                tsk_getu64(fs->endian, attr->c.nr.start_vcn),
-                (ntfs_runlist *) ((uintptr_t)
-                    attr + tsk_getu16(fs->endian,
-                        attr->c.nr.run_off)), &fs_attr_run, NULL,
+                run_start_vcn,
+                (ntfs_runlist *) ((uintptr_t) attr + run_off),
+                attr_len - run_off,
+                &fs_attr_run, NULL,
                 a_attrinum);
             if (retval != TSK_OK) {
                 tsk_error_errstr2_concat(" - proc_attrseq");
@@ -2343,10 +2359,6 @@ ntfs_proc_attrseq(NTFS_INFO * ntfs,
                     ("proc_attrseq: resident data of File Name Attribute is too small!");
                 return TSK_COR;
             }
-            TSK_FS_META_NAME_LIST *fs_name;
-            UTF16 *name16;
-            UTF8 *name8;
-
             ntfs_attr_fname *fname = (ntfs_attr_fname *) ((uintptr_t) attr + attr_off);
             if (fname->nspace == NTFS_FNAME_DOS) {
                 continue;
@@ -2374,6 +2386,7 @@ ntfs_proc_attrseq(NTFS_INFO * ntfs,
 
             fs_file->meta->time2.ntfs.fn_id = id;
 
+            TSK_FS_META_NAME_LIST *fs_name;
 
             /* Seek to the end of the fs_name structures in TSK_FS_META */
             if (fs_file->meta->name2) {
@@ -2400,9 +2413,16 @@ ntfs_proc_attrseq(NTFS_INFO * ntfs,
                 }
                 fs_name->next = NULL;
             }
+            if (fname->nlen > attr_len - 66) {
+                tsk_error_reset();
+                tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
+                tsk_error_set_errstr
+                    ("proc_attrseq: invalid name value size out of bounds!");
+                return TSK_COR;
+            }
+            UTF16 *name16 = (UTF16 *) & fname->name;
+            UTF8 *name8 = (UTF8 *) fs_name->name;
 
-            name16 = (UTF16 *) & fname->name;
-            name8 = (UTF8 *) fs_name->name;
             retVal =
                 tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
                 (UTF16 *) ((uintptr_t) name16 +
@@ -3243,23 +3263,26 @@ ntfs_load_bmap(NTFS_INFO * ntfs)
         tsk_getu16(fs->endian, mft->attr_off));
     data_attr = NULL;
 
+    uint32_t attr_len = 0;
+    uint32_t attr_type = 0;
+
     /* cycle through them */
     while ((uintptr_t) attr + sizeof (ntfs_attr) <=
             ((uintptr_t) mft + (uintptr_t) ntfs->mft_rsize_b)) {
 
-        if ((tsk_getu32(fs->endian, attr->len) == 0) ||
-            (tsk_getu32(fs->endian, attr->type) == 0xffffffff)) {
+        attr_len = tsk_getu32(fs->endian, attr->len);
+        attr_type = tsk_getu32(fs->endian, attr->type);
+
+        if ((attr_len == 0) || (attr_type == 0xffffffff)) {
             break;
         }
 
-        if (tsk_getu32(fs->endian, attr->type) == NTFS_ATYPE_DATA) {
+        if (attr_type == NTFS_ATYPE_DATA) {
             data_attr = attr;
             break;
         }
 
-        attr =
-            (ntfs_attr *) ((uintptr_t) attr + tsk_getu32(fs->endian,
-                attr->len));
+        attr = (ntfs_attr *) ((uintptr_t) attr + attr_len);
     }
 
     /* did we get it? */
@@ -3269,18 +3292,26 @@ ntfs_load_bmap(NTFS_INFO * ntfs)
         tsk_error_set_errstr("Error Finding Bitmap Data Attribute");
         goto on_error;
     }
-    uint32_t attr_len = tsk_getu32(fs->endian, data_attr->len);
+    attr_len = tsk_getu32(fs->endian, data_attr->len);
     if (attr_len > ntfs->mft_rsize_b) {
         goto on_error;
     }
 
-    /* convert to generic form */
+    uint64_t run_start_vcn = tsk_getu64(fs->endian, data_attr->c.nr.start_vcn);
+    uint16_t run_off = tsk_getu16(fs->endian, data_attr->c.nr.run_off);
+
+    if ((run_off < 48) || (run_off >= attr_len)) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
+        tsk_error_set_errstr("Invalid run_off of Bitmap Data Attribute - value out of bounds");
+        goto on_error;
+    }
+    /* convert data run to generic form */
     if ((ntfs_make_data_run(ntfs,
-                tsk_getu64(fs->endian, data_attr->c.nr.start_vcn),
-                (ntfs_runlist
-                    *) ((uintptr_t) data_attr + tsk_getu16(fs->endian,
-                        data_attr->c.nr.run_off)), &(ntfs->bmap),
-                NULL, NTFS_MFT_BMAP)) != TSK_OK) {
+                run_start_vcn,
+                (ntfs_runlist *) ((uintptr_t) data_attr + run_off),
+                attr_len - run_off,
+                &(ntfs->bmap), NULL, NTFS_MFT_BMAP)) != TSK_OK) {
         goto on_error;
     }
     ntfs->bmap_buf = (char *) tsk_malloc(fs->block_size);
diff --git a/tsk/fs/tsk_apfs.hpp b/tsk/fs/tsk_apfs.hpp
index fc0bd169580b7b76895d69784f34b2fa3c7d48b3..fbfb3da2bbfdf8bb8fe1732b249e7637678e402e 100755
--- a/tsk/fs/tsk_apfs.hpp
+++ b/tsk/fs/tsk_apfs.hpp
@@ -131,8 +131,11 @@ class APFSBtreeNodeIterator {
   }
 
   template <typename Void = void>
-  auto init_value()
+  auto init_value(int recursion_depth)
       -> std::enable_if_t<Node::is_variable_kv_node::value, Void> {
+    if ((recursion_depth < 0) || (recursion_depth > 64)) {
+      throw std::runtime_error("init_value exceeds recursion depth");
+    }
     if (this->_node->has_fixed_kv_size()) {
       throw std::runtime_error("btree does not have variable sized keys");
     }
@@ -150,12 +153,15 @@ class APFSBtreeNodeIterator {
       const auto block_num = *((apfs_block_num *)val_data);
 
       _child_it = std::make_unique<typename Node::iterator>(
-          own_node(_node.get(), block_num), 0);
+          own_node(_node.get(), block_num), 0, recursion_depth);
     }
   }
 
   template <typename Void = void>
-  auto init_value() -> std::enable_if_t<Node::is_fixed_kv_node::value, Void> {
+  auto init_value(int recursion_depth) -> std::enable_if_t<Node::is_fixed_kv_node::value, Void> {
+    if ((recursion_depth < 0) || (recursion_depth > 64)) {
+      throw std::runtime_error("init_value exceeds recursion depth");
+    }
     if (!this->_node->has_fixed_kv_size()) {
       throw std::runtime_error("btree does not have fixed sized keys");
     }
@@ -170,7 +176,7 @@ class APFSBtreeNodeIterator {
       const auto block_num = *((apfs_block_num *)val_data);
 
       _child_it = std::make_unique<typename Node::iterator>(
-          own_node(_node.get(), block_num), 0);
+          own_node(_node.get(), block_num), 0, recursion_depth);
     }
   }
 
@@ -178,12 +184,12 @@ class APFSBtreeNodeIterator {
   // Forward iterators must be DefaultConstructible
   APFSBtreeNodeIterator() = default;
 
-  APFSBtreeNodeIterator(const Node *node, uint32_t index);
+  APFSBtreeNodeIterator(const Node *node, uint32_t index, int recursion_depth);
 
-  APFSBtreeNodeIterator(lw_shared_ptr<Node> &&node, uint32_t index);
+  APFSBtreeNodeIterator(lw_shared_ptr<Node> &&node, uint32_t index, int recursion_depth);
 
   APFSBtreeNodeIterator(const Node *node, uint32_t index,
-                        typename Node::iterator &&child);
+                        typename Node::iterator &&child, int recursion_depth);
 
   virtual ~APFSBtreeNodeIterator() = default;
 
@@ -270,7 +276,7 @@ class APFSBtreeNodeIterator {
         auto index{_index};
 
         this->~APFSBtreeNodeIterator();
-        new (this) APFSBtreeNodeIterator(std::move(node), index);
+        new (this) APFSBtreeNodeIterator(std::move(node), index, 0);
       }
       return (*this);
     }
@@ -287,7 +293,7 @@ class APFSBtreeNodeIterator {
     auto index{_index};
 
     this->~APFSBtreeNodeIterator();
-    new (this) APFSBtreeNodeIterator(std::move(node), index);
+    new (this) APFSBtreeNodeIterator(std::move(node), index, 0);
 
     return (*this);
   }
@@ -484,8 +490,8 @@ class APFSBtreeNode : public APFSObject, public APFSOmap::node_tag {
  public:
   using iterator = APFSBtreeNodeIterator<APFSBtreeNode>;
 
-  iterator begin() const { return {this, 0}; }
-  iterator end() const { return {this, key_count()}; }
+  iterator begin() const { return {this, 0, 0}; }
+  iterator end() const { return {this, key_count(), 0}; }
 
   template <typename T, typename Compare>
   iterator find(const T &value, Compare comp) const {
@@ -505,7 +511,7 @@ class APFSBtreeNode : public APFSObject, public APFSOmap::node_tag {
 
         if (res == 0) {
           // We've found it!
-          return {this, i - 1};
+          return {this, i - 1, 0};
         }
 
         if (res < 0) {
@@ -526,14 +532,14 @@ class APFSBtreeNode : public APFSObject, public APFSOmap::node_tag {
       const auto &k = key(i - 1);
 
       if (comp(k, value) <= 0) {
-        iterator it{this, i - 1};
+        iterator it{this, i - 1, 0};
 
         auto ret = it._child_it->_node->find(value, comp);
         if (ret == it._child_it->_node->end()) {
           return end();
         }
 
-        return {this, i - 1, std::move(ret)};
+        return {this, i - 1, std::move(ret), 0};
       }
     }
 
@@ -580,8 +586,8 @@ class APFSJObjBtreeNode : public APFSBtreeNode<> {
 
   inline bool is_leaf() const noexcept { return (bn()->level == 0); }
 
-  inline iterator begin() const { return {this, 0}; }
-  inline iterator end() const { return {this, key_count()}; }
+  inline iterator begin() const { return {this, 0, 0}; }
+  inline iterator end() const { return {this, key_count(), 0}; }
 
   template <typename T, typename Compare>
   inline iterator find(const T &value, Compare comp) const {
@@ -595,7 +601,7 @@ class APFSJObjBtreeNode : public APFSBtreeNode<> {
 
         if (res == 0) {
           // We've found it!
-          return {this, i};
+          return {this, i, 0};
         }
 
         if (res > 0) {
@@ -627,11 +633,11 @@ class APFSJObjBtreeNode : public APFSBtreeNode<> {
       if (v == 0) {
         // We need to see if the jobj might be in the last node
         if (last != 0) {
-          iterator it{this, last - 1};
+          iterator it{this, last - 1, 0};
 
           auto ret = it._child_it->_node->find(value, comp);
           if (ret != it._child_it->_node->end()) {
-            return {this, last - 1, std::move(ret)};
+            return {this, last - 1, std::move(ret), 0};
           }
         }
 
@@ -644,14 +650,14 @@ class APFSJObjBtreeNode : public APFSBtreeNode<> {
       return end();
     }
 
-    iterator it{this, last};
+    iterator it{this, last, 0};
 
     auto ret = it._child_it->_node->find(value, comp);
     if (ret == it._child_it->_node->end()) {
       return end();
     }
 
-    return {this, last, std::move(ret)};
+    return {this, last, std::move(ret), 0};
   }
 
   template <typename T, typename Compare>
@@ -1135,7 +1141,7 @@ APFSBtreeNodeIterator<APFSJObjBtreeNode>::own_node(
 
 template <>
 template <>
-inline void APFSBtreeNodeIterator<APFSJObjBtreeNode>::init_value<void>() {
+inline void APFSBtreeNodeIterator<APFSJObjBtreeNode>::init_value<void>(int recursion_depth) {
   const auto &t = _node->_table_data.toc.variable[_index];
   const auto key_data = _node->_table_data.koff + t.key_offset;
   const auto val_data = _node->_table_data.voff - t.val_offset;
@@ -1156,37 +1162,37 @@ inline void APFSBtreeNodeIterator<APFSJObjBtreeNode>::init_value<void>() {
     }
 
     _child_it = std::make_unique<typename APFSJObjBtreeNode::iterator>(
-        own_node(_node.get(), it->value->paddr), 0);
+        own_node(_node.get(), it->value->paddr), 0, recursion_depth);
   }
 }
 
 template <typename Node>
 APFSBtreeNodeIterator<Node>::APFSBtreeNodeIterator(const Node *node,
-                                                   uint32_t index)
+                                                   uint32_t index, int recursion_depth)
     : _node{own_node(node)}, _index{index} {
   // If we're the end, then there's nothing to do
   if (index >= _node->key_count()) {
     return;
   }
 
-  init_value();
+  init_value(recursion_depth + 1);
 }
 
 template <typename Node>
 APFSBtreeNodeIterator<Node>::APFSBtreeNodeIterator(lw_shared_ptr<Node> &&node,
-                                                   uint32_t index)
+                                                   uint32_t index, int recursion_depth)
     : _node{std::forward<lw_shared_ptr<Node>>(node)}, _index{index} {
   // If we're the end, then there's nothing to do
   if (index >= _node->key_count()) {
     return;
   }
 
-  init_value();
+  init_value(recursion_depth + 1);
 }
 
 template <typename Node>
 APFSBtreeNodeIterator<Node>::APFSBtreeNodeIterator(
-    const Node *node, uint32_t index, typename Node::iterator &&child)
+    const Node *node, uint32_t index, typename Node::iterator &&child, int recursion_depth)
     : _node{own_node(node)}, _index{index} {
   _child_it = std::make_unique<typename Node::iterator>(
       std::forward<typename Node::iterator>(child));