From cb9c162ea440a27dfb0ee4689fc80592fa042610 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 17 May 2016 16:34:59 -0400 Subject: [PATCH 1/2] drive ContentViewer from List view. --- .../timeline/TimeLineTopComponent.java | 25 +++++++++- .../timeline/explorernodes/EventNode.java | 50 +++++++++++++++++-- .../timeline/explorernodes/EventRootNode.java | 33 +----------- .../timeline/ui/TimeLineResultView.java | 8 +-- .../timeline/ui/listvew/ListChart.java | 10 ++++ .../timeline/ui/listvew/ListViewPane.java | 6 +++ 6 files changed, 88 insertions(+), 44 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index aca7219998..8470175a0c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,13 @@ */ package org.sleuthkit.autopsy.timeline; +import com.google.common.collect.Iterables; import java.awt.BorderLayout; import java.util.Collections; import java.util.List; import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.SplitPane; @@ -46,6 +49,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; +import org.sleuthkit.autopsy.timeline.explorernodes.EventNode; import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar; import org.sleuthkit.autopsy.timeline.ui.StatusBar; import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView; @@ -56,7 +60,7 @@ import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; /** - * TopComponent for the timeline feature. + * TopComponent for the Timeline feature. */ @TopComponent.Description( preferredID = "TimeLineTopComponent", @@ -75,6 +79,21 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private final TimeLineController controller; + /** + * Listener that drives the ContentViewer when in List ViewMode. + */ + private final InvalidationListener selectionListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if (controller.getSelectedEventIDs().size() == 1) { + EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel()); + SwingUtilities.invokeLater(() -> dataContentPanel.setNode(eventNode)); + } else { + SwingUtilities.invokeLater(() -> dataContentPanel.setNode(null)); + } + } + }; + public TimeLineTopComponent(TimeLineController controller) { initComponents(); this.controller = controller; @@ -108,6 +127,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer lowerSplitXPane.add(contentViewerContainerPanel); } }); + controller.getSelectedEventIDs().removeListener(selectionListener); break; case LIST: /* @@ -119,6 +139,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer splitYPane.add(contentViewerContainerPanel); dataResultPanel.setNode(null); }); + controller.getSelectedEventIDs().addListener(selectionListener); break; default: throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index abb7245eda..9ea0b37e39 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,21 +31,25 @@ import org.openide.nodes.Children; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** - * * Explorer Node for {@link SingleEvent}s. + * * Explorer Node for SingleEvents. */ -class EventNode extends DisplayableItemNode { +public class EventNode extends DisplayableItemNode { private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName()); @@ -155,10 +159,48 @@ class EventNode extends DisplayableItemNode { } @Override - public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + final public void setValue(String t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { String oldValue = getValue(); value = t; firePropertyChange("time", oldValue, t); // NON-NLS } } + + /** + * Factory method to create an EventNode form the event ID and the events + * model. + * + * @param eventID The ID of the event this node is for. + * @param eventsModel The model that provides access to the events DB. + * + * @return An EventNode with the file (and artifact) backing this event in + * its lookup. + */ + public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) { + /* + * Look up the event by id and creata an EventNode with the appropriate + * data in the lookup. + */ + final SingleEvent eventById = eventsModel.getEventById(eventID); + try { + SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); + if (file != null) { + if (eventById.getArtifactID().isPresent()) { + BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); + return new EventNode(eventById, file, blackboardArtifact); + } else { + return new EventNode(eventById, file); + } + } else { + //This should never happen in normal operations + LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS + return null; + } + } catch (IllegalStateException | TskCoreException ex) { + //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. + LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS + return null; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 6fd3693514..7438a0f802 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -20,23 +20,16 @@ package org.sleuthkit.autopsy.timeline.explorernodes; import java.util.Collection; import java.util.List; -import java.util.logging.Level; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * Root Explorer node to represent events. @@ -132,31 +125,7 @@ public class EventRootNode extends DisplayableItemNode { */ return new TooManyNode(eventIDs.size()); } else { - /* - * look up the event by id and creata an EventNode with the - * appropriate data in the lookup. - */ - final SingleEvent eventById = filteredEvents.getEventById(eventID); - try { - SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); - AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); - if (file != null) { - if (eventById.getArtifactID().isPresent()) { - BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); - return new EventNode(eventById, file, blackboardArtifact); - } else { - return new EventNode(eventById, file); - } - } else { - //This should never happen in normal operations - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS - return null; - } - } catch (IllegalStateException | TskCoreException ex) { - //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS - return null; - } + return EventNode.createEventNode(eventID, filteredEvents); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java index f61e2b5b28..9fb308229f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java @@ -62,13 +62,9 @@ public class TimeLineResultView { dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent); //set up listeners on relevant properties - TimeLineController.getTimeZone().addListener((Observable observable) -> { - dataResultPanel.setPath(getSummaryString()); - }); + TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getSummaryString())); - controller.getSelectedEventIDs().addListener((Observable o) -> { - refresh(); - }); + controller.getSelectedEventIDs().addListener((Observable selectedIDs) -> refresh()); refresh(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java index 6f01fbbec0..e9b5f12d1a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java @@ -22,6 +22,7 @@ import java.util.Collection; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TableCell; @@ -128,6 +129,15 @@ class ListChart extends BorderPane { table.getItems().setAll(eventIDs); } + /** + * Get the List of IDs of events that are selected in this list. + * + * @return The List of IDs of events that are selected in this list. + */ + ObservableList getSelectedEventIDs() { + return table.getSelectionModel().getSelectedItems(); + } + private class ImageCell extends EventTableCell { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java index 6f3fdec60f..574de0fcfe 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.listvew; import java.util.List; import javafx.application.Platform; +import javafx.beans.Observable; import javafx.concurrent.Task; import javafx.scene.Parent; import org.joda.time.Interval; @@ -58,6 +59,11 @@ public class ListViewPane extends AbstractTimeLineView { //initialize chart; setCenter(listChart); setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable()); + + //keep controller's list of selected event IDs in sync with this list's + listChart.getSelectedEventIDs().addListener((Observable selectedIDs) -> { + controller.selectEventIDs(listChart.getSelectedEventIDs()); + }); } @Override From f7e7404ec4d9c06d832daf6cb307b059326d299c Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 18 May 2016 14:29:27 -0400 Subject: [PATCH 2/2] address review comments: fix typos, rename variables, catch exceptions higher up. --- .../timeline/TimeLineTopComponent.java | 38 ++++++++++++++----- .../timeline/explorernodes/EventNode.java | 32 ++++++---------- .../timeline/explorernodes/EventRootNode.java | 18 ++++++++- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index 8470175a0c..6ae2e1dcf2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -22,6 +22,7 @@ import com.google.common.collect.Iterables; import java.awt.BorderLayout; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -37,6 +38,7 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javax.swing.SwingUtilities; +import org.controlsfx.control.Notifications; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.NbBundle; @@ -58,6 +60,7 @@ import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree; import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; +import org.sleuthkit.datamodel.TskCoreException; /** * TopComponent for the Timeline feature. @@ -71,7 +74,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName()); - private final DataContentPanel dataContentPanel; + private final DataContentPanel contentViewerPanel; private final TimeLineResultView tlResultView; @@ -79,17 +82,32 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer private final TimeLineController controller; + @NbBundle.Messages({ + "TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."}) /** * Listener that drives the ContentViewer when in List ViewMode. */ - private final InvalidationListener selectionListener = new InvalidationListener() { + private final InvalidationListener selectedEventListener = new InvalidationListener() { @Override public void invalidated(Observable observable) { if (controller.getSelectedEventIDs().size() == 1) { - EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel()); - SwingUtilities.invokeLater(() -> dataContentPanel.setNode(eventNode)); + try { + EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel()); + SwingUtilities.invokeLater(() -> contentViewerPanel.setNode(eventNode)); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + Platform.runLater(() -> { + Notifications.create() + .owner(jFXVizPanel.getScene().getWindow()) + .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg()) + .showError(); + }); + } } else { - SwingUtilities.invokeLater(() -> dataContentPanel.setNode(null)); + SwingUtilities.invokeLater(() -> contentViewerPanel.setNode(null)); } } }; @@ -103,9 +121,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent")); setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application - dataContentPanel = DataContentPanel.createInstance(); - this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER); - tlResultView = new TimeLineResultView(controller, dataContentPanel); + contentViewerPanel = DataContentPanel.createInstance(); + this.contentViewerContainerPanel.add(contentViewerPanel, BorderLayout.CENTER); + tlResultView = new TimeLineResultView(controller, contentViewerPanel); final DataResultPanel dataResultPanel = tlResultView.getDataResultPanel(); this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER); dataResultPanel.open(); @@ -127,7 +145,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer lowerSplitXPane.add(contentViewerContainerPanel); } }); - controller.getSelectedEventIDs().removeListener(selectionListener); + controller.getSelectedEventIDs().removeListener(selectedEventListener); break; case LIST: /* @@ -139,7 +157,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer splitYPane.add(contentViewerContainerPanel); dataResultPanel.setNode(null); }); - controller.getSelectedEventIDs().addListener(selectionListener); + controller.getSelectedEventIDs().addListener(selectedEventListener); break; default: throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index 9ea0b37e39..09ffec0ff0 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -167,7 +167,7 @@ public class EventNode extends DisplayableItemNode { } /** - * Factory method to create an EventNode form the event ID and the events + * Factory method to create an EventNode from the event ID and the events * model. * * @param eventID The ID of the event this node is for. @@ -176,31 +176,21 @@ public class EventNode extends DisplayableItemNode { * @return An EventNode with the file (and artifact) backing this event in * its lookup. */ - public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) { + public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, IllegalStateException { /* * Look up the event by id and creata an EventNode with the appropriate * data in the lookup. */ final SingleEvent eventById = eventsModel.getEventById(eventID); - try { - SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); - AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); - if (file != null) { - if (eventById.getArtifactID().isPresent()) { - BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); - return new EventNode(eventById, file, blackboardArtifact); - } else { - return new EventNode(eventById, file); - } - } else { - //This should never happen in normal operations - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS - return null; - } - } catch (IllegalStateException | TskCoreException ex) { - //if some how the case was closed or ther is another unspecified exception, just bail out with a warning. - LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS - return null; + + SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); + + if (eventById.getArtifactID().isPresent()) { + BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); + return new EventNode(eventById, file, blackboardArtifact); + } else { + return new EventNode(eventById, file); } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 7438a0f802..eb84883808 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline.explorernodes; import java.util.Collection; import java.util.List; +import java.util.logging.Level; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -30,6 +31,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; +import org.sleuthkit.datamodel.TskCoreException; /** * Root Explorer node to represent events. @@ -125,7 +127,21 @@ public class EventRootNode extends DisplayableItemNode { */ return new TooManyNode(eventIDs.size()); } else { - return EventNode.createEventNode(eventID, filteredEvents); + try { + return EventNode.createEventNode(eventID, filteredEvents); + } catch (IllegalStateException ex) { + //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. + LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + return null; + } catch (TskCoreException ex) { + /* + * Just log it: There might be lots of these errors, and we + * don't want to flood the user with notifications. It will + * be obvious the UI is broken anyways + */ + LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + return null; + } } } }