package org.codefx.libfx.collection.tree.stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import org.codefx.libfx.collection.tree.navigate.TreeNavigator;
/**
* Utility class to easily create {@link TreePath}s for recurring situations.
*/
class TreePathFactory {
/**
* @param <E>
* the type of elements contained in the tree
* @param node
* the only node of the returned path
* @return a tree path containing the node
*/
public static <E> TreePath<TreeNode<E>> createWithSingleNode(E node) {
Objects.requireNonNull(node, "The argument 'node' must not be null.");
return new StackTreePath<>(Collections.singletonList(SimpleTreeNode.root(node)));
}
/**
* @param <E>
* the type of elements contained in the tree
* @param navigator
* the navigator used to navigate the tree
* @param pathAsList
* the path as a list where the path' first node is the first element in the list
* @return a tree path containing the nodes for the elements in the list
*/
public static <E> TreePath<TreeNode<E>> createFromElementList(TreeNavigator<E> navigator, List<E> pathAsList) {
Objects.requireNonNull(navigator, "The argument 'navigator' must not be null.");
Objects.requireNonNull(pathAsList, "The argument 'pathAsList' must not be null.");
TreePath<TreeNode<E>> path = new StackTreePath<>();
pathAsList.forEach(element -> {
OptionalInt childIndex = navigator.getChildIndex(element);
TreeNode<E> node = SimpleTreeNode.node(element, childIndex);
path.append(node);
});
return path;
}
/**
* @param <E>
* the type of elements contained in the tree
* @param navigator
* the navigator used to navigate the tree
* @param node
* a node in the tree
* @param descendant
* a descendant of the specified node
* @return a tree path leading from the node to the descendant
* @throws IllegalArgumentException
* if the node is no ancestor of the descendant
*/
public static <E> TreePath<TreeNode<E>> createFromNodeToDescendant(
TreeNavigator<E> navigator, E node, E descendant) throws IllegalArgumentException {
Objects.requireNonNull(navigator, "The argument 'navigator' must not be null.");
Objects.requireNonNull(node, "The argument 'node' must not be null.");
Objects.requireNonNull(descendant, "The argument 'descendant' must not be null.");
if (node == descendant)
return createWithSingleNode(node);
List<TreeNode<E>> pathFromDescendantBackToNode = createPathFromDescendantBackToNode(navigator, node, descendant);
return createPathByInverting(pathFromDescendantBackToNode);
}
private static <E> List<TreeNode<E>> createPathFromDescendantBackToNode(
TreeNavigator<E> navigator, E node, E descendant) {
List<TreeNode<E>> pathFromDescendantToNode = createPathWithSingleNode(navigator, descendant);
Optional<E> parent = addAllAncestorsToPathUntilReachingNode(
navigator, node, descendant, pathFromDescendantToNode);
addNodeToPathOrThrowException(navigator, node, parent, descendant, pathFromDescendantToNode);
return pathFromDescendantToNode;
}
private static <E> List<TreeNode<E>> createPathWithSingleNode(TreeNavigator<E> navigator, E descendant) {
List<TreeNode<E>> pathFromStartBackToNode = new ArrayList<>();
TreeNode<E> descendantNode = SimpleTreeNode.node(descendant, navigator.getChildIndex(descendant));
pathFromStartBackToNode.add(descendantNode);
return pathFromStartBackToNode;
}
private static <E> Optional<E> addAllAncestorsToPathUntilReachingNode(
TreeNavigator<E> navigator, E node, E descendant, List<TreeNode<E>> pathFromStartBackToNode) {
Optional<E> parent = navigator.getParent(descendant);
while (parent.isPresent() && parent.get() != node) {
TreeNode<E> parentNode = SimpleTreeNode.node(parent.get(), navigator.getChildIndex(parent.get()));
pathFromStartBackToNode.add(parentNode);
parent = navigator.getParent(parent.get());
}
return parent;
}
private static <E> void addNodeToPathOrThrowException(
TreeNavigator<E> navigator, E node, Optional<E> parent, E descendant,
List<TreeNode<E>> pathFromStartBackToNode) {
if (!parent.isPresent() || parent.get() != node)
throw new IllegalArgumentException(
"The specified node '" + node
+ "' is no ancestor of the specified descendant '" + descendant + "'.");
else {
TreeNode<E> startNode = SimpleTreeNode.node(node, navigator.getChildIndex(node));
pathFromStartBackToNode.add(startNode);
}
}
private static <E> TreePath<TreeNode<E>> createPathByInverting(List<TreeNode<E>> pathFromDescendantBackToNode) {
StackTreePath<TreeNode<E>> treePath = new StackTreePath<TreeNode<E>>();
for (int i = pathFromDescendantBackToNode.size() - 1; i >= 0; i--)
treePath.append(pathFromDescendantBackToNode.get(i));
return treePath;
}
/**
* @param <E>
* the type of elements contained in the tree
* @param navigator
* the navigator used to navigate the tree
* @param node
* a node in the tree
* @return a tree path leading from the root to the node
*/
public static <E> TreePath<TreeNode<E>> createFromRootToNode(TreeNavigator<E> navigator, E node) {
Objects.requireNonNull(navigator, "The argument 'navigator' must not be null.");
Objects.requireNonNull(node, "The argument 'node' must not be null.");
List<TreeNode<E>> pathFromDescendantBackToNode = createPathFromNodeBackToRoot(navigator, node);
return createPathByInverting(pathFromDescendantBackToNode);
}
private static <E> List<TreeNode<E>> createPathFromNodeBackToRoot(
TreeNavigator<E> navigator, E node) {
List<TreeNode<E>> pathFromNodeToRoot = createPathWithSingleNode(navigator, node);
addAllAncestorNodesToPath(navigator, node, pathFromNodeToRoot);
return pathFromNodeToRoot;
}
private static <E> void addAllAncestorNodesToPath(
TreeNavigator<E> navigator, E node, List<TreeNode<E>> pathFromStartBackToNode) {
Optional<E> parent = navigator.getParent(node);
while (parent.isPresent()) {
TreeNode<E> parentNode = SimpleTreeNode.node(parent.get(), navigator.getChildIndex(parent.get()));
pathFromStartBackToNode.add(parentNode);
parent = navigator.getParent(parent.get());
}
}
}