package xyz.zhouxy.plusone.commons.util; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.google.common.base.Preconditions; /** * TreeBuilder * * @author ZhouXY * @since 1.0 */ public class TreeBuilder { private final Function identityGetter; private final Function> parentIdentityGetter; private final BiConsumer addChildMethod; private final Comparator defaultComparator; public TreeBuilder(Function identityGetter, Function> parentIdentityGetter, BiConsumer addChild) { this(identityGetter, parentIdentityGetter, addChild, null); } public TreeBuilder(Function identityGetter, Function> parentIdentityGetter, BiConsumer addChild, Comparator defaultComparator) { this.identityGetter = identityGetter; this.parentIdentityGetter = parentIdentityGetter; this.addChildMethod = addChild; this.defaultComparator = defaultComparator; } /** * 将节点构建成树。使用 {@link #defaultComparator} 进行排序。如果 {@link #defaultComparator} *

* 注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 * * @param nodes 平铺的节点列表 */ public List buildTree(Collection nodes) { Preconditions.checkNotNull(nodes); return buildTreeInternal(nodes, this.defaultComparator); } /** * 将节点构建成树。 *

* !!注意:该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 * * @param nodes 平铺的节点列表 * @param comparator 用于节点的排序。 * 若为 {@code null},则使用 {@link #defaultComparator}; * 若 {@link #defaultComparator} 也为 {@code null},则不排序。 * 仅影响调用 addChild 的顺序,如果操作对象本身对应的控制了子节点的顺序,无法影响其相关逻辑。 */ public List buildTree(Collection nodes, @Nullable Comparator comparator) { Preconditions.checkNotNull(nodes); final Comparator c = (comparator != null) ? comparator : this.defaultComparator; return buildTreeInternal(nodes, c); } /** * 将节点构建成树。 *

* 注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 * * @param nodes 平铺的节点列表 * @param comparator 用于节点的排序。若为 {@code null},则不排序 */ private List buildTreeInternal(Collection nodes, @Nullable Comparator comparator) { final Collection allNodes; if (comparator == null) { allNodes = nodes; } else { allNodes = nodes.stream().sorted(comparator).collect(Collectors.toList()); } final Map identityNodeMap = allNodes.stream() .collect(Collectors.toMap(identityGetter, Function.identity(), (n1, n2) -> n1)); // 根节点 final List rootNodes = allNodes.stream() .filter(node -> !this.parentIdentityGetter.apply(node).isPresent()) .collect(Collectors.toList()); allNodes.forEach(node -> parentIdentityGetter.apply(node).ifPresent(parentIdentity -> { if (identityNodeMap.containsKey(parentIdentity)) { @SuppressWarnings("unchecked") TSubTree parentNode = (TSubTree) identityNodeMap.get(parentIdentity); addChildMethod.accept(parentNode, node); } })); return rootNodes; } }