From 95093becb0de527c88e2fbc594636878af99e7bf Mon Sep 17 00:00:00 2001 From: liqiang <1144388620@qq.com> Date: Sun, 8 Mar 2020 19:25:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=A0=91=E7=94=9F?= =?UTF-8?q?=E6=88=90=20=E6=96=B0=E5=8A=9F=E8=83=BD=20=20=E5=85=B7=E5=A4=87?= =?UTF-8?q?=E9=AB=98=E8=87=AA=E7=94=B1=20=E7=81=B5=E6=B4=BB=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/lang/tree/Convert.java | 15 +++ .../core/lang/tree/DefaultConverter.java | 20 +++ .../java/cn/hutool/core/lang/tree/Node.java | 71 +++++++++++ .../hutool/core/lang/tree/TreeNodeConfig.java | 117 ++++++++++++++++++ .../cn/hutool/core/lang/tree/TreeNodeMap.java | 94 ++++++++++++++ .../cn/hutool/core/lang/tree/TreeUtil.java | 92 ++++++++++++++ .../hutool/core/lang/tree/package-info.java | 1 + .../java/cn/hutool/core/lang/TreeTest.java | 62 ++++++++++ 8 files changed, 472 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/Convert.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeConfig.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeMap.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/tree/package-info.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Convert.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Convert.java new file mode 100644 index 000000000..5110af25b --- /dev/null +++ b/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 { + /** + * @param object 源数据实体 + * @param treeNode 树节点实体 + */ + void convert(T object, TreeNodeMap treeNode); +} + diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java new file mode 100644 index 000000000..ff151eda0 --- /dev/null +++ b/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 { + @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); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java new file mode 100644 index 000000000..354174753 --- /dev/null +++ b/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; + } + + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeConfig.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeConfig.java new file mode 100644 index 000000000..c1c1b10f6 --- /dev/null +++ b/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; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeMap.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeMap.java new file mode 100644 index 000000000..581b66c99 --- /dev/null +++ b/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 { + + private static final long serialVersionUID = 8376668307601977428L; + + private TreeNodeConfig treeNodeConfig; + + public TreeNodeMap() { + this.treeNodeConfig = TreeNodeConfig.getDefaultConfig(); + } + + public TreeNodeMap(TreeNodeConfig treeNodeConfig) { + this.treeNodeConfig = treeNodeConfig; + } + + public T getId() { + return (T) super.get(treeNodeConfig.getIdKey()); + } + + public void setId(String id) { + super.put(treeNodeConfig.getIdKey(), id); + } + + public T getParentId() { + return (T) super.get(treeNodeConfig.getParentIdKey()); + } + + public void setParentId(String parentId) { + super.put(treeNodeConfig.getParentIdKey(), parentId); + } + + public 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 getChildren() { + return (List) super.get(treeNodeConfig.getChildrenKey()); + } + + public void setChildren(List 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; + } + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java new file mode 100644 index 000000000..3edd7e99c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java @@ -0,0 +1,92 @@ +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 build(List list, Object parentId) { + return build(list, parentId, TreeNodeConfig.getDefaultConfig(), new DefaultConverter()); + } + + /** + * 树构建 + */ + public static List build(List list, Object parentId, Convert convert) { + return build(list, parentId, TreeNodeConfig.getDefaultConfig(), convert); + } + + /** + * 树构建 + * + * @param list 源数据集合 + * @param parentId 最顶层父id值 一般为 0 之类 + * @param treeNodeConfig 配置 + * @param convert 转换器 + * @param 转换的实体 为数据源里的对象类型 + * @return + */ + public static List build(List list, Object parentId, TreeNodeConfig treeNodeConfig, Convert convert) { + List treeNodes = CollectionUtil.newArrayList(); + for (T obj : list) { + TreeNodeMap treeNode = new TreeNodeMap(treeNodeConfig); + convert.convert(obj, treeNode); + treeNodes.add(treeNode); + } + + List 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 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 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); + } + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/package-info.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/package-info.java new file mode 100644 index 000000000..ff040f6cf --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/package-info.java @@ -0,0 +1 @@ +package cn.hutool.core.lang.tree; \ No newline at end of file diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java new file mode 100644 index 000000000..a92bb54b4 --- /dev/null +++ b/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 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 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 treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig, + new Convert() { + @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); + } +} From 864bf93a27f6ff65c421f9e1c583cc7ea972bd67 Mon Sep 17 00:00:00 2001 From: liqiang <1144388620@qq.com> Date: Sun, 8 Mar 2020 19:27:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=A0=91=E7=94=9F?= =?UTF-8?q?=E6=88=90=20=E6=96=B0=E5=8A=9F=E8=83=BD=20=20=E5=85=B7=E5=A4=87?= =?UTF-8?q?=E9=AB=98=E8=87=AA=E7=94=B1=20=E7=81=B5=E6=B4=BB=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java index 3edd7e99c..45e9f6003 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java @@ -65,6 +65,7 @@ public class TreeUtil { * @param maxDeep 最大递归深度 可能为null即不限制 */ private static void innerBuild(List treeNodes, TreeNodeMap parentNode, int deep, Integer maxDeep) { + if (CollectionUtil.isEmpty(treeNodes)) { return; }