private static <T, CopyType> void copyMutableRecurse(TreeDef<T> def, T root, List<T> children, CopyType copiedRoot, BiFunction<T, CopyType, CopyType> mapper) { for (T child : children) { List<T> grandChildren = def.childrenOf(child); copyMutableRecurse(def, root, grandChildren, mapper.apply(child, copiedRoot), mapper); } }
private static <T> void toStringHelper(TreeDef<T> treeDef, T root, Function<? super T, String> toString, String indent, StringBuilder builder, String prefix) { requireNonNull(prefix); for (T child : treeDef.childrenOf(root)) { builder.append(prefix); builder.append(toString.apply(child)); builder.append("\n"); toStringHelper(treeDef, child, toString, indent, builder, prefix + indent); } } }
/** Recursively determines equality between two trees. */ private static <E, A> boolean equals(TreeDef<E> expectedDef, E expectedRoot, TreeDef<A> actualDef, A actualRoot, BiPredicate<? super E, ? super A> compareFunc) { // compare the roots if (!compareFunc.test(expectedRoot, actualRoot)) { return false; } // compare the children lists List<E> expectedChildren = expectedDef.childrenOf(expectedRoot); List<A> actualChildren = actualDef.childrenOf(actualRoot); if (expectedChildren.size() != actualChildren.size()) { return false; } // recurse on each pair of children for (int i = 0; i < expectedChildren.size(); ++i) { E expectedChild = expectedChildren.get(i); A actualChild = actualChildren.get(i); if (!equals(expectedDef, expectedChild, actualDef, actualChild, compareFunc)) { return false; } } return true; }
/** * Copies the given tree of T to CopyType, starting at the leaf nodes * of the tree and moving in to the root node, which allows CopyType to * be immutable (but does not require it). * * @param def defines the structure of the tree * @param root root of the tree * @param nodeMapper given an unmapped node, and a list of CopyType nodes which have already been mapped, return a mapped node. * @return a CopyType with the same contents as the source tree */ public static <T, CopyType> CopyType copyLeavesIn(TreeDef<T> def, T root, BiFunction<T, List<CopyType>, CopyType> nodeMapper) { List<CopyType> childrenMapped = def.childrenOf(root).stream() .map(child -> copyLeavesIn(def, child, nodeMapper)) .collect(Collectors.toList()); return nodeMapper.apply(root, childrenMapped); }
/** * Copies the given tree of T to CopyType, starting at the root node * of the tree and moving out to the leaf nodes, which generally requires * CopyType to be mutable (if you want CopyType nodes to know who their * children are). * * @param def defines the structure of the tree * @param root root of the tree * @param nodeMapper given an unmapped node, and a parent CopyType which has already been mapped, return a mapped node. * This function must have the side effect that the returned node should be added as a child of its * parent node. * @return a CopyType with the same contents as the source tree */ public static <T, CopyType> CopyType copyRootOut(TreeDef<T> def, T root, BiFunction<T, CopyType, CopyType> mapper) { List<T> children = def.childrenOf(root); CopyType copyRoot = mapper.apply(root, null); copyMutableRecurse(def, root, children, copyRoot, mapper); return copyRoot; }
private static <T, R> void copyRecurse(TreeNode<R> copiedRoot, TreeDef<T> treeDef, T root, List<T> children, Function<? super T, ? extends R> mapper) { for (T child : children) { R mapped = mapper.apply(child); List<T> grandChildren = treeDef.childrenOf(child); copyRecurse(new TreeNode<>(copiedRoot, mapped, grandChildren.size()), treeDef, child, grandChildren, mapper); } }
/** * Finds a child TreeNode based on its path. * <p> * Searches the child nodes for the first element, then that * node's children for the second element, etc. * * @param treeDef defines a tree * @param node starting point for the search * @param path the path of nodes which we're looking * @param equality a function for determining equality between the tree nodes and the path elements */ public static <T, P> Optional<T> findByPath(TreeDef<T> treeDef, T node, List<P> path, BiPredicate<T, P> equality) { requireNonNull(treeDef); requireNonNull(node); requireNonNull(equality); T value = node; for (P segment : path) { Optional<T> valueOpt = treeDef.childrenOf(value).stream().filter(n -> equality.test(n, segment)).findFirst(); if (!valueOpt.isPresent()) { return valueOpt; } value = valueOpt.get(); } return Optional.of(value); }
/** * Creates a hierarchy of TreeNodes that copies the structure and content of the given tree, * using {@code mapper} to calculate the content of the nodes. */ public static <T, R> TreeNode<R> copy(TreeDef<T> treeDef, T root, Function<? super T, ? extends R> mapper) { // Leaves need to know their parent, but TreeQuery.copyLeavesIn makes that impossible // Parents need to know how many children they're goign to have, but TreeQuery.copyRootOut makes that impossible // thus, we do our own thing here List<T> children = treeDef.childrenOf(root); R mapped = mapper.apply(root); TreeNode<R> copyRoot = new TreeNode<>(null, mapped, children.size()); copyRecurse(copyRoot, treeDef, root, children, mapper); return copyRoot; }
/** Creates a new TreeDef which whose {@code childrenOf} method is filtered by the given predicate. */ default TreeDef<T> filter(Predicate<T> predicate) { return TreeDef.of(node -> TreeImp.filteredList(childrenOf(node), predicate)); }