Browse Source

!100 通用树生成 新功能
Merge pull request !100 from 凉白开/v5-dev

Looly 5 years ago
parent
commit
e2af8b96a5

+ 15 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/Convert.java

@@ -0,0 +1,15 @@
+package cn.hutool.core.lang.tree;
+
+/**
+ * 树节点转换器 可以参考{{@link DefaultConverter}}
+ *
+ * @author liangbaikai
+ */
+public interface Convert<T, TreeNodeMap> {
+    /**
+     * @param object   源数据实体
+     * @param treeNode 树节点实体
+     */
+    void convert(T object, TreeNodeMap treeNode);
+}
+

+ 20 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java

@@ -0,0 +1,20 @@
+package cn.hutool.core.lang.tree;
+
+/**
+ * 默认的简单转换器
+ *
+ * @author liangbaikai
+ */
+public class DefaultConverter implements Convert<Node, TreeNodeMap> {
+    @Override
+    public void convert(Node object, TreeNodeMap treeNode) {
+        treeNode.setId(object.getId());
+        treeNode.setParentId(object.getPid());
+        treeNode.setWeight(object.getWeight());
+        treeNode.setName(object.getName());
+
+        //扩展字段
+        // treeNode.extra("other",11);
+        // treeNode.extra("other2",object.getXXX);
+    }
+}

+ 71 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java

