mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
通用树生成 新功能 具备高自由 灵活 扩展特性
This commit is contained in:
parent
fb4af337ea
commit
95093becb0
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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
hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java
Normal file
71
hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package cn.hutool.core.lang.tree;
|
62
hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java
Normal file
62
hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user