浏览代码

addd Tree

Looly 5 年之前
父节点
当前提交
dbcca73dc2

+ 1 - 0
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)

+ 1 - 1
hutool-core/src/main/java/cn/hutool/core/lang/ConsistentHash.java

@@ -23,7 +23,7 @@ public class ConsistentHash<T> implements Serializable{
 	/** 复制的节点个数 */
 	private final int numberOfReplicas;
 	/** 一致性Hash环 */
-	private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();
+	private final SortedMap<Integer, T> circle = new TreeMap<>();
 	
 	/**
 	 * 构造,使用Java默认的Hash算法

+ 12 - 1
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);
 	}
 
 	/**

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

@@ -1,15 +0,0 @@
-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 - 20
hutool-core/src/main/java/cn/hutool/core/lang/tree/DefaultConverter.java

@@ -1,20 +0,0 @@
-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);
-    }
-}

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

@@ -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;
-    }
-
-
-}

+ 121 - 0
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 <T> ID类型
+ * @author liangbaikai
+ * @since 5.2.1
+ */
+public class Tree<T> extends LinkedHashMap<String, Object> implements Comparable<Tree<T>> {
+	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<T> 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<T> setParentId(T parentId) {
+		this.put(treeNodeConfig.getParentIdKey(), parentId);
+		return this;
+	}
+
+	@SuppressWarnings("unchecked")
+	public T getName() {
+		return (T) this.get(treeNodeConfig.getNameKey());
+	}
+
+	public Tree<T> setName(Object name) {
+		this.put(treeNodeConfig.getNameKey(), name);
+		return this;
+	}
+
+	public Comparable<?> getWeight() {
+		return (Comparable<?>) this.get(treeNodeConfig.getWeightKey());
+	}
+
+	public Tree<T> setWeight(Comparable<?> weight) {
+		this.put(treeNodeConfig.getWeightKey(), weight);
+		return this;
+	}
+
+	@SuppressWarnings("unchecked")
+	public List<Tree<T>> getChildren() {
+		return (List<Tree<T>>) this.get(treeNodeConfig.getChildrenKey());
+	}
+
+	public void setChildren(List<Tree<T>> 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<T> tree) {
+		final Comparable weight = this.getWeight();
+		if (null != weight) {
+			final Comparable weightOther = tree.getWeight();
+			return weight.compareTo(weightOther);
+		}
+		return 0;
+	}
+}

+ 146 - 0
hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeNode.java

@@ -0,0 +1,146 @@
+package cn.hutool.core.lang.tree;
+
+
+/**
+ * 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名<br>
+ * 在你的项目里它可以是部门实体、地区实体等任意类树节点实体
+ * 类树节点实体: 包含key,父Key.不限于这些属性的可以构造成一颗树的实体对象
+ *
+ * @author liangbaikai
+ */
+public class TreeNode<T> implements Comparable<Tree<T>> {
+
+	/**
+	 * 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<T> setParentId(T parentId) {
+		this.parentId = parentId;
+		return this;
+	}
+
+	/**
+	 * 获取节点标签名称
+	 *
+	 * @return 节点标签名称
+	 */
+	public CharSequence getName() {
+		return name;
+	}
+
+	/**
+	 * 设置节点标签名称
+	 *
+	 * @param name 节点标签名称
+	 * @return this
+	 */
+	public TreeNode<T> setName(CharSequence name) {
+		this.name = name;
+		return this;
+	}
+
+	/**
+	 * 获取权重
+	 *
+	 * @return 权重
+	 */
+	public Comparable<?> getWeight() {
+		return weight;
+	}
+
+	/**
+	 * 设置权重
+	 *
+	 * @param weight 权重
+	 * @return this
+	 */
+	public TreeNode<T> 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;
+	}
+}

