|
@@ -0,0 +1,161 @@
|
|
|
+package edu.travel.commodity.utils;
|
|
|
+
|
|
|
+import java.util.*;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.function.BiConsumer;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 通用树形结构构建工具类
|
|
|
+ *
|
|
|
+ * <p><strong>功能特性:</strong></p>
|
|
|
+ * <ul>
|
|
|
+ * <li>支持任意 Java 对象(实体类/DTO/VO)</li>
|
|
|
+ * <li>自动检测循环依赖(死循环)</li>
|
|
|
+ * <li>零依赖(无需继承接口或父类)</li>
|
|
|
+ * <li>编译期类型安全检查</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p><strong>使用示例:</strong></p>
|
|
|
+ * <pre>{@code
|
|
|
+ * List<Department> tree = TreeUtils.buildTree(
|
|
|
+ * departments,
|
|
|
+ * Department::getId,
|
|
|
+ * Department::getParentId,
|
|
|
+ * Department::setChildren
|
|
|
+ * );
|
|
|
+ * }</pre>
|
|
|
+ */
|
|
|
+public class TreeUtils {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建树形结构的核心方法
|
|
|
+ *
|
|
|
+ * @param nodes 待处理的节点列表(必须包含所有节点)
|
|
|
+ * @param idGetter 节点ID的获取方法(方法引用,如:User::getId)
|
|
|
+ * @param pidGetter 父节点ID的获取方法(方法引用,如:User::getParentId)
|
|
|
+ * @param childrenSetter 子节点列表的设置方法(如:(node, children) -> node.setChildren(children))
|
|
|
+ * @param <T> 节点类型(如:User.class)
|
|
|
+ * @param <ID> ID 的类型(需正确实现 equals 和 hashCode)
|
|
|
+ * @return 树形结构的根节点列表
|
|
|
+ *
|
|
|
+ * @throws IllegalArgumentException 如果存在以下情况:
|
|
|
+ * <ul>
|
|
|
+ * <li>节点列表为null</li>
|
|
|
+ * <li>存在重复ID</li>
|
|
|
+ * <li>ID字段为null</li>
|
|
|
+ * </ul>
|
|
|
+ * @throws IllegalStateException 如果检测到循环依赖
|
|
|
+ */
|
|
|
+ public static <T, ID> List<T> buildTree(List<T> nodes,
|
|
|
+ Function<T, ID> idGetter,
|
|
|
+ Function<T, ID> pidGetter,
|
|
|
+ BiConsumer<T, List<T>> childrenSetter) {
|
|
|
+ // 防御性拷贝,避免修改原始数据
|
|
|
+ List<T> copyNodes = new ArrayList<>(Optional.ofNullable(nodes).orElseGet(ArrayList::new));
|
|
|
+
|
|
|
+ //========== 数据校验 ==========//
|
|
|
+ validateNodes(copyNodes, idGetter);
|
|
|
+
|
|
|
+ //========== 初始化数据结构 ==========//
|
|
|
+ Map<ID, T> nodeMap = new HashMap<>(copyNodes.size());
|
|
|
+ List<T> rootNodes = new ArrayList<>();
|
|
|
+
|
|
|
+ // 构建 id -> node 映射表
|
|
|
+ copyNodes.forEach(node -> nodeMap.put(idGetter.apply(node), node));
|
|
|
+
|
|
|
+ //========== 构建父子关系 ==========//
|
|
|
+ for (T node : copyNodes) {
|
|
|
+ ID currentId = idGetter.apply(node);
|
|
|
+ ID parentId = pidGetter.apply(node);
|
|
|
+
|
|
|
+ // 判断根节点条件(父ID为空/等于自身/不存在于Map中)
|
|
|
+ if (isRootNode(parentId, currentId, nodeMap)) {
|
|
|
+ rootNodes.add(node);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取父节点并建立关联
|
|
|
+ T parent = nodeMap.get(parentId);
|
|
|
+ if (parent != null) {
|
|
|
+ linkParentAndChild(parent, node, childrenSetter);
|
|
|
+ checkCircularDependency(parent, currentId, idGetter, pidGetter, nodeMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return rootNodes;
|
|
|
+ }
|
|
|
+
|
|
|
+ //========== 私有方法 ==========//
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 数据校验
|
|
|
+ */
|
|
|
+ private static <T, ID> void validateNodes(List<T> nodes, Function<T, ID> idGetter) {
|
|
|
+ if (nodes == null) {
|
|
|
+ throw new IllegalArgumentException("节点列表不能为null");
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<ID> idSet = new HashSet<>();
|
|
|
+ for (T node : nodes) {
|
|
|
+ ID id = idGetter.apply(node);
|
|
|
+ if (id == null) {
|
|
|
+ throw new IllegalArgumentException("节点存在空ID: " + node);
|
|
|
+ }
|
|
|
+ if (idSet.contains(id)) {
|
|
|
+ throw new IllegalArgumentException("存在重复ID: " + id);
|
|
|
+ }
|
|
|
+ idSet.add(id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为根节点
|
|
|
+ */
|
|
|
+ private static <ID> boolean isRootNode(ID parentId, ID currentId, Map<ID, ?> nodeMap) {
|
|
|
+ return parentId == null
|
|
|
+ || parentId.equals(currentId)
|
|
|
+ || !nodeMap.containsKey(parentId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 建立父子关联关系
|
|
|
+ */
|
|
|
+ private static <T> void linkParentAndChild(T parent,
|
|
|
+ T child,
|
|
|
+ BiConsumer<T, List<T>> childrenSetter) {
|
|
|
+ List<T> children = new ArrayList<>();
|
|
|
+ childrenSetter.accept(parent, children);
|
|
|
+ children.add(child);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 循环依赖检测(核心安全机制)
|
|
|
+ */
|
|
|
+ private static <T, ID> void checkCircularDependency(T parent,
|
|
|
+ ID childId,
|
|
|
+ Function<T, ID> idGetter,
|
|
|
+ Function<T, ID> pidGetter,
|
|
|
+ Map<ID, T> nodeMap) {
|
|
|
+ Set<ID> visited = new HashSet<>();
|
|
|
+ T current = parent;
|
|
|
+
|
|
|
+ while (current != null) {
|
|
|
+ ID currentParentId = pidGetter.apply(current);
|
|
|
+ if (currentParentId == null) break;
|
|
|
+
|
|
|
+ // 发现直接循环(A→B→A)
|
|
|
+ if (currentParentId.equals(childId)) {
|
|
|
+ throw new IllegalStateException("检测到循环依赖:节点 "
|
|
|
+ + idGetter.apply(current) + " → " + childId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发现间接循环(A→B→C→A)
|
|
|
+ if (visited.contains(currentParentId)) {
|
|
|
+ throw new IllegalStateException("检测到循环依赖路径:" + visited);
|
|
|
+ }
|
|
|
+
|
|
|
+ visited.add(currentParentId);
|
|
|
+ current = nodeMap.get(currentParentId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|