diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e73db1e8..73749004c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【http 】 增加HttpResource(issue#1943@Github) * 【http 】 增加BytesBody、FormUrlEncodedBody * 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) +* 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) 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 index 85801c438..76272e9bc 100644 --- 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 @@ -2,6 +2,7 @@ package cn.hutool.core.lang.tree; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -21,7 +22,7 @@ import java.util.function.Consumer; * @author liangbaikai * @since 5.2.1 */ - public class Tree extends LinkedHashMap implements Node { +public class Tree extends LinkedHashMap implements Node { private static final long serialVersionUID = 1L; private final TreeNodeConfig treeNodeConfig; @@ -175,6 +176,16 @@ import java.util.function.Consumer; return (List>) this.get(treeNodeConfig.getChildrenKey()); } + /** + * 是否有子节点,无子节点则此为叶子节点 + * + * @return 是否有子节点 + * @since 5.7.17 + */ + public boolean hasChild() { + return CollUtil.isNotEmpty(getChildren()); + } + /** * 递归树并处理子树下的节点: * @@ -184,18 +195,67 @@ import java.util.function.Consumer; public void walk(Consumer> consumer) { consumer.accept(this); final List> children = getChildren(); - if(CollUtil.isNotEmpty(children)){ - children.forEach((tree)-> tree.walk(consumer)); + if (CollUtil.isNotEmpty(children)) { + children.forEach((tree) -> tree.walk(consumer)); } } + /** + * 递归过滤并生成新的树
+ * 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点 + * + * @param filter 节点过滤规则函数,只需处理本级节点本身即可 + * @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之 + * @see #filter(Filter) + * @since 5.7.17 + */ + public Tree filterNew(Filter> filter) { + return cloneTree().filter(filter); + } + + /** + * 递归过滤当前树,注意此方法会修改当前树
+ * 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点 + * + * @param filter 节点过滤规则函数,只需处理本级节点本身即可 + * @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之 + * @see #filterNew(Filter) + * @since 5.7.17 + */ + public Tree filter(Filter> filter) { + final List> children = getChildren(); + if (CollUtil.isNotEmpty(children)) { + // 递归过滤子节点 + final List> filteredChildren = new ArrayList<>(children.size()); + Tree filteredChild; + for (Tree child : children) { + filteredChild = child.filter(filter); + if (null != filteredChild) { + filteredChildren.add(filteredChild); + } + } + if(CollUtil.isNotEmpty(filteredChildren)){ + // 子节点有符合过滤条件的节点,则本节点保留 + return this.setChildren(filteredChildren); + } else { + this.setChildren(null); + } + } + + // 子节点都不符合过滤条件,检查本节点 + return filter.accept(this) ? this : null; + } + /** * 设置子节点,设置后会覆盖所有原有子节点 * - * @param children 子节点列表 + * @param children 子节点列表,如果为{@code null}表示移除子节点 * @return this */ public Tree setChildren(List> children) { + if(null == children){ + this.remove(treeNodeConfig.getChildrenKey()); + } this.put(treeNodeConfig.getChildrenKey(), children); return this; } @@ -241,6 +301,34 @@ import java.util.function.Consumer; return stringWriter.toString(); } + /** + * 递归克隆当前节点(即克隆整个树,保留字段值)
+ * 注意,此方法只会克隆节点,节点属性如果是引用类型,不会克隆 + * + * @return 新的节点 + * @since 5.7.17 + */ + public Tree cloneTree() { + final Tree result = ObjectUtil.clone(this); + result.setChildren(cloneChildren()); + return result; + } + + /** + * 递归复制子节点 + * + * @return 新的子节点列表 + */ + private List> cloneChildren() { + final List> children = getChildren(); + if (null == children) { + return null; + } + final List> newChildren = new ArrayList<>(children.size()); + children.forEach((t) -> newChildren.add(t.cloneTree())); + return newChildren; + } + /** * 打印 * diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java index 3007991cf..c695a707c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java @@ -76,4 +76,48 @@ public class TreeTest { Assert .assertEquals(7, ids.size()); } + + @Test + public void cloneTreeTest(){ + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + final Tree cloneTree = tree.cloneTree(); + + List ids = new ArrayList<>(); + cloneTree.walk((tr)-> ids.add(tr.getId())); + + Assert .assertEquals(7, ids.size()); + } + + @Test + public void filterTest(){ + // 经过过滤,丢掉"用户添加"节点 + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + tree.filter((t)->{ + final CharSequence name = t.getName(); + return null != name && name.toString().contains("管理"); + }); + + List ids = new ArrayList<>(); + tree.walk((tr)-> ids.add(tr.getId())); + Assert .assertEquals(6, ids.size()); + } + + @Test + public void filterNewTest(){ + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + + // 经过过滤,生成新的树 + Tree newTree = tree.filterNew((t)->{ + final CharSequence name = t.getName(); + return null != name && name.toString().contains("管理"); + }); + + List ids = new ArrayList<>(); + newTree.walk((tr)-> ids.add(tr.getId())); + Assert .assertEquals(6, ids.size()); + + List ids2 = new ArrayList<>(); + tree.walk((tr)-> ids2.add(tr.getId())); + Assert .assertEquals(7, ids2.size()); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java b/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java index cfcf6591a..a67ff667d 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java @@ -68,7 +68,6 @@ public class TimingWheel { * @param consumer 任务处理器 */ public TimingWheel(long tickMs, int wheelSize, long currentTime, Consumer consumer) { - this.currentTime = currentTime; this.tickMs = tickMs; this.wheelSize = wheelSize; this.interval = tickMs * wheelSize;