+ 135 - 107
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();
-
-    // 树节点默认属性常量 当然你可以重新设置
-    /**
-     * 节点 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;
-    }
-
+	/**
+	 * 默认属性配置对象
+	 */
+	public static TreeNodeConfig DEFAULT_CONFIG = new TreeNodeConfig();
+
+	// 属性名配置字段
+	private String idKey = "id";
+	private String parentIdKey = "parentId";
+	private String weightKey = "weight";
+	private String nameKey = "name";
+	private String childrenKey = "children";
+	// 可以配置递归深度 从0开始计算 默认此配置为空,即不限制
+	private Integer deep;
+
+
+	/**
+	 * 获取ID对应的名称
+	 *
+	 * @return ID对应的名称
+	 */
+	public String getIdKey() {
+		return this.idKey;
+	}
+
+	/**
+	 * 设置ID对应的名称
+	 *
+	 * @param idKey ID对应的名称
+	 * @return this
+	 */
+	public TreeNodeConfig setIdKey(String idKey) {
+		this.idKey = idKey;
+		return this;
+	}
+
+	/**
+	 * 获取权重对应的名称
+	 *
+	 * @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;
+	}
+
+
+	/**
+	 * 设置父点对应的名称
+	 *
+	 * @param parentIdKey 父点对应的名称
+	 * @return this
+	 */
+	public TreeNodeConfig setParentIdKey(String parentIdKey) {
+		this.parentIdKey = parentIdKey;
+		return this;
+	}
+
+	/**
+	 * 获取递归深度
+	 *
+	 * @return 递归深度
+	 */
+	public Integer getDeep() {
+		return this.deep;
+	}
+
+	/**
+	 * 设置递归深度
+	 *
+	 * @param deep 递归深度
+	 * @return this
+	 */
+	public TreeNodeConfig setDeep(Integer deep) {
+		this.deep = deep;
+		return this;
+	}
 }

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

@@ -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<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;
-        }
-    }
-}

+ 80 - 71
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<TreeNodeMap> build(List<Node> list, Object parentId) {
-        return build(list, parentId, TreeNodeConfig.getDefaultConfig(), new DefaultConverter());
-    }
+	/**
+	 * 树构建
+	 */
+	public static List<Tree<Integer>> build(List<TreeNode<Integer>> list) {
+		return build(list, 0);
+	}
 
-    /**
-     * 树构建
-     */
-    public static <T> List<TreeNodeMap> build(List<T> list, Object parentId, Convert<T, TreeNodeMap> convert) {
-        return build(list, parentId, TreeNodeConfig.getDefaultConfig(), convert);
-    }
+	/**
+	 * 树构建
+	 */
+	public static <T> List<Tree<T>> build(List<TreeNode<T>> list, T parentId) {
+		return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, new DefaultNodeParser<>());
+	}
 
-    /**
-     * 树构建
-     *
-     * @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);
-        }
+	/**
+	 * 树构建
+	 */
+	public static <T, E> List<Tree<E>> build(List<T> list, E parentId, NodeParser<T, E> nodeParser) {
+		return build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, nodeParser);
+	}
 
-        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 list           源数据集合
+	 * @param parentId       最顶层父id值 一般为 0 之类
+	 * @param treeNodeConfig 配置
+	 * @param nodeParser        转换器
+	 * @param <T>            转换的实体 为数据源里的对象类型
+	 * @return List
+	 */
+	public static <T, E> List<Tree<E>> build(List<T> list, E parentId, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
+		List<Tree<E>> treeNodes = CollectionUtil.newArrayList();
+		for (T obj : list) {
+			Tree<E> 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<TreeNodeMap> treeNodes, TreeNodeMap parentNode, int deep, Integer maxDeep) {
+		List<Tree<E>> finalTreeNodes = CollectionUtil.newArrayList();
+		for (Tree<E> 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 <T> void innerBuild(List<Tree<T>> treeNodes, Tree<T> 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<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);
-            }
-        }
-    }
+		if (CollectionUtil.isEmpty(treeNodes)) {
+			return;
+		}
+		//maxDeep 可能为空
+		if (maxDeep != null && deep >= maxDeep) {
+			return;
+		}
+
+		// 每层排序 TreeNodeMap 实现了Comparable接口
+		treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
+		for (Tree<T> childNode : treeNodes) {
+			if (parentNode.getId().equals(childNode.getParentId())) {
+				List<Tree<T>> 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);
+			}
+		}
+	}
 
 }

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

@@ -1 +1,15 @@
+/**
+ * 提供通用树生成,特点:
+ * <pre>
+ *     1、每个字段可自定义
+ *     2、支持排序 树深度配置,自定义转换器等
+ *     3、支持额外属性扩展
+ *     4、贴心 许多属性,特性都有默认值处理
+ *     5、使用简单 可一行代码生成树
+ *     6、代码简洁轻量无额外依赖
+ * <pre/>
+ *
+ * @author liangbaikai(https://gitee.com/liangbaikai00/)
+ * @since 5.2.1
+ */
 package cn.hutool.core.lang.tree;