@@ -0,0 +1,71 @@
+package cn.hutool.core.lang.tree;
+
+
+/**
+ * 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名,在你的项目里它可以是部门实体 地区实体等任意类树节点实体
+ * 类树节点实体: 包含key,父Key.不限于这些属性的可以构造成一颗树的实体对象
+ *
+ * @author liangbaikai
+ */
+public class Node {
+
+    // ID
+    private String id;
+
+    // 上级ID
+    private String pid;
+
+    // 名称
+    private String name;
+
+    // 顺序 越小优先级越高 默认0
+    private Comparable weight = 0;
+
+
+    public Node() {
+    }
+
+    public Node(String id, String pid, String name, Comparable weight) {
+        this.id = id;
+        this.pid = pid;
+        this.name = name;
+        if (weight != null) {
+            this.weight = weight;
+        }
+
+    }
+
+    public Comparable getWeight() {
+        return weight;
+    }
+
+    public void setWeight(Comparable weight) {
+        this.weight = weight;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPid() {
+        return pid;
+    }
+
+    public void setPid(String pid) {
+        this.pid = pid;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+}

+ 117 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeConfig.java

@@ -0,0 +1,117 @@
+package cn.hutool.core.lang.tree;
+
+/**
+ * 树配置属性相关
+ *
+ * @author liangbaikai
+ */
+public class TreeNodeConfig {
+
+    /**
+     * 默认属性配置对象
+     */
+    private static TreeNodeConfig defaultConfig = new TreeNodeConfig();
+
+    // 树节点默认属性常量 当然你可以重新设置
+    /**
+     * 节点 id 名称
+     */
+    static final String TREE_ID = "id";
+    /**
+     * 节点 parentId 父id名称
+     */
+    static final String TREE_PARENT_ID = "parentId";
+    /**
+     * 节点 name 显示名称
+     */
+    static final String TREE_NAME = "name";
+    /**
+     * 节点 weight 顺序名称
+     */
+    static final String TREE_WEIGHT = "weight";
+    /**
+     * 节点 name 子节点名称
+     */
+    static final String TREE_CHILDREN = "children";
+
+
+    static {
+        //init
+        defaultConfig.setIdKey(TREE_ID);
+        defaultConfig.setWeightKey(TREE_WEIGHT);
+        defaultConfig.setNameKey(TREE_NAME);
+        defaultConfig.setChildrenKey(TREE_CHILDREN);
+        defaultConfig.setParentIdKey(TREE_PARENT_ID);
+    }
+
+    // 属性名配置字段
+    private String idKey;
+    private String parentIdKey;
+    private String weightKey;
+    private String nameKey;
+    private String childrenKey;
+
+    // 可以配置递归深度 从0开始计算 默认此配置为空,即不限制
+    private Integer deep;
+
+
+    public String getIdKey() {
+        return getOrDefault(idKey, TREE_ID);
+    }
+
+    public void setIdKey(String idKey) {
+        this.idKey = idKey;
+    }
+
+    public String getWeightKey() {
+        return getOrDefault(weightKey, TREE_WEIGHT);
+    }
+
+    public void setWeightKey(String weightKey) {
+        this.weightKey = weightKey;
+    }
+
+    public String getNameKey() {
+        return getOrDefault(nameKey, TREE_NAME);
+    }
+
+    public void setNameKey(String nameKey) {
+        this.nameKey = nameKey;
+    }
+
+    public String getChildrenKey() {
+        return getOrDefault(childrenKey, TREE_CHILDREN);
+    }
+
+    public void setChildrenKey(String childrenKey) {
+        this.childrenKey = childrenKey;
+    }
+
+    public String getParentIdKey() {
+        return getOrDefault(parentIdKey, TREE_PARENT_ID);
+    }
+
+    public void setParentIdKey(String parentIdKey) {
+        this.parentIdKey = parentIdKey;
+    }
+
+    public String getOrDefault(String key, String defaultKey) {
+        if (key == null) {
+            return defaultKey;
+        }
+        return key;
+    }
+
+    public Integer getDeep() {
+        return deep;
+    }
+
+    public void setDeep(Integer deep) {
+        this.deep = deep;
+    }
+
+    public static TreeNodeConfig getDefaultConfig() {
+        return defaultConfig;
+    }
+
+}

+ 94 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeMap.java

@@ -0,0 +1,94 @@
+package cn.hutool.core.lang.tree;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * 通过转换器将你的实体转化为TreeNodeMap节点实体 属性都存在此处,属性有序,可支持排序
+ *
+ * @author liangbaikai
+ */
+public class TreeNodeMap extends LinkedHashMap implements Comparable<TreeNodeMap> {
+
+    private static final long serialVersionUID = 8376668307601977428L;
+
+    private TreeNodeConfig treeNodeConfig;
+
+    public TreeNodeMap() {
+        this.treeNodeConfig = TreeNodeConfig.getDefaultConfig();
+    }
+
+    public TreeNodeMap(TreeNodeConfig treeNodeConfig) {
+        this.treeNodeConfig = treeNodeConfig;
+    }
+
+    public <T> T getId() {
+        return (T) super.get(treeNodeConfig.getIdKey());
+    }
+
+    public void setId(String id) {
+        super.put(treeNodeConfig.getIdKey(), id);
+    }
+
+    public <T> T getParentId() {
+        return (T) super.get(treeNodeConfig.getParentIdKey());
+    }
+
+    public void setParentId(String parentId) {
+        super.put(treeNodeConfig.getParentIdKey(), parentId);
+    }
+
+    public <T> T getName() {
+        return (T) super.get(treeNodeConfig.getNameKey());
+    }
+
+    public void setName(String name) {
+        super.put(treeNodeConfig.getNameKey(), name);
+    }
+
+    public Comparable getWeight() {
+        return (Comparable) super.get(treeNodeConfig.getWeightKey());
+    }
+
+    public TreeNodeMap setWeight(Comparable weight) {
+        super.put(treeNodeConfig.getWeightKey(), weight);
+        return this;
+    }
+
+    public List<TreeNodeMap> getChildren() {
+        return (List<TreeNodeMap>) super.get(treeNodeConfig.getChildrenKey());
+    }
+
+    public void setChildren(List<TreeNodeMap> children) {
+        super.put(treeNodeConfig.getChildrenKey(), children);
+    }
+
+    /**
+     * 扩展属性
+     *
+     * @param key
+     * @param value
+     */
+    public void extra(String key, Object value) {
+        if (key != null && key != "") {
+            super.put(key, value);
+        }
+    }
+
+    /**
+     * 可以支持排序
+     *
+     * @param treeNodeMap weight值越小 优先级越高 默认为0
+     * @return
+     */
+    @Override
+    public int compareTo(TreeNodeMap treeNodeMap) {
+        try {
+            //可能为空
+            int res = this.getWeight().compareTo(treeNodeMap.getWeight());
+            return res;
+        } catch (NullPointerException e) {
+            return 0;
+        }
+    }
+}

+ 93 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java

@@ -0,0 +1,93 @@
+package cn.hutool.core.lang.tree;
+
+import cn.hutool.core.collection.CollectionUtil;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 树工具类 可参考  cn.hutool.core.lang.TreeTest
+ *
+ * @author liangbaikai
+ */
+public class TreeUtil {
+
+    /**
+     * 树构建
+     */
+    public static List<TreeNodeMap> build(List<Node> list, Object parentId) {
+        return build(list, parentId, TreeNodeConfig.getDefaultConfig(), new DefaultConverter());
+    }
+
+    /**
+     * 树构建
+     */
+    public static <T> List<TreeNodeMap> build(List<T> list, Object parentId, Convert<T, TreeNodeMap> convert) {
+        return build(list, parentId, TreeNodeConfig.getDefaultConfig(), convert);
+    }
+
+    /**
+     * 树构建
+     *
+     * @param list           源数据集合
+     * @param parentId       最顶层父id值 一般为 0 之类
+     * @param treeNodeConfig 配置
+     * @param convert        转换器
+     * @param <T>            转换的实体 为数据源里的对象类型
+     * @return
+     */
+    public static <T> List<TreeNodeMap> build(List<T> list, Object parentId, TreeNodeConfig treeNodeConfig, Convert<T, TreeNodeMap> convert) {
+        List<TreeNodeMap> treeNodes = CollectionUtil.newArrayList();
+        for (T obj : list) {
+            TreeNodeMap treeNode = new TreeNodeMap(treeNodeConfig);
+            convert.convert(obj, treeNode);
+            treeNodes.add(treeNode);
+        }
+
+        List<TreeNodeMap> finalTreeNodes = CollectionUtil.newArrayList();
+        for (TreeNodeMap treeNode : treeNodes) {
+            if (parentId.equals(treeNode.getParentId())) {
+                finalTreeNodes.add(treeNode);
+                innerBuild(treeNodes, treeNode, 0, treeNodeConfig.getDeep());
+            }
+        }
+        // 内存每层已经排过了 这是最外层排序
+        finalTreeNodes = finalTreeNodes.stream().sorted().collect(Collectors.toList());
+        return finalTreeNodes;
+    }
+
+    /**
+     * 递归处理
+     *
+     * @param treeNodes  数据集合
+     * @param parentNode 当前节点
+     * @param deep       已递归深度
+     * @param maxDeep    最大递归深度 可能为null即不限制
+     */
+    private static void innerBuild(List<TreeNodeMap> treeNodes, TreeNodeMap parentNode, int deep, Integer maxDeep) {
+
+        if (CollectionUtil.isEmpty(treeNodes)) {
+            return;
+        }
+        //maxDeep 可能为空
+        if (maxDeep != null && deep >= maxDeep) {
+            return;
+        }
+
+        // 每层排序 TreeNodeMap 实现了Comparable接口
+        treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
+        for (TreeNodeMap childNode : treeNodes) {
+            if (parentNode.getId().equals(childNode.getParentId())) {
+                List<TreeNodeMap> children = parentNode.getChildren();
+                if (children == null) {
+                    children = CollectionUtil.newArrayList();
+                    parentNode.setChildren(children);
+                }
+                children.add(childNode);
+                childNode.setParentId(parentNode.getId());
+                innerBuild(treeNodes, childNode, deep + 1, maxDeep);
+            }
+        }
+    }
+
+}

+ 1 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/package-info.java

@@ -0,0 +1 @@
+package cn.hutool.core.lang.tree;

+ 62 - 0
hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java

@@ -0,0 +1,62 @@
+package cn.hutool.core.lang;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.tree.*;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * 通用树测试
+ *
+ * @author liangbaikai
+ */
+public class TreeTest {
+    // 模拟数据
+    static List<Node> nodeList = CollectionUtil.newArrayList();
+
+    static {
+        // 模拟数据
+        nodeList.add(new Node("1", "0", "系统管理", 5));
+        nodeList.add(new Node("11", "1", "用户管理", 222222));
+        nodeList.add(new Node("111", "11", "用户添加", 0));
+        nodeList.add(new Node("2", "0", "店铺管理", 1));
+        nodeList.add(new Node("21", "2", "商品管理", 44));
+        nodeList.add(new Node("221", "2", "商品管理2", 2));
+    }
+
+
+    @Test
+    public void sampleTree() {
+        List<TreeNodeMap> treeNodes = TreeUtil.build(nodeList, "0");
+
+        System.out.println(treeNodes);
+    }
+
+    @Test
+    public void tree() {
+
+        //配置
+        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
+        // 自定义属性名 都要默认值的
+        treeNodeConfig.setWeightKey("order");
+        treeNodeConfig.setDeep(3);
+        treeNodeConfig.setIdKey("rid");
+
+        //转换器
+        List<TreeNodeMap> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,
+                new Convert<Node, TreeNodeMap>() {
+                    @Override
+                    public void convert(Node object, TreeNodeMap treeNode) {
+                        treeNode.setId(object.getId());
+                        treeNode.setParentId(object.getPid());
+                        treeNode.setWeight(object.getWeight());
+                        treeNode.setName(object.getName());
+                        // 扩展属性 ...
+                        treeNode.extra("extraField", 666);
+                        treeNode.extra("other", new Object());
+                    }
+                });
+        System.out.println(treeNodes);
+    }
+}