/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javafx.application.Platform;
import javafx.scene.control.TreeItem;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
* A node in the nav/hash tree. Manages inserts and removals. Has parents and
* children. Does not have graphical properties these are configured in
* {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path
* segment and may or may not have a group
*/
class GroupTreeItem extends TreeItem<GroupTreeNode> {
static final Executor treeInsertTread = Executors.newSingleThreadExecutor();
GroupTreeItem getTreeItemForGroup(DrawableGroup grouping) {
if (Objects.equals(getValue().getGroup(), grouping)) {
return this;
} else {
for (GroupTreeItem child : childMap.values()) {
GroupTreeItem val = child.getTreeItemForGroup(grouping);
if (val != null) {
return val;
}
}
}
return null;
}
/**
* maps a path segment to the child item of this item with that path segment
*/
private final Map<String, GroupTreeItem> childMap = new HashMap<>();
/**
* the comparator if any used to sort the children of this item
*/
private Comparator<DrawableGroup> comp;
GroupTreeItem(String t, DrawableGroup g, boolean expanded) {
super(new GroupTreeNode(t, g));
setExpanded(expanded);
comp = GroupComparators.ALPHABETICAL;
}
/**
* Returns the full absolute path of this level in the tree
*
* @return the full absolute path of this level in the tree
*/
public String getAbsolutePath() {
if (getParent() != null) {
return ((GroupTreeItem) getParent()).getAbsolutePath() + getValue().getPath() + "/";
} else {
return getValue().getPath() + "/";
}
}
/**
* Recursive method to add a grouping at a given path.
*
* @param path Full path (or subset not yet added) to add
* @param g Group to add
* @param tree True if it is part of a tree (versus a list)
*/
synchronized void insert(List<String> path, DrawableGroup g, boolean tree) {
if (tree) {
// Are we at the end of the recursion?
if (path.isEmpty()) {
getValue().setGroup(g);
} else {
String prefix = path.get(0);
GroupTreeItem prefixTreeItem = childMap.computeIfAbsent(prefix, (String t) -> {
final GroupTreeItem newTreeItem = new GroupTreeItem(t, null, false);
Platform.runLater(() -> {
getChildren().add(newTreeItem);
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
});
return newTreeItem;
});
// recursively go into the path
treeInsertTread.execute(() -> {
prefixTreeItem.insert(path.subList(1, path.size()), g, true);
});
}
} else {
String join = StringUtils.join(path, "/");
//flat list
childMap.computeIfAbsent(join, (String t) -> {
final GroupTreeItem newTreeItem = new GroupTreeItem(t, g, true);
Platform.runLater(() -> {
getChildren().add(newTreeItem);
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
});
return newTreeItem;
});
}
}
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
if (path.isEmpty()) {
// end of recursion
return this;
} else {
String prefix = path.get(0);
GroupTreeItem prefixTreeItem = childMap.get(prefix);
if (prefixTreeItem == null) {
// @@@ ERROR;
return null;
}
// recursively go into the path
return prefixTreeItem.getTreeItemForPath(path.subList(1, path.size()));
}
}
synchronized void removeFromParent() {
final GroupTreeItem parent = (GroupTreeItem) getParent();
if (parent != null) {
parent.childMap.remove(getValue().getPath());
Platform.runLater(() -> {
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
});
if (parent.childMap.isEmpty()) {
parent.removeFromParent();
}
}
}
/**
* must be performed on fx thread because it manipualtes the tree directly.
*
* @param newComp
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
this.comp = newComp;
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
for (GroupTreeItem ti : childMap.values()) {
ti.resortChildren(comp);
}
}
}