
org.fulib.fx.data.TraversableNodeTree Maven / Gradle / Ivy
package org.fulib.fx.data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import static org.fulib.fx.util.FrameworkUtil.error;
/**
* A tree that can be traversed by a path in form of directory-like structure implemented using nodes.
*
* @param The type of the values stored in the tree.
*/
public class TraversableNodeTree implements TraversableTree {
private final @NotNull Node root;
private @NotNull Node current;
/**
* Creates a new traversable tree.
*/
public TraversableNodeTree() {
this.root = new Node<>("", null, null, null);
this.current = this.root;
}
@Override
public @Nullable E root() {
return this.root.value();
}
@Override
public E traverse(String path) {
return this.follow(path, true);
}
@Override
public @Nullable E get(String path) {
return this.follow(path, false);
}
@Override
public boolean containsPath(String path) {
return this.follow(path, false) != null;
}
@Override
public @Nullable E current() {
return this.current.value();
}
/**
* Inserts a value at the given path. If the path does not exist, it will be created.
*
* If the path starts with a slash, the root node will be used as the starting point.
*
* If the path does not start with a slash, the current node will be used as the starting point.
*
* @param path The path to insert the value at.
* @param value The value to insert.
*/
@Override
public void insert(@NotNull String path, @NotNull E value) {
Node node = path.startsWith("/") ? this.root : this.current;
for (String element : path.split("/")) {
if (element.isBlank()) {
continue;
}
if (element.equals("..")) {
if (node.parent == null) {
throw new IllegalArgumentException(error(3006));
}
node = node.parent;
continue;
}
Node traversed = node;
node = node.children().parallelStream().filter(child -> child.id().equals(element)).findAny().orElseGet(() -> {
Node newChild = new Node<>(element, null, null, null);
traversed.addChild(newChild);
return newChild;
});
}
node.value(value);
}
public Node currentNode() {
return this.current;
}
public void setCurrentNode(Node node) {
this.current = node;
}
/**
* Follows the given path and returns the value at the end of the path.
*
* If the path starts with a slash, the root node will be used as the starting point.
*
* If the path does not start with a slash, the current node will be used as the starting point.
*
* @param path The path to follow.
* @param navigate Whether the current node should be changed to the node at the end of the path.
* @return The value at the end of the path, or null if the path does not exist.
*/
private E follow(String path, boolean navigate) {
Node node = path.startsWith("/") ? this.root : this.current;
for (String element : path.split("/")) {
if (element.isBlank()) {
continue;
}
if (node == null)
return null;
if (element.equals("..")) {
if (node.parent != null) {
node = node.parent;
}
continue;
}
node = node.children().parallelStream().filter(child -> child.id().equals(element)).findAny().orElse(null);
}
if (navigate && node != null)
this.current = node;
return node == null ? null : node.value();
}
/**
* A node for representing a tree.
*
* @param The type of the value stored in the node (and the tree).
*/
public static class Node {
private final @NotNull String id;
private @Nullable E value;
private @Nullable Node parent;
private @Nullable Collection> children;
public Node(@NotNull String id, @Nullable E value, @Nullable Node parent, @Nullable Collection> children) {
this.id = id;
this.value = value;
this.parent = parent;
this.children = children;
}
public @NotNull String id() {
return this.id;
}
public @Nullable E value() {
return this.value;
}
public @Nullable Node parent() {
return this.parent;
}
public @NotNull Collection> children() {
if (this.children == null)
this.children = new ArrayList<>();
return this.children;
}
public void value(@Nullable E value) {
this.value = value;
}
public void addChild(Node child) {
this.children().add(child);
if (child.parent != null) {
child.parent.removeChild(child);
}
child.parent = this;
}
public void removeChild(Node child) {
child.parent = null;
if (this.children == null)
return;
this.children.remove(child);
}
}
}