/*
* Copyright 2016 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.common.base;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
/** Queries against {@link TreeDef} trees, e.g. lowest common ancestor, list of parents, etc. */
public class TreeQuery {
/** Returns true iff child is a descendant of parent. */
public static <T> boolean isDescendantOf(TreeDef.Parented<T> treeDef, T child, T parent) {
T candidateParent = treeDef.parentOf(child);
while (candidateParent != null) {
if (candidateParent.equals(parent)) {
return true;
} else {
candidateParent = treeDef.parentOf(candidateParent);
}
}
return false;
}
/** 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) {
if (child.equals(parent)) {
return true;
} else {
return isDescendantOf(treeDef, child, parent);
}
}
/** Returns the root of the given tree. */
public static <T> T root(TreeDef.Parented<T> treeDef, T node) {
T lastParent;
T parent = node;
do {
lastParent = parent;
parent = treeDef.parentOf(lastParent);
} while (parent != null);
return lastParent;
}
/** Creates a mutable list whose first element is {@code node}, and last element is its root parent. */
public static <T> List<T> toRoot(TreeDef.Parented<T> treeDef, T node) {
List<T> list = new ArrayList<>();
T tip = node;
while (tip != null) {
list.add(tip);
tip = treeDef.parentOf(tip);
}
return list;
}
/**
* 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 -> {
return 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, 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);
}
}
/**
* Creates a mutable list whose first element is {@code node}, and last element is {@code parent}.
* @throws IllegalArgumentException if {@code parent} is not a parent of {@code node}
*/
public static <T> List<T> toParent(TreeDef.Parented<T> treeDef, T node, T parent) {
List<T> list = new ArrayList<>();
T tip = node;
while (true) {
list.add(tip);
tip = treeDef.parentOf(tip);
if (tip == null) {
throw new IllegalArgumentException(parent + " is not a parent of " + node);
} else if (tip.equals(parent)) {
list.add(parent);
return list;
}
}
}
/** Returns the common parent of the two given elements. */
private static <T> Optional<T> lowestCommonAncestor(TreeDef.Parented<T> treeDef, T nodeA, T nodeB) {
class TreeSearcher {
private T tip;
private final Set<T> visited;
public TreeSearcher(T start) {
this.tip = start;
this.visited = new HashSet<>();
}
public boolean hasMore() {
return tip != null;
}
public Optional<T> march(TreeSearcher other) {
if (other.visited.contains(tip)) {
return Optional.of(tip);
} else {
visited.add(tip);
tip = treeDef.parentOf(tip);
return Optional.empty();
}
}
}
// make a list of a's parents (bottom of stack is 'a' itself, top of is root)
TreeSearcher searchA = new TreeSearcher(nodeA);
TreeSearcher searchB = new TreeSearcher(nodeB);
Optional<T> commonAncestor = searchB.march(searchA);
// so long as both searches
while (searchA.hasMore() && searchB.hasMore()) {
commonAncestor = searchA.march(searchB);
if (commonAncestor.isPresent()) {
return commonAncestor;
}
commonAncestor = searchB.march(searchA);
if (commonAncestor.isPresent()) {
return commonAncestor;
}
}
while (searchA.hasMore() && !commonAncestor.isPresent()) {
commonAncestor = searchA.march(searchB);
}
while (searchB.hasMore() && !commonAncestor.isPresent()) {
commonAncestor = searchB.march(searchA);
}
return commonAncestor;
}
/** 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));
}
/** Returns the common parent of N elements. */
public static <T> Optional<T> lowestCommonAncestor(TreeDef.Parented<T> treeDef, List<T> nodes) {
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 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) {
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 path of the given node, using {@code /} as the path delimiter.
*
* @see #path(com.diffplug.common.base.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, "/");
}
/**
* 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.base.TreeDef.Parented, Object, Function, String)
*/
public static <T> String path(TreeDef.Parented<T> treeDef, T node) {
return path(treeDef, node, Object::toString);
}
/**
* 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) {
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);
}
/**
* 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) {
return findByPath(treeDef, node, path, (treeSide, pathSide) -> {
return Objects.equals(treeMapper.apply(treeSide), pathMapper.apply(pathSide));
});
}
/**
* 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);
}
/**
* 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);
}
/**
* 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, " ");
}
/**
* 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();
}
private static <T> void toStringHelper(TreeDef<T> treeDef, T root, Function<? super T, String> toString, String indent, StringBuilder builder, String 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);
}
}
}