/** * 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 mapper maps elements to some value for comparison between the tree and the path */ public static <T> Optional<T> findByPath(TreeDef<T> treeDef, T node, List<T> path, Function<? super T, ?> mapper) { return findByPath(treeDef, node, mapper, path, mapper); }
/** Returns true iff child is a descendant of parent, or if child is equal to parent. */ public static <T> boolean isDescendantOfOrEqualTo(TreeDef.Parented<T> treeDef, T child, T parent) { requireNonNull(treeDef); requireNonNull(parent); if (child.equals(parent)) { return true; } else { return isDescendantOf(treeDef, child, parent); } }
/** Returns the common parent of N elements. */ @SafeVarargs public static <T> Optional<T> lowestCommonAncestor(TreeDef.Parented<T> treeDef, T... nodes) { return lowestCommonAncestor(treeDef, Arrays.asList(nodes)); }
/** * Converts the entire tree into a string-based representation. * * @see #toString(TreeDef, Object, Function, String) */ public static <T> String toString(TreeDef<T> treeDef, T root) { return toString(treeDef, root, Object::toString); }
/** * Returns the path of the given node, using {@code /} as the path delimiter. * * @see #path(com.diffplug.common.tree.TreeDef.Parented, Object, Function, String) */ public static <T> String path(TreeDef.Parented<T> treeDef, T node, Function<? super T, String> toString) { return path(treeDef, node, toString, "/"); }
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); } }
/** * Converts the entire tree into a string-based representation. * * @param treeDef the treeDef * @param root the root of the tree * @param toString the function which generates the name for each node in the tree * @param indent the string to use for each level of indentation */ public static <T> String toString(TreeDef<T> treeDef, T root, Function<? super T, String> toString, String indent) { StringBuilder builder = new StringBuilder(); builder.append(toString.apply(root)); builder.append("\n"); toStringHelper(treeDef, root, toString, indent, builder, indent); return builder.toString(); }
/** * 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); }
/** * Returns the path of the given node. * * @param treeDef the treeDef * @param node the root of the tree * @param toString a function to map each node to a string in the path * @param delimiter a string to use as a path separator */ public static <T> String path(TreeDef.Parented<T> treeDef, T node, Function<? super T, String> toString, String delimiter) { requireNonNull(node); requireNonNull(delimiter); List<T> toRoot = toRoot(treeDef, node); ListIterator<T> iterator = toRoot.listIterator(toRoot.size()); StringBuilder builder = new StringBuilder(); while (iterator.hasPrevious()) { T segment = iterator.previous(); // add the node builder.append(toString.apply(segment)); // add the separator if it makes sense if (iterator.hasPrevious()) { builder.append(delimiter); } } return builder.toString(); }
/** Returns the root shell of the given control. */ public static Shell rootShell(Control ctl) { Shell shell; if (ctl instanceof Shell) { shell = (Shell) ctl; } else { shell = ctl.getShell(); } return TreeQuery.root(SwtMisc.treeDefShell(), shell); }
/** * Converts the entire tree into a string-based representation. * * @see #toString(TreeDef, Object, Function, String) */ public static <T> String toString(TreeDef<T> treeDef, T root, Function<? super T, String> toString) { return toString(treeDef, root, toString, " "); }
/** * Returns the path of the given node, using {@code /} as the path delimiter and {@link Object#toString()} as the mapping function. * * @see #path(com.diffplug.common.tree.TreeDef.Parented, Object, Function, String) */ public static <T> String path(TreeDef.Parented<T> treeDef, T node) { return path(treeDef, node, Object::toString); }
/** * 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> 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); } } }
/** * Returns an {@link AssertionError} containing the contents of the * two trees. Attempts to throw a JUnit ComparisonFailure if JUnit * is on the class path, but it fails to a plain old {@code java.lang.AssertionError} * if the reflection calls fail. */ private AssertionError createAssertionError() { // convert both sides to strings String expected = TreeQuery.toString(expectedDef, expectedRoot, expectedToString); String actual = TreeQuery.toString(actualDef, actualRoot, actualToString); // try to create a junit ComparisonFailure for (String exceptionType : Arrays.asList( "org.junit.ComparisonFailure", "junit.framework.ComparisonFailure")) { try { return createComparisonFailure(exceptionType, expected, actual); } catch (Exception e) {} } // we'll have to settle for a plain-jane AssertionError return new AssertionError("Expected:\n" + expected + "\n\nActual:\n" + actual); }
/** * 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 treeMapper maps elements in the tree to some value for comparison with the path elements * @param path the path of nodes which we're looking * @param pathMapper maps elements in the path to some value for comparison with the tree elements */ public static <T, P> Optional<T> findByPath(TreeDef<T> treeDef, T node, Function<? super T, ?> treeMapper, List<P> path, Function<? super P, ?> pathMapper) { requireNonNull(treeMapper); requireNonNull(path); requireNonNull(pathMapper); return findByPath(treeDef, node, path, (treeSide, pathSide) -> { return Objects.equals(treeMapper.apply(treeSide), pathMapper.apply(pathSide)); }); }
/** Returns the path of this node, using the given {@code toString} method and {@code delimiter}. */ public String getPath(Function<? super T, String> toString, String delimiter) { requireNonNull(toString); requireNonNull(delimiter); return TreeQuery.path(treeDef(), this, node -> toString.apply(node.getContent()), delimiter); }
/** Returns the common parent of N elements. */ public static <T> Optional<T> lowestCommonAncestor(TreeDef.Parented<T> treeDef, List<T> nodes) { requireNonNull(treeDef); if (nodes.size() == 0) { return Optional.empty(); } else { Optional<T> soFar = Optional.of(nodes.get(0)); for (int i = 1; i < nodes.size() && soFar.isPresent(); ++i) { soFar = lowestCommonAncestor(treeDef, soFar.get(), nodes.get(i)); } return soFar; } }
/** * Returns a "deep" toString, including the entire tree below this level. * * @see TreeQuery#toString(TreeDef, Object, Function, String) */ public String toStringDeep() { return TreeQuery.toString(treeDef(), this, node -> node.getContent().toString()); }
/** @see #findByPath(Object...) */ public TreeNode<T> findByPath(List<T> path) { Optional<TreeNode<T>> result = TreeQuery.findByPath(treeDef(), this, TreeNode::getContent, path, Function.identity()); if (result.isPresent()) { return result.get(); } else { throw new IllegalArgumentException(this.toString() + " has no element with path " + path); } }