+ 25 - 0
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 <T> ID类型
+ * @author liangbaikai
+ */
+public class DefaultNodeParser<T> implements NodeParser<TreeNode<T>, T> {
+
+	@Override
+	public void parse(TreeNode<T> object, Tree<T> 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);
+	}
+}

+ 18 - 0
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 <T> 转换的实体 为数据源里的对象类型
+ * @author liangbaikai
+ */
+public interface NodeParser<T, E> {
+	/**
+	 * @param object   源数据实体
+	 * @param treeNode 树节点实体
+	 */
+	void parse(T object, Tree<E> treeNode);
+}
+

+ 110 - 99
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;
 
 /**
  * 正则相关工具类<br>
  * 常用正则请见 {@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<Character> RE_KEYS = CollectionUtil.newHashSet(new Character[] { '$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|' });
+	/**
+	 * 正则中需要被转义的关键字
+	 */
+	public final static Set<Character> 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生成新的字符串<br>
 	 * 例如:<br>
 	 * 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 {
 	 * 匹配结束后会删除匹配内容之前的内容(包括匹配内容)<br>
 	 * 例如:<br>
 	 * 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 {
 	 * 匹配结束后会删除匹配内容之前的内容(包括匹配内容)<br>
 	 * 例如:<br>
 	 * 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<CharSequence> contentHolder, String template) {
@@ -221,7 +232,7 @@ public class ReUtil {
 			return null;
 		}
 
-		HashSet<String> varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet<String>());
+		HashSet<String> 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生成新的字符串<br>
 	 * 例如:<br>
 	 * 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<CharSequence> 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<String> findAll(String regex, CharSequence content, int group) {
-		return findAll(regex, content, group, new ArrayList<String>());
+		return findAll(regex, content, group, new ArrayList<>());
 	}
 
 	/**
 	 * 取得内容中匹配的所有结果
-	 * 
-	 * @param <T> 集合类型
-	 * @param regex 正则
-	 * @param content 被查找的内容
-	 * @param group 正则的分组
+	 *
+	 * @param <T>        集合类型
+	 * @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<String> findAll(Pattern pattern, CharSequence content, int group) {
-		return findAll(pattern, content, group, new ArrayList<String>());
+		return findAll(pattern, content, group, new ArrayList<>());
 	}
 
 	/**
 	 * 取得内容中匹配的所有结果
-	 * 
-	 * @param <T> 集合类型
-	 * @param pattern 编译后的正则模式
-	 * @param content 被查找的内容
-	 * @param group 正则的分组
+	 *
+	 * @param <T>        集合类型
+	 * @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 {
 	/**
 	 * 正则替换指定值<br>
 	 * 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
-	 * 
+	 *
 	 * <p>
 	 * 例如:原字符串是:中文1234,我想把1234换成(1234),则可以:
-	 * 
+	 *
 	 * <pre>
 	 * ReUtil.replaceAll("中文1234", "(\\d+)", "($1)"))
-	 * 
+	 *
 	 * 结果:中文(1234)
 	 * </pre>
-	 * 
-	 * @param content 文本
-	 * @param regex 正则
+	 *
+	 * @param content             文本
+	 * @param regex               正则
 	 * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容
 	 * @return 处理后的文本
 	 */
@@ -603,9 +614,9 @@ public class ReUtil {
 	/**
 	 * 正则替换指定值<br>
 	 * 通过正则查找到字符串,然后把匹配到的字符串加入到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<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet<String>());
+			final Set<String> 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<Matcher, String> 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<Matcher, String> replaceFun){
+	public static String replaceAll(CharSequence str, Pattern pattern, Func1<Matcher, String> 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 转义后的文本
 	 */

+ 46 - 47
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<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);
-    }
+	// 模拟数据
+	static List<TreeNode<String>> nodeList = CollectionUtil.newArrayList();
+
+	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<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0");
+		for (Tree<String> tree : treeNodes) {
+			Console.log(tree);
+		}
+	}
+
+	@Test
+	public void tree() {
+
+		//配置
+		TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
+		// 自定义属性名 都要默认值的
+		treeNodeConfig.setWeightKey("order");
+		treeNodeConfig.setDeep(3);
+		treeNodeConfig.setIdKey("rid");
+
+		//转换器
+		List<Tree<String>> 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);
+	}
 }

+ 1 - 1
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}