diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec1cad10..c1adfd8c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 * 【core 】 修改FastDateParser策略,与JDK保持一致(issue#I1AXIN@Gitee) +* 【core 】 增加tree(树状结构)(pr#100@Gitee) ### Bug修复 * 【setting】 修复Props.toBean方法null的问题 * 【core 】 修复DataUtil.parseLocalDateTime无时间部分报错问题(issue#I1B18H@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ConsistentHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/ConsistentHash.java index 0b426065b..637af8b01 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ConsistentHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ConsistentHash.java @@ -23,7 +23,7 @@ public class ConsistentHash implements Serializable{ /** 复制的节点个数 */ private final int numberOfReplicas; /** 一致性Hash环 */ - private final SortedMap circle = new TreeMap(); + private final SortedMap circle = new TreeMap<>(); /** * 构造,使用Java默认的Hash算法 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java index 442bfd0a1..e490bdb48 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java @@ -959,7 +959,18 @@ public class Validator { * @return 是否为汉字 */ public static boolean isChinese(CharSequence value) { - return isMactchRegex("^" + ReUtil.RE_CHINESE + "+$", value); + return isMactchRegex("^" + ReUtil.RE_CHINESES + "$", value); + } + + /** + * 验证是否包含汉字 + * + * @param value 值 + * @return 是否包含汉字 + * @since 5.2.1 + */ + public static boolean hasChinese(CharSequence value) { + return ReUtil.contains(ReUtil.RE_CHINESES, value); } /** 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 deleted file mode 100644 index 5110af25b..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Convert.java +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index ff151eda0..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 354174753..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Node.java +++ /dev/null @@ -1,71 +0,0 @@ -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/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java new file mode 100644 index 000000000..e1f92ef1c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java @@ -0,0 +1,121 @@ +package cn.hutool.core.lang.tree; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + * 通过转换器将你的实体转化为TreeNodeMap节点实体 属性都存在此处,属性有序,可支持排序 + * + * @param ID类型 + * @author liangbaikai + * @since 5.2.1 + */ +public class Tree extends LinkedHashMap implements Comparable> { + private static final long serialVersionUID = 1L; + + private TreeNodeConfig treeNodeConfig; + + public Tree() { + this(null); + } + + /** + * 构造 + * + * @param treeNodeConfig TreeNode配置 + */ + public Tree(TreeNodeConfig treeNodeConfig) { + super(); + this.treeNodeConfig = ObjectUtil.defaultIfNull( + treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG); + } + + /** + * 获取节点ID + * + * @return 节点ID + */ + @SuppressWarnings("unchecked") + public T getId() { + return (T) this.get(treeNodeConfig.getIdKey()); + } + + /** + * 设置节点ID + * + * @param id 节点ID + * @return this + */ + public Tree setId(T id) { + this.put(treeNodeConfig.getIdKey(), id); + return this; + } + + /** + * 获取父节点ID + * + * @return 父节点ID + */ + @SuppressWarnings("unchecked") + public T getParentId() { + return (T) this.get(treeNodeConfig.getParentIdKey()); + } + + public Tree setParentId(T parentId) { + this.put(treeNodeConfig.getParentIdKey(), parentId); + return this; + } + + @SuppressWarnings("unchecked") + public T getName() { + return (T) this.get(treeNodeConfig.getNameKey()); + } + + public Tree setName(Object name) { + this.put(treeNodeConfig.getNameKey(), name); + return this; + } + + public Comparable getWeight() { + return (Comparable) this.get(treeNodeConfig.getWeightKey()); + } + + public Tree setWeight(Comparable weight) { + this.put(treeNodeConfig.getWeightKey(), weight); + return this; + } + + @SuppressWarnings("unchecked") + public List> getChildren() { + return (List>) this.get(treeNodeConfig.getChildrenKey()); + } + + public void setChildren(List> children) { + this.put(treeNodeConfig.getChildrenKey(), children); + } + + /** + * 扩展属性 + * + * @param key 键 + * @param value 扩展值 + */ + public void putExtra(String key, Object value) { + Assert.notEmpty(key, "Key must be not empty !"); + this.put(key, value); + } + + @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"}) + @Override + public int compareTo(Tree tree) { + final Comparable weight = this.getWeight(); + if (null != weight) { + final Comparable weightOther = tree.getWeight(); + return weight.compareTo(weightOther); + } + return 0; + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java new file mode 100644 index 000000000..6bec83a02 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java @@ -0,0 +1,146 @@ +package cn.hutool.core.lang.tree; + + +/** + * 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名
+ * 在你的项目里它可以是部门实体、地区实体等任意类树节点实体 + * 类树节点实体: 包含key,父Key.不限于这些属性的可以构造成一颗树的实体对象 + * + * @author liangbaikai + */ +public class TreeNode implements Comparable> { + + /** + * ID + */ + private T id; + + /** + * 父节点ID + */ + private T parentId; + + /** + * 名称 + */ + private CharSequence name; + + /** + * 顺序 越小优先级越高 默认0 + */ + private Comparable weight = 0; + + + /** + * 空构造 + */ + public TreeNode() { + } + + /** + * 构造 + * + * @param id ID + * @param parentId 父节点ID + * @param name 名称 + * @param weight 权重 + */ + public TreeNode(T id, T parentId, String name, Comparable weight) { + this.id = id; + this.parentId = parentId; + this.name = name; + if (weight != null) { + this.weight = weight; + } + + } + + /** + * 获取ID + * + * @return ID + */ + public T getId() { + return id; + } + + /** + * 设置ID + * + * @param id ID + */ + public void setId(T id) { + this.id = id; + } + + /** + * 获取父节点ID + * + * @return 父节点ID + */ + public T getParentId() { + return this.parentId; + } + + /** + * 设置父节点ID + * + * @param parentId 父节点ID + * @return 父节点ID + */ + public TreeNode setParentId(T parentId) { + this.parentId = parentId; + return this; + } + + /** + * 获取节点标签名称 + * + * @return 节点标签名称 + */ + public CharSequence getName() { + return name; + } + + /** + * 设置节点标签名称 + * + * @param name 节点标签名称 + * @return this + */ + public TreeNode setName(CharSequence name) { + this.name = name; + return this; + } + + /** + * 获取权重 + * + * @return 权重 + */ + public Comparable getWeight() { + return weight; + } + + /** + * 设置权重 + * + * @param weight 权重 + * @return this + */ + public TreeNode setWeight(Comparable weight) { + this.weight = weight; + return this; + } + + @SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"}) + @Override + public int compareTo(Tree tree) { + final Comparable weight = this.getWeight(); + if (null != weight) { + final Comparable weightOther = tree.getWeight(); + return weight.compareTo(weightOther); + } + return 0; + } +} 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 index c1c1b10f6..f4b79cf66 100644 --- 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 @@ -7,111 +7,139 @@ package cn.hutool.core.lang.tree; */ public class TreeNodeConfig { - /** - * 默认属性配置对象 - */ - private static TreeNodeConfig defaultConfig = new TreeNodeConfig(); + /** + * 默认属性配置对象 + */ + public static TreeNodeConfig DEFAULT_CONFIG = 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"; + // 属性名配置字段 + private String idKey = "id"; + private String parentIdKey = "parentId"; + private String weightKey = "weight"; + private String nameKey = "name"; + private String childrenKey = "children"; + // 可以配置递归深度 从0开始计算 默认此配置为空,即不限制 + private Integer deep; - static { - //init - defaultConfig.setIdKey(TREE_ID); - defaultConfig.setWeightKey(TREE_WEIGHT); - defaultConfig.setNameKey(TREE_NAME); - defaultConfig.setChildrenKey(TREE_CHILDREN); - defaultConfig.setParentIdKey(TREE_PARENT_ID); - } + /** + * 获取ID对应的名称 + * + * @return ID对应的名称 + */ + public String getIdKey() { + return this.idKey; + } - // 属性名配置字段 - private String idKey; - private String parentIdKey; - private String weightKey; - private String nameKey; - private String childrenKey; + /** + * 设置ID对应的名称 + * + * @param idKey ID对应的名称 + * @return this + */ + public TreeNodeConfig setIdKey(String idKey) { + this.idKey = idKey; + return this; + } - // 可以配置递归深度 从0开始计算 默认此配置为空,即不限制 - private Integer deep; + /** + * 获取权重对应的名称 + * + * @return 权重对应的名称 + */ + public String getWeightKey() { + return this.weightKey; + } + + /** + * 设置权重对应的名称 + * + * @param weightKey 权重对应的名称 + * @return this + */ + public TreeNodeConfig setWeightKey(String weightKey) { + this.weightKey = weightKey; + return this; + } + + /** + * 获取节点名对应的名称 + * + * @return 节点名对应的名称 + */ + public String getNameKey() { + return this.nameKey; + } + + /** + * 设置节点名对应的名称 + * + * @param nameKey 节点名对应的名称 + * @return this + */ + public TreeNodeConfig setNameKey(String nameKey) { + this.nameKey = nameKey; + return this; + } + + /** + * 获取子点对应的名称 + * + * @return 子点对应的名称 + */ + public String getChildrenKey() { + return this.childrenKey; + } + + /** + * 设置子点对应的名称 + * + * @param childrenKey 子点对应的名称 + * @return this + */ + public TreeNodeConfig setChildrenKey(String childrenKey) { + this.childrenKey = childrenKey; + return this; + } + + /** + * 获取父节点ID对应的名称 + * + * @return 父点对应的名称 + */ + public String getParentIdKey() { + return this.parentIdKey; + } - public String getIdKey() { - return getOrDefault(idKey, TREE_ID); - } + /** + * 设置父点对应的名称 + * + * @param parentIdKey 父点对应的名称 + * @return this + */ + public TreeNodeConfig setParentIdKey(String parentIdKey) { + this.parentIdKey = parentIdKey; + return this; + } - 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; - } + /** + * 获取递归深度 + * + * @return 递归深度 + */ + public Integer getDeep() { + return this.deep; + } + /** + * 设置递归深度 + * + * @param deep 递归深度 + * @return this + */ + public TreeNodeConfig setDeep(Integer deep) { + this.deep = deep; + return this; + } } 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 deleted file mode 100644 index 581b66c99..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNodeMap.java +++ /dev/null @@ -1,94 +0,0 @@ -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 index 45e9f6003..e9e1045e7 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 @@ -1,6 +1,8 @@ package cn.hutool.core.lang.tree; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.tree.parser.DefaultNodeParser; +import cn.hutool.core.lang.tree.parser.NodeParser; import java.util.List; import java.util.stream.Collectors; @@ -12,82 +14,89 @@ import java.util.stream.Collectors; */ 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) { + return build(list, 0); + } - /** - * 树构建 - */ - public static List build(List list, Object parentId, Convert convert) { - return build(list, parentId, TreeNodeConfig.getDefaultConfig(), convert); - } + /** + * 树构建 + */ + public static List> build(List> list, T parentId) { + return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, new DefaultNodeParser<>()); + } - /** - * 树构建 - * - * @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); - } + /** + * 树构建 + */ + public static List> build(List list, E parentId, NodeParser nodeParser) { + return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, nodeParser); + } - 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 list 源数据集合 + * @param parentId 最顶层父id值 一般为 0 之类 + * @param treeNodeConfig 配置 + * @param nodeParser 转换器 + * @param 转换的实体 为数据源里的对象类型 + * @return List + */ + public static List> build(List list, E parentId, TreeNodeConfig treeNodeConfig, NodeParser nodeParser) { + List> treeNodes = CollectionUtil.newArrayList(); + for (T obj : list) { + Tree treeNode = new Tree<>(treeNodeConfig); + nodeParser.parse(obj, treeNode); + treeNodes.add(treeNode); + } - /** - * 递归处理 - * - * @param treeNodes 数据集合 - * @param parentNode 当前节点 - * @param deep 已递归深度 - * @param maxDeep 最大递归深度 可能为null即不限制 - */ - private static void innerBuild(List treeNodes, TreeNodeMap parentNode, int deep, Integer maxDeep) { + List> finalTreeNodes = CollectionUtil.newArrayList(); + for (Tree 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; + } - if (CollectionUtil.isEmpty(treeNodes)) { - return; - } - //maxDeep 可能为空 - if (maxDeep != null && deep >= maxDeep) { - return; - } + /** + * 递归处理 + * + * @param treeNodes 数据集合 + * @param parentNode 当前节点 + * @param deep 已递归深度 + * @param maxDeep 最大递归深度 可能为null即不限制 + */ + private static void innerBuild(List> treeNodes, Tree parentNode, int deep, Integer maxDeep) { - // 每层排序 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); - } - } - } + if (CollectionUtil.isEmpty(treeNodes)) { + return; + } + //maxDeep 可能为空 + if (maxDeep != null && deep >= maxDeep) { + return; + } + + // 每层排序 TreeNodeMap 实现了Comparable接口 + treeNodes = treeNodes.stream().sorted().collect(Collectors.toList()); + for (Tree 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 index ff040f6cf..db431330d 100644 --- 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 @@ -1 +1,15 @@ +/** + * 提供通用树生成,特点: + *
+ *     1、每个字段可自定义
+ *     2、支持排序 树深度配置,自定义转换器等
+ *     3、支持额外属性扩展
+ *     4、贴心 许多属性,特性都有默认值处理
+ *     5、使用简单 可一行代码生成树
+ *     6、代码简洁轻量无额外依赖
+ * 
+ *
+ * @author liangbaikai(https://gitee.com/liangbaikai00/)
+ * @since 5.2.1
+ */
 package cn.hutool.core.lang.tree;
\ No newline at end of file
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/parser/DefaultNodeParser.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/parser/DefaultNodeParser.java
new file mode 100644
index 000000000..dcdec6221
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/parser/DefaultNodeParser.java
@@ -0,0 +1,25 @@
+package cn.hutool.core.lang.tree.parser;
+
+import cn.hutool.core.lang.tree.TreeNode;
+import cn.hutool.core.lang.tree.Tree;
+
+/**
+ * 默认的简单转换器
+ *
+ * @param  ID类型
+ * @author liangbaikai
+ */
+public class DefaultNodeParser implements NodeParser, T> {
+
+	@Override
+	public void parse(TreeNode object, Tree treeNode) {
+		treeNode.setId(object.getId());
+		treeNode.setParentId(object.getParentId());
+		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/parser/NodeParser.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/parser/NodeParser.java
new file mode 100644
index 000000000..ce99b817d
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/parser/NodeParser.java
@@ -0,0 +1,18 @@
+package cn.hutool.core.lang.tree.parser;
+
+import cn.hutool.core.lang.tree.Tree;
+
+/**
+ * 树节点解析器 可以参考{@link DefaultNodeParser}
+ *
+ * @param  转换的实体 为数据源里的对象类型
+ * @author liangbaikai
+ */
+public interface NodeParser {
+	/**
+	 * @param object   源数据实体
+	 * @param treeNode 树节点实体
+	 */
+	void parse(T object, Tree treeNode);
+}
+
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
index 409b8a552..1f4c32b52 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
@@ -1,6 +1,6 @@
 package cn.hutool.core.util;
 
-import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.exceptions.UtilException;
 import cn.hutool.core.lang.Holder;
@@ -8,30 +8,41 @@ import cn.hutool.core.lang.PatternPool;
 import cn.hutool.core.lang.Validator;
 import cn.hutool.core.lang.func.Func1;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * 正则相关工具类
* 常用正则请见 {@link Validator} - * + * * @author xiaoleilu */ public class ReUtil { - /** 正则表达式匹配中文汉字 */ + /** + * 正则表达式匹配中文汉字 + */ public final static String RE_CHINESE = "[\u4E00-\u9FFF]"; - /** 正则表达式匹配中文字符串 */ + /** + * 正则表达式匹配中文字符串 + */ public final static String RE_CHINESES = RE_CHINESE + "+"; - /** 正则中需要被转义的关键字 */ - public final static Set RE_KEYS = CollectionUtil.newHashSet(new Character[] { '$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|' }); + /** + * 正则中需要被转义的关键字 + */ + public final static Set RE_KEYS = CollUtil.newHashSet('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'); /** * 获得匹配的字符串,获得正则中分组0的内容 - * - * @param regex 匹配的正则 + * + * @param regex 匹配的正则 * @param content 被匹配的内容 * @return 匹配后得到的字符串,未匹配返回null * @since 3.1.2 @@ -42,8 +53,8 @@ public class ReUtil { /** * 获得匹配的字符串,获得正则中分组1的内容 - * - * @param regex 匹配的正则 + * + * @param regex 匹配的正则 * @param content 被匹配的内容 * @return 匹配后得到的字符串,未匹配返回null * @since 3.1.2 @@ -54,9 +65,9 @@ public class ReUtil { /** * 获得匹配的字符串 - * - * @param regex 匹配的正则 - * @param content 被匹配的内容 + * + * @param regex 匹配的正则 + * @param content 被匹配的内容 * @param groupIndex 匹配正则的分组序号 * @return 匹配后得到的字符串,未匹配返回null */ @@ -72,7 +83,7 @@ public class ReUtil { /** * 获得匹配的字符串,,获得正则中分组0的内容 - * + * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 * @return 匹配后得到的字符串,未匹配返回null @@ -84,7 +95,7 @@ public class ReUtil { /** * 获得匹配的字符串,,获得正则中分组1的内容 - * + * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 * @return 匹配后得到的字符串,未匹配返回null @@ -96,9 +107,9 @@ public class ReUtil { /** * 获得匹配的字符串,对应分组0表示整个匹配内容,1表示第一个括号分组内容,依次类推 - * - * @param pattern 编译后的正则模式 - * @param content 被匹配的内容 + * + * @param pattern 编译后的正则模式 + * @param content 被匹配的内容 * @param groupIndex 匹配正则的分组序号,0表示整个匹配内容,1表示第一个括号分组内容,依次类推 * @return 匹配后得到的字符串,未匹配返回null */ @@ -113,10 +124,10 @@ public class ReUtil { } return null; } - + /** * 获得匹配的字符串匹配到的所有分组 - * + * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 * @return 匹配后得到的字符串数组,按照分组顺序依次列出,未匹配到返回空列表,任何一个参数为null返回null @@ -128,9 +139,9 @@ public class ReUtil { /** * 获得匹配的字符串匹配到的所有分组 - * - * @param pattern 编译后的正则模式 - * @param content 被匹配的内容 + * + * @param pattern 编译后的正则模式 + * @param content 被匹配的内容 * @param withGroup0 是否包括分组0,此分组表示全匹配的信息 * @return 匹配后得到的字符串数组,按照分组顺序依次列出,未匹配到返回空列表,任何一个参数为null返回null * @since 4.0.13 @@ -156,9 +167,9 @@ public class ReUtil { * 从content中匹配出多个值并根据template生成新的字符串
* 例如:
* content 2013年5月 pattern (.*?)年(.*?)月 template: $1-$2 return 2013-5 - * - * @param pattern 匹配正则 - * @param content 被匹配的内容 + * + * @param pattern 匹配正则 + * @param content 被匹配的内容 * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 * @return 新字符串 */ @@ -189,9 +200,9 @@ public class ReUtil { * 匹配结束后会删除匹配内容之前的内容(包括匹配内容)
* 例如:
* content 2013年5月 pattern (.*?)年(.*?)月 template: $1-$2 return 2013-5 - * - * @param regex 匹配正则字符串 - * @param content 被匹配的内容 + * + * @param regex 匹配正则字符串 + * @param content 被匹配的内容 * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 * @return 按照template拼接后的字符串 */ @@ -210,10 +221,10 @@ public class ReUtil { * 匹配结束后会删除匹配内容之前的内容(包括匹配内容)
* 例如:
* content 2013年5月 pattern (.*?)年(.*?)月 template: $1-$2 return 2013-5 - * - * @param pattern 匹配正则 + * + * @param pattern 匹配正则 * @param contentHolder 被匹配的内容的Holder,value为内容正文,经过这个方法的原文将被去掉匹配之前的内容 - * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 + * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 * @return 新字符串 */ public static String extractMultiAndDelPre(Pattern pattern, Holder contentHolder, String template) { @@ -221,7 +232,7 @@ public class ReUtil { return null; } - HashSet varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet()); + HashSet varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet<>()); final CharSequence content = contentHolder.get(); Matcher matcher = pattern.matcher(content); @@ -240,10 +251,10 @@ public class ReUtil { * 从content中匹配出多个值并根据template生成新的字符串
* 例如:
* content 2013年5月 pattern (.*?)年(.*?)月 template: $1-$2 return 2013-5 - * - * @param regex 匹配正则字符串 + * + * @param regex 匹配正则字符串 * @param contentHolder 被匹配的内容的Holder,value为内容正文,经过这个方法的原文将被去掉匹配之前的内容 - * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 + * @param template 生成内容模板,变量 $1 表示group1的内容,以此类推 * @return 按照template拼接后的字符串 */ public static String extractMultiAndDelPre(String regex, Holder contentHolder, String template) { @@ -258,8 +269,8 @@ public class ReUtil { /** * 删除匹配的第一个内容 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 被匹配的内容 * @return 删除后剩余的内容 */ @@ -275,7 +286,7 @@ public class ReUtil { /** * 删除匹配的第一个内容 - * + * * @param pattern 正则 * @param content 被匹配的内容 * @return 删除后剩余的内容 @@ -290,8 +301,8 @@ public class ReUtil { /** * 删除匹配的全部内容 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 被匹配的内容 * @return 删除后剩余的内容 */ @@ -307,7 +318,7 @@ public class ReUtil { /** * 删除匹配的全部内容 - * + * * @param pattern 正则 * @param content 被匹配的内容 * @return 删除后剩余的内容 @@ -322,8 +333,8 @@ public class ReUtil { /** * 删除正则匹配到的内容之前的字符 如果没有找到,则返回原文 - * - * @param regex 定位正则 + * + * @param regex 定位正则 * @param content 被查找的内容 * @return 删除前缀后的新内容 */ @@ -343,8 +354,8 @@ public class ReUtil { /** * 取得内容中匹配的所有结果,获得匹配的所有结果中正则对应分组0的内容 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 被查找的内容 * @return 结果列表 * @since 3.1.2 @@ -355,8 +366,8 @@ public class ReUtil { /** * 取得内容中匹配的所有结果,获得匹配的所有结果中正则对应分组1的内容 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 被查找的内容 * @return 结果列表 * @since 3.1.2 @@ -367,24 +378,24 @@ public class ReUtil { /** * 取得内容中匹配的所有结果 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 被查找的内容 - * @param group 正则的分组 + * @param group 正则的分组 * @return 结果列表 * @since 3.0.6 */ public static List findAll(String regex, CharSequence content, int group) { - return findAll(regex, content, group, new ArrayList()); + return findAll(regex, content, group, new ArrayList<>()); } /** * 取得内容中匹配的所有结果 - * - * @param 集合类型 - * @param regex 正则 - * @param content 被查找的内容 - * @param group 正则的分组 + * + * @param 集合类型 + * @param regex 正则 + * @param content 被查找的内容 + * @param group 正则的分组 * @param collection 返回的集合类型 * @return 结果集 */ @@ -398,7 +409,7 @@ public class ReUtil { /** * 取得内容中匹配的所有结果,获得匹配的所有结果中正则对应分组0的内容 - * + * * @param pattern 编译后的正则模式 * @param content 被查找的内容 * @return 结果列表 @@ -410,7 +421,7 @@ public class ReUtil { /** * 取得内容中匹配的所有结果,获得匹配的所有结果中正则对应分组1的内容 - * + * * @param pattern 编译后的正则模式 * @param content 被查找的内容 * @return 结果列表 @@ -422,24 +433,24 @@ public class ReUtil { /** * 取得内容中匹配的所有结果 - * + * * @param pattern 编译后的正则模式 * @param content 被查找的内容 - * @param group 正则的分组 + * @param group 正则的分组 * @return 结果列表 * @since 3.0.6 */ public static List findAll(Pattern pattern, CharSequence content, int group) { - return findAll(pattern, content, group, new ArrayList()); + return findAll(pattern, content, group, new ArrayList<>()); } /** * 取得内容中匹配的所有结果 - * - * @param 集合类型 - * @param pattern 编译后的正则模式 - * @param content 被查找的内容 - * @param group 正则的分组 + * + * @param 集合类型 + * @param pattern 编译后的正则模式 + * @param content 被查找的内容 + * @param group 正则的分组 * @param collection 返回的集合类型 * @return 结果集 */ @@ -461,8 +472,8 @@ public class ReUtil { /** * 计算指定字符串中,匹配pattern的个数 - * - * @param regex 正则表达式 + * + * @param regex 正则表达式 * @param content 被查找的内容 * @return 匹配个数 */ @@ -478,7 +489,7 @@ public class ReUtil { /** * 计算指定字符串中,匹配pattern的个数 - * + * * @param pattern 编译后的正则模式 * @param content 被查找的内容 * @return 匹配个数 @@ -499,8 +510,8 @@ public class ReUtil { /** * 指定内容中是否有表达式匹配的内容 - * - * @param regex 正则表达式 + * + * @param regex 正则表达式 * @param content 被查找的内容 * @return 指定内容中是否有表达式匹配的内容 * @since 3.3.1 @@ -516,7 +527,7 @@ public class ReUtil { /** * 指定内容中是否有表达式匹配的内容 - * + * * @param pattern 编译后的正则模式 * @param content 被查找的内容 * @return 指定内容中是否有表达式匹配的内容 @@ -531,7 +542,7 @@ public class ReUtil { /** * 从字符串中获得第一个整数 - * + * * @param StringWithNumber 带数字的字符串 * @return 整数 */ @@ -541,8 +552,8 @@ public class ReUtil { /** * 给定内容是否匹配正则 - * - * @param regex 正则 + * + * @param regex 正则 * @param content 内容 * @return 正则为null或者""则不检查,返回true,内容为null返回false */ @@ -564,7 +575,7 @@ public class ReUtil { /** * 给定内容是否匹配正则 - * + * * @param pattern 模式 * @param content 内容 * @return 正则为null或者""则不检查,返回true,内容为null返回false @@ -580,18 +591,18 @@ public class ReUtil { /** * 正则替换指定值
* 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串 - * + * *

* 例如:原字符串是:中文1234,我想把1234换成(1234),则可以: - * + * *

 	 * ReUtil.replaceAll("中文1234", "(\\d+)", "($1)"))
-	 * 
+	 *
 	 * 结果:中文(1234)
 	 * 
- * - * @param content 文本 - * @param regex 正则 + * + * @param content 文本 + * @param regex 正则 * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容 * @return 处理后的文本 */ @@ -603,9 +614,9 @@ public class ReUtil { /** * 正则替换指定值
* 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串 - * - * @param content 文本 - * @param pattern {@link Pattern} + * + * @param content 文本 + * @param pattern {@link Pattern} * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容 * @return 处理后的文本 * @since 3.0.4 @@ -618,7 +629,7 @@ public class ReUtil { final Matcher matcher = pattern.matcher(content); boolean result = matcher.find(); if (result) { - final Set varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet()); + final Set varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet<>()); final StringBuffer sb = new StringBuffer(); do { String replacement = replacementTemplate; @@ -634,12 +645,12 @@ public class ReUtil { } return StrUtil.str(content); } - + /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param regex 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param regex 用于匹配的正则式 * @param replaceFun 决定如何替换的函数 * @return 替换后的文本 * @since 4.2.2 @@ -647,17 +658,17 @@ public class ReUtil { public static String replaceAll(CharSequence str, String regex, Func1 replaceFun) { return replaceAll(str, Pattern.compile(regex), replaceFun); } - + /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param pattern 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param pattern 用于匹配的正则式 * @param replaceFun 决定如何替换的函数,可能被多次调用(当有多个匹配时) * @return 替换后的字符串 * @since 4.2.2 */ - public static String replaceAll(CharSequence str, Pattern pattern, Func1 replaceFun){ + public static String replaceAll(CharSequence str, Pattern pattern, Func1 replaceFun) { if (StrUtil.isEmpty(str)) { return StrUtil.str(str); } @@ -677,7 +688,7 @@ public class ReUtil { /** * 转义字符,将正则的关键字转义 - * + * * @param c 字符 * @return 转义后的文本 */ @@ -692,7 +703,7 @@ public class ReUtil { /** * 转义字符串,将正则的关键字转义 - * + * * @param content 文本 * @return 转义后的文本 */ 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 index a92bb54b4..8f0195ea9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/TreeTest.java @@ -12,51 +12,50 @@ import java.util.List; * @author liangbaikai */ public class TreeTest { - // 模拟数据 - static List nodeList = CollectionUtil.newArrayList(); + // 模拟数据 + 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)); - } + static { + // 模拟数据 + nodeList.add(new TreeNode<>("1", "0", "系统管理", 5)); + nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222)); + nodeList.add(new TreeNode<>("111", "11", "用户添加", 0)); + + nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1)); + nodeList.add(new TreeNode<>("21", "2", "商品管理", 44)); + nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2)); + } - @Test - public void sampleTree() { - List treeNodes = TreeUtil.build(nodeList, "0"); + @Test + public void sampleTree() { + List> treeNodes = TreeUtil.build(nodeList, "0"); + for (Tree tree : treeNodes) { + Console.log(tree); + } + } - System.out.println(treeNodes); - } + @Test + public void tree() { - @Test - public void tree() { + //配置 + TreeNodeConfig treeNodeConfig = new TreeNodeConfig(); + // 自定义属性名 都要默认值的 + treeNodeConfig.setWeightKey("order"); + treeNodeConfig.setDeep(3); + treeNodeConfig.setIdKey("rid"); - //配置 - 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); - } + //转换器 + List> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig, + (treeNode, tree) -> { + tree.setId(treeNode.getId()); + tree.setParentId(treeNode.getParentId()); + tree.setWeight(treeNode.getWeight()); + tree.setName(treeNode.getName()); + // 扩展属性 ... + tree.putExtra("extraField", 666); + tree.putExtra("other", new Object()); + }); + System.out.println(treeNodes); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java index 4b7414ec3..d54a19d80 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java @@ -116,7 +116,7 @@ public class CronUtil { } /** - * 移除Task + * 更新Task的执行时间规则 * * @param id Task的ID * @param pattern {@link CronPattern}