diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java index ba3333e7f7..7361bf5d88 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventBundle.java @@ -47,7 +47,7 @@ public interface EventBundle> { Optional getParentBundle(); - default long getCount() { + default int getCount() { return getEventIDs().size(); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index 975b0cc40f..ac5a02d007 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -18,9 +18,9 @@ */ package org.sleuthkit.autopsy.timeline.datamodel; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; -import java.util.Collections; import java.util.Comparator; import java.util.Objects; import java.util.Optional; @@ -89,28 +89,28 @@ public class EventCluster implements EventBundle { /** * the set of ids of the clustered events */ - final private Set eventIDs; + final private ImmutableSet eventIDs; /** * the ids of the subset of clustered events that have at least one tag * applied to them */ - private final Set tagged; + private final ImmutableSet tagged; /** * the ids of the subset of clustered events that have at least one hash set * hit */ - private final Set hashHits; + private final ImmutableSet hashHits; private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, Set tagged, String description, DescriptionLoD lod, EventStripe parent) { this.span = spanningInterval; this.type = type; - this.hashHits = hashHits; - this.tagged = tagged; + this.hashHits = ImmutableSet.copyOf(hashHits); + this.tagged = ImmutableSet.copyOf(tagged); this.description = description; - this.eventIDs = eventIDs; + this.eventIDs = ImmutableSet.copyOf(eventIDs); this.lod = lod; this.parent = parent; } @@ -139,18 +139,21 @@ public class EventCluster implements EventBundle { } @Override - public Set getEventIDs() { - return Collections.unmodifiableSet(eventIDs); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDs() { + return eventIDs; } @Override - public Set getEventIDsWithHashHits() { - return Collections.unmodifiableSet(hashHits); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDsWithHashHits() { + return hashHits; } @Override - public Set getEventIDsWithTags() { - return Collections.unmodifiableSet(tagged); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDsWithTags() { + return tagged; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java index f46cbd82da..7a6bb4be42 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventStripe.java @@ -19,13 +19,11 @@ package org.sleuthkit.autopsy.timeline.datamodel; import com.google.common.base.Preconditions; -import java.util.Collections; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; import java.util.Comparator; -import java.util.HashSet; import java.util.Optional; -import java.util.Set; import java.util.SortedSet; -import java.util.TreeSet; import javax.annotation.concurrent.Immutable; import org.python.google.common.base.Objects; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; @@ -50,7 +48,7 @@ public final class EventStripe implements EventBundle { private final EventCluster parent; - private final SortedSet clusters = new TreeSet<>(Comparator.comparing(EventCluster::getStartMillis)); + private final ImmutableSortedSet clusters; /** * the type of all the events @@ -70,59 +68,70 @@ public final class EventStripe implements EventBundle { /** * the set of ids of the events */ - private final Set eventIDs = new HashSet<>(); + private final ImmutableSet eventIDs; /** * the ids of the subset of events that have at least one tag applied to * them */ - private final Set tagged = new HashSet<>(); + private final ImmutableSet tagged; /** * the ids of the subset of events that have at least one hash set hit */ - private final Set hashHits = new HashSet<>(); + private final ImmutableSet hashHits; public EventStripe withParent(EventCluster parent) { - EventStripe eventStripe = new EventStripe(parent, this.type, this.description, this.lod); - eventStripe.clusters.addAll(clusters); - eventStripe.eventIDs.addAll(eventIDs); - eventStripe.tagged.addAll(tagged); - eventStripe.hashHits.addAll(hashHits); + EventStripe eventStripe = new EventStripe(parent, this.type, this.description, this.lod, clusters, eventIDs, tagged, hashHits); return eventStripe; } - private EventStripe(EventCluster parent, EventType type, String description, DescriptionLoD lod) { + private EventStripe(EventCluster parent, EventType type, String description, DescriptionLoD lod, SortedSet clusters, ImmutableSet eventIDs, ImmutableSet tagged, ImmutableSet hashHits) { this.parent = parent; this.type = type; this.description = description; this.lod = lod; + this.clusters = ImmutableSortedSet.copyOf(Comparator.comparing(EventCluster::getStartMillis), clusters); + + this.eventIDs = eventIDs; + this.tagged = tagged; + this.hashHits = hashHits; } public EventStripe(EventCluster cluster, EventCluster parent) { - clusters.add(cluster); + this.clusters = ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)) + .add(cluster).build(); type = cluster.getEventType(); description = cluster.getDescription(); lod = cluster.getDescriptionLoD(); - eventIDs.addAll(cluster.getEventIDs()); - tagged.addAll(cluster.getEventIDsWithTags()); - hashHits.addAll(cluster.getEventIDsWithHashHits()); + eventIDs = cluster.getEventIDs(); + tagged = cluster.getEventIDsWithTags(); + hashHits = cluster.getEventIDsWithHashHits(); this.parent = parent; } private EventStripe(EventStripe u, EventStripe v) { - clusters.addAll(u.clusters); - clusters.addAll(v.clusters); + clusters = ImmutableSortedSet.orderedBy(Comparator.comparing(EventCluster::getStartMillis)) + .addAll(u.getClusters()) + .addAll(v.getClusters()) + .build(); + type = u.getEventType(); description = u.getDescription(); lod = u.getDescriptionLoD(); - eventIDs.addAll(u.getEventIDs()); - eventIDs.addAll(v.getEventIDs()); - tagged.addAll(u.getEventIDsWithTags()); - tagged.addAll(v.getEventIDsWithTags()); - hashHits.addAll(u.getEventIDsWithHashHits()); - hashHits.addAll(v.getEventIDsWithHashHits()); + eventIDs = ImmutableSet.builder() + .addAll(u.getEventIDs()) + .addAll(v.getEventIDs()) + .build(); + tagged = ImmutableSet.builder() + .addAll(u.getEventIDsWithTags()) + .addAll(v.getEventIDsWithTags()) + .build(); + hashHits = ImmutableSet.builder() + .addAll(u.getEventIDsWithHashHits()) + .addAll(v.getEventIDsWithHashHits()) + .build(); parent = u.getParentBundle().orElse(v.getParentBundle().orElse(null)); } @@ -147,18 +156,21 @@ public final class EventStripe implements EventBundle { } @Override - public Set getEventIDs() { - return Collections.unmodifiableSet(eventIDs); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDs() { + return eventIDs; } @Override - public Set getEventIDsWithHashHits() { - return Collections.unmodifiableSet(hashHits); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDsWithHashHits() { + return hashHits; } @Override - public Set getEventIDsWithTags() { - return Collections.unmodifiableSet(tagged); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSet getEventIDsWithTags() { + return tagged; } @Override @@ -172,14 +184,13 @@ public final class EventStripe implements EventBundle { } @Override - public SortedSet< EventCluster> getClusters() { - return Collections.unmodifiableSortedSet(clusters); + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public ImmutableSortedSet< EventCluster> getClusters() { + return clusters; } @Override public String toString() { return "EventStripe{" + "description=" + description + ", eventIDs=" + eventIDs.size() + '}'; } - - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java index 730c97bf6b..87dc2f56ae 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventBundleNodeBase.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -32,7 +31,10 @@ import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.event.EventHandler; import javafx.geometry.Insets; @@ -120,7 +122,7 @@ public abstract class EventBundleNodeBase subNodes = new ArrayList<>(); + final ObservableList subNodes = FXCollections.observableArrayList(); final Pane subNodePane = new Pane(); final Label descrLabel = new Label(); final Label countLabel = new Label(); @@ -152,7 +154,11 @@ public abstract class EventBundleNodeBase setDescriptionVisibiltiyImpl(descVisibility.get())); descVisibility.set(DescriptionVisibility.SHOWN); //trigger listener for initial value + + Bindings.bindContent(subNodePane.getChildren(), subNodes); } final DescriptionLoD getDescriptionLoD() { @@ -342,6 +350,8 @@ public abstract class EventBundleNodeBase { private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName()); + /** + * Use this recursive function to flatten a tree of nodes into an single + * stream. More specifically it takes an EventStripeNode and produces a + * stream of EventStripes conaiting the stripes for the given node and all + * child eventStripes, ignoring intervening EventCluster nodes. + * + * @see + * #loadSubBundles(org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD.RelativeDetail) + * for usage + */ + private final static Function> stripeFlattener = new Function>() { + @Override + public Stream apply(EventStripeNode node) { + return Stream.concat( + Stream.of(node.getEventStripe()), + node.getSubNodes().stream().flatMap(clusterNode -> clusterNode.getSubNodes().stream().flatMap(this))); + } + }; + private static final BorderWidths CLUSTER_BORDER_WIDTHS = new BorderWidths(2, 1, 2, 1); private static final Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS //NOI18N private static final Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS //NOI18N @@ -89,6 +113,7 @@ final public class EventClusterNode extends EventBundleNodeBase> loggedTask = new Task>() { + Task> loggedTask = new LoggedTask>(Bundle.EventStripeNode_loggedTask_name(), false) { private volatile DescriptionLoD loadedDescriptionLoD = getDescriptionLoD().withRelativeDetail(relativeDetail); - { - updateTitle(Bundle.EventStripeNode_loggedTask_name()); - } - @Override protected List call() throws Exception { List bundles; @@ -182,7 +203,9 @@ final public class EventClusterNode extends EventBundleNodeBase eventStripe.withParent(getEventCluster())); + return bundles.stream() + .map(eventStripe -> eventStripe.withParent(getEventCluster())) + .collect(Collectors.toList()); } @Override @@ -190,14 +213,16 @@ final public class EventClusterNode extends EventBundleNodeBase bundles = get(); + //clear the existing subnodes + List transform = subNodes.stream().flatMap(stripeFlattener).collect(Collectors.toList()); + chart.getEventStripes().removeAll(transform); + subNodes.clear(); if (bundles.isEmpty()) { - subNodePane.getChildren().clear(); getChildren().setAll(subNodePane, infoHBox); descLOD.set(getEventBundle().getDescriptionLoD()); } else { chart.getEventStripes().addAll(bundles); - subNodes.addAll(Lists.transform(bundles, EventClusterNode.this::createStripeNode)); - subNodePane.getChildren().setAll(subNodes); + subNodes.addAll(Lists.transform(bundles, EventClusterNode.this::createChildNode)); getChildren().setAll(new VBox(infoHBox, subNodePane)); descLOD.set(loadedDescriptionLoD); } @@ -214,7 +239,8 @@ final public class EventClusterNode extends EventBundleNodeBase impl * @return all the nodes that pass the given predicate */ synchronized Iterable> getNodes(Predicate> p) { - //use this recursive function to flatten the tree of nodes into an iterable. + //use this recursive function to flatten the tree of nodes into an single stream. Function, Stream>> stripeFlattener = new Function, Stream>>() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java index 573302a100..dde8cb03c4 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/EventStripeNode.java @@ -68,22 +68,25 @@ final public class EventStripeNode extends EventBundleNodeBase childMap = new HashMap<>(); private final EventBundle bundle; + private Comparator>> comparator = TreeComparator.Description; public EventBundle getEventBundle() { return bundle; } - EventDescriptionTreeItem(EventBundle g) { + EventDescriptionTreeItem(EventBundle g, Comparator>> comp) { bundle = g; + comparator = comp; setValue(g); } @@ -56,10 +58,11 @@ class EventDescriptionTreeItem extends NavTreeItem { public void insert(Deque> path) { EventBundle head = path.removeFirst(); EventDescriptionTreeItem treeItem = childMap.computeIfAbsent(head.getDescription(), description -> { - EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(head); + EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(head, comparator); newTreeItem.setExpanded(true); childMap.put(description, newTreeItem); getChildren().add(newTreeItem); + resort(comparator, false); return newTreeItem; }); @@ -81,8 +84,12 @@ class EventDescriptionTreeItem extends NavTreeItem { } @Override - public void resort(Comparator>> comp) { + void resort(Comparator>> comp, Boolean recursive) { + this.comparator = comp; FXCollections.sort(getChildren(), comp); + if (recursive) { + childMap.values().forEach(ti -> ti.resort(comp, true)); + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java index afad634153..7c73a4b49f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventTypeTreeItem.java @@ -34,10 +34,11 @@ class EventTypeTreeItem extends NavTreeItem { */ private final Map childMap = new HashMap<>(); - private final Comparator>> comparator = TreeComparator.Description; + private Comparator>> comparator = TreeComparator.Description; - EventTypeTreeItem(EventBundle g) { + EventTypeTreeItem(EventBundle g, Comparator>> comp) { setValue(g); + comparator = comp; } @Override @@ -49,11 +50,11 @@ class EventTypeTreeItem extends NavTreeItem { public void insert(Deque> path) { EventBundle head = path.removeFirst(); EventDescriptionTreeItem treeItem = childMap.computeIfAbsent(head.getDescription(), description -> { - EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(head); + EventDescriptionTreeItem newTreeItem = new EventDescriptionTreeItem(head, comparator); newTreeItem.setExpanded(true); childMap.put(head.getDescription(), newTreeItem); getChildren().add(newTreeItem); - + resort(comparator, false); return newTreeItem; }); @@ -63,16 +64,15 @@ class EventTypeTreeItem extends NavTreeItem { } void remove(Deque> path) { - EventBundle head = path.removeFirst(); EventDescriptionTreeItem descTreeItem = childMap.get(head.getDescription()); if (descTreeItem != null) { if (path.isEmpty() == false) { descTreeItem.remove(path); - } else if (descTreeItem.getChildren().isEmpty()) { + } + if (descTreeItem.getChildren().isEmpty()) { childMap.remove(head.getDescription()); getChildren().remove(descTreeItem); - } } } @@ -92,8 +92,11 @@ class EventTypeTreeItem extends NavTreeItem { } @Override - public void resort(Comparator>> comp - ) { + void resort(Comparator>> comp, Boolean recursive) { + this.comparator = comp; FXCollections.sort(getChildren(), comp); + if (recursive) { + childMap.values().forEach(ti -> ti.resort(comp, true)); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java index aed1e0374f..1eba76c5fc 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/EventsTree.java @@ -94,7 +94,6 @@ final public class EventsTree extends BorderPane { getRoot().remove(bundle); } } - getRoot().resort(sortByBox.getSelectionModel().getSelectedItem()); }); setRoot(); @@ -114,11 +113,10 @@ final public class EventsTree extends BorderPane { @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void setRoot() { - RootItem root = new RootItem(); + RootItem root = new RootItem(TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem())); for (EventBundle bundle : detailViewPane.getEventStripes()) { root.insert(bundle); } - root.resort(TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem())); eventsTree.setRoot(root); } @@ -131,7 +129,7 @@ final public class EventsTree extends BorderPane { sortByBox.getItems().setAll(Arrays.asList(TreeComparator.Description, TreeComparator.Count)); sortByBox.getSelectionModel().select(TreeComparator.Description); sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> { - getRoot().resort(TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem())); + getRoot().resort(TreeComparator.Type.reversed().thenComparing(sortByBox.getSelectionModel().getSelectedItem()), true); }); eventsTree.setShowRoot(false); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java index 30d1fdd9ea..d2d69248b4 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/NavTreeItem.java @@ -32,9 +32,8 @@ abstract class NavTreeItem extends TreeItem> { abstract long getCount(); - abstract void resort(Comparator>> comp); + abstract void resort(Comparator>> comp, Boolean recursive); abstract NavTreeItem findTreeItemForEvent(EventBundle t); - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java index f71d2e831d..ccfa97e2dd 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/tree/RootItem.java @@ -42,9 +42,10 @@ class RootItem extends NavTreeItem { /** * the comparator if any used to sort the children of this item */ -// private TreeNodeComparators comp; - RootItem() { + private Comparator>> comparator = TreeComparator.Type.reversed(); + RootItem(Comparator>> comp) { + comp = comp; } @Override @@ -62,12 +63,13 @@ class RootItem extends NavTreeItem { EventTypeTreeItem treeItem = childMap.computeIfAbsent(bundle.getEventType().getBaseType(), baseType -> { - EventTypeTreeItem newTreeItem = new EventTypeTreeItem(bundle); + EventTypeTreeItem newTreeItem = new EventTypeTreeItem(bundle, comparator); newTreeItem.setExpanded(true); getChildren().add(newTreeItem); return newTreeItem; }); treeItem.insert(getTreePath(bundle)); + } void remove(EventBundle bundle) { @@ -96,8 +98,9 @@ class RootItem extends NavTreeItem { } @Override - public void resort(Comparator>> comp) { - childMap.values().forEach(ti -> ti.resort(comp)); + void resort(Comparator>> comp, Boolean recursive) { + comparator = comp; + childMap.values().forEach(ti -> ti.resort(comp, true)); } @Override