diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java new file mode 100644 index 0000000..6aa90e7 --- /dev/null +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -0,0 +1,157 @@ +package net.kemitix.node; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * An abstract node item, providing default implementations for most read-only + * operations. + * + * @param the type of data stored in each node + * + * @author pcampbell + */ +abstract class AbstractNodeItem implements Node { + + private T data; + + private String name; + + private Node parent; + + private final Set> children; + + protected AbstractNodeItem( + final T data, final String name, final Node parent, + final Set> children) { + this.data = data; + this.name = name; + this.parent = parent; + this.children = children; + } + + @Override + public String getName() { + return name; + } + + @Override + public Optional getData() { + return Optional.ofNullable(data); + } + + @Override + public boolean isEmpty() { + return data == null; + } + + @Override + public Optional> getParent() { + return Optional.ofNullable(parent); + } + + @Override + public Set> getChildren() { + return new HashSet<>(children); + } + + /** + * Fetches the node for the child if present. + * + * @param child the child's data to search for + * + * @return an {@link Optional} containing the child node if found + */ + @Override + public Optional> findChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); + } + return children.stream().filter(node -> { + final Optional d = node.getData(); + return d.isPresent() && d.get().equals(child); + }).findAny(); + } + + @Override + public Node getChild(final T child) { + return findChild(child).orElseThrow( + () -> new NodeException("Child not found")); + } + + /** + * Checks if the node is an ancestor. + * + * @param node the potential ancestor + * + * @return true if the node is an ancestor + */ + @Override + public boolean isDescendantOf(final Node node) { + return parent != null && (node.equals(parent) || parent.isDescendantOf( + node)); + } + + /** + * Walks the node tree using the path to select each child. + * + * @param path the path to the desired child + * + * @return the child or null + */ + @Override + public Optional> findInPath(final List path) { + if (path == null) { + throw new NullPointerException("path"); + } + if (path.size() > 0) { + Optional> found = findChild(path.get(0)); + if (found.isPresent()) { + if (path.size() > 1) { + return found.get().findInPath(path.subList(1, path.size())); + } + return found; + } + } + return Optional.empty(); + } + + @Override + public Optional> findChildByName(final String named) { + if (named == null) { + throw new NullPointerException("name"); + } + return children.stream() + .filter(n -> n.getName().equals(named)) + .findAny(); + } + + @Override + public Node getChildByName(final String named) { + return findChildByName(named).orElseThrow( + () -> new NodeException("Named child not found")); + } + + @Override + public String drawTree(final int depth) { + final StringBuilder sb = new StringBuilder(); + final String unnamed = "(unnamed)"; + if (isNamed()) { + sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n", + name)); + } else if (!children.isEmpty()) { + sb.append( + String.format("[%1$" + (depth + unnamed.length()) + "s]\n", + unnamed)); + } + getChildren().forEach(c -> sb.append(c.drawTree(depth + 1))); + return sb.toString(); + } + + @Override + public boolean isNamed() { + return name != null && name.length() > 0; + } +}