diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java index faad9e498..4ea977c3e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java @@ -21,11 +21,10 @@ import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.resource.ResourceUtil; import org.dromara.hutool.core.net.url.UrlDecoder; import org.dromara.hutool.core.net.url.UrlUtil; +import org.dromara.hutool.core.stream.EasyStream; import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.split.SplitUtil; -import org.dromara.hutool.core.tree.hierarchy.HierarchyIteratorUtil; -import org.dromara.hutool.core.tree.hierarchy.HierarchyUtil; import org.dromara.hutool.core.util.CharsetUtil; import java.io.IOException; @@ -826,10 +825,9 @@ public class ClassUtil { */ public static void traverseTypeHierarchyWhile( final Class root, final Predicate> filter, final Predicate> terminator) { - HierarchyUtil.traverseByBreadthFirst( - root, filter, - HierarchyIteratorUtil.scan(ClassUtil::getNextTypeHierarchies, terminator.negate()) - ); + EasyStream.iterateHierarchies(root, ClassUtil::getNextTypeHierarchies, filter) + .takeWhile(terminator) + .exec(); } /** @@ -857,7 +855,7 @@ public class ClassUtil { } return getNextTypeHierarchies(t); }; - HierarchyUtil.traverseByBreadthFirst(root, filter, HierarchyIteratorUtil.scan(function)); + EasyStream.iterateHierarchies(root, function, filter).exec(); } private static Set> getNextTypeHierarchies(final Class t) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/stream/EasyStream.java b/hutool-core/src/main/java/org/dromara/hutool/core/stream/EasyStream.java index 40efb932e..3c063c618 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/stream/EasyStream.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/stream/EasyStream.java @@ -12,14 +12,15 @@ package org.dromara.hutool.core.stream; +import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.collection.CollUtil; import org.dromara.hutool.core.lang.Opt; import org.dromara.hutool.core.math.NumberUtil; -import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.util.ObjUtil; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.OptionalDouble; @@ -223,6 +224,55 @@ public class EasyStream extends AbstractEnhancedWrappedStream(StreamUtil.iterate(seed, hasNext, next)); } + /** + *

指定一个层级结构的根节点(通常是树或图), + * 然后获取包含根节点在内,根节点所有层级结构中的节点组成的流。
+ * 该方法用于以平铺的方式对图或树节点进行访问,可以使用并行流提高效率。 + * + *

eg: + *

{@code
+	 * Tree root = // 构建树结构
+	 * // 搜索树结构中所有级别为3的节点,并按权重排序
+	 * List thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
+	 * 	.filter(node -> node.getLevel() == 3)
+	 * 	.sorted(Comparator.comparing(Tree::getWeight))
+	 * 	.toList();
+	 * }
+ * + * @param root 根节点 + * @param discoverer 下一层级节点的获取方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + * @return 包含根节点在内,根节点所有层级结构中的节点组成的流 + */ + public static EasyStream iterateHierarchies( + T root, final Function> discoverer, Predicate filter) { + return of(StreamUtil.iterateHierarchies(root, discoverer, filter)); + } + + /** + *

指定一个层级结构的根节点(通常是树或图), + * 然后获取包含根节点在内,根节点所有层级结构中的节点组成的流。
+ * 该方法用于以平铺的方式对图或树节点进行访问,可以使用并行流提高效率。 + * + *

eg: + *

{@code
+	 * Tree root = // 构建树结构
+	 * // 搜索树结构中所有级别为3的节点,并按权重排序
+	 * List thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
+	 * 	.filter(node -> node.getLevel() == 3)
+	 * 	.sorted(Comparator.comparing(Tree::getWeight))
+	 * 	.toList();
+	 * }
+ * + * @param root 根节点 + * @param discoverer 下一层级节点的获取方法 + * @return 包含根节点在内,根节点所有层级结构中的节点组成的流 + */ + public static EasyStream iterateHierarchies( + T root, final Function> discoverer) { + return of(StreamUtil.iterateHierarchies(root, discoverer)); + } + /** * 返回无限串行无序流 * 其中每一个元素都由给定的{@link Supplier}生成 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/stream/StreamUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/stream/StreamUtil.java index 0dd7b74fa..9f57f0d73 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/stream/StreamUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/stream/StreamUtil.java @@ -12,11 +12,13 @@ package org.dromara.hutool.core.stream; +import static java.util.Objects.requireNonNull; import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.stream.spliterators.DropWhileSpliterator; import org.dromara.hutool.core.stream.spliterators.IterateSpliterator; import org.dromara.hutool.core.stream.spliterators.TakeWhileSpliterator; +import org.dromara.hutool.core.tree.HierarchyIterator; import org.dromara.hutool.core.util.CharsetUtil; import java.io.File; @@ -34,8 +36,6 @@ import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static java.util.Objects.requireNonNull; - /** * {@link Stream} 工具类 * @@ -210,6 +210,59 @@ public class StreamUtil { return StreamSupport.stream(IterateSpliterator.create(seed, hasNext, next), false); } + /** + *

指定一个层级结构的根节点(通常是树或图), + * 然后获取包含根节点在内,根节点所有层级结构中的节点组成的流。
+ * 该方法用于以平铺的方式按广度优先对图或树节点进行访问,可以使用并行流提高效率。 + * + *

eg: + *

{@code
+	 * Tree root = // 构建树结构
+	 * // 搜索树结构中所有级别为3的节点,并按权重排序
+	 * List thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
+	 * 	.filter(node -> node.getLevel() == 3)
+	 * 	.sorted(Comparator.comparing(Tree::getWeight))
+	 * 	.toList();
+	 * }
+ * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param discoverer 下一层级节点的获取方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + * @return 包含根节点在内,根节点所有层级结构中的节点组成的流 + * @param 元素类型 + * @see HierarchyIterator + */ + public static Stream iterateHierarchies( + T root, final Function> discoverer, Predicate filter) { + return ofIter(HierarchyIterator.breadthFirst(root, discoverer, filter)); + } + + /** + *

指定一个层级结构的根节点(通常是树或图), + * 然后获取包含根节点在内,根节点所有层级结构中的节点组成的流。
+ * 该方法用于以平铺的方式按广度优先对图或树节点进行访问,可以使用并行流提高效率。 + * + *

eg: + *

{@code
+	 * Tree root = // 构建树结构
+	 * // 搜索树结构中所有级别为3的节点,并按权重排序
+	 * List thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
+	 * 	.filter(node -> node.getLevel() == 3)
+	 * 	.sorted(Comparator.comparing(Tree::getWeight))
+	 * 	.toList();
+	 * }
+ * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param discoverer 下一层级节点的获取方法 + * @return 包含根节点在内,根节点所有层级结构中的节点组成的流 + * @param 元素类型 + * @see HierarchyIterator + */ + public static Stream iterateHierarchies( + T root, final Function> discoverer) { + return ofIter(HierarchyIterator.breadthFirst(root, discoverer)); + } + /** * 保留 与指定断言 匹配时的元素, 在第一次不匹配时终止, 抛弃当前(第一个不匹配元素)及后续所有元素 *

与 jdk9 中的 takeWhile 方法不太一样, 这里的实现是个 顺序的、有状态的中间操作

diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/HierarchyIterator.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/HierarchyIterator.java new file mode 100644 index 000000000..e556f4928 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/tree/HierarchyIterator.java @@ -0,0 +1,238 @@ +package org.dromara.hutool.core.tree; + +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.stream.EasyStream; +import org.dromara.hutool.core.stream.StreamUtil; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + *

用于迭代层级结构(比如树或图)的迭代器, + * 支持{@link #depthFirst 广度优先}与{@link #breadthFirst 深度优先}两种遍历模式。
+ * 迭代器仅适用于访问层级结构,因此不支持{@link Iterator#remove}方法。 + * 要构建树或者操作数,请参见{@link BeanTree}或{@link TreeUtil}。 + * + *

该迭代器侧重于打通图或树这类数据结构与传统集合间的隔阂, + * 从而支持通过传统可迭代集合的方式对树或图中的节点进行操作。
+ * 比如: + *

{@code
+ * Tree root = // 构建树结构
+ * // 搜索树结构中所有级别为3的节点,并按权重排序
+ * List thirdLevelNodes = StreamUtil.iterateHierarchies(root, Tree:getChildren)
+ * 	.filter(node -> node.getLevel() == 3)
+ * 	.sorted(Comparator.comparing(Tree::getWeight))
+ * 	.toList();
+ * }
+ * + * @author huangchengxing + * @param 元素类型 + * @see EasyStream#iterateHierarchies + * @see StreamUtil#iterateHierarchies + */ +public abstract class HierarchyIterator implements Iterator { + + /** + * 下一层级节点的获取方法 + */ + protected final Function> elementDiscoverer; + /** + * 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + */ + protected final Predicate filter; + /** + * 已经访问过的列表 + */ + protected final Set accessed = new HashSet<>(); + /** + * 当前待遍历的节点 + */ + protected final LinkedList queue = new LinkedList<>(); + + /** + * 获取一个迭代器,用于按广度优先迭代层级结构中的每一个结点 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 下一层级节点的获取方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + * @param 元素类型 + * @return 迭代器 + */ + public static HierarchyIterator breadthFirst( + final T root, final Function> nextDiscoverer, final Predicate filter) { + return new BreadthFirst<>(root, nextDiscoverer, filter); + } + + /** + * 获取一个迭代器,用于按广度优先迭代层级结构中的每一个结点 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 下一层级节点的获取方法 + * @param 元素类型 + * @return 迭代器 + */ + public static HierarchyIterator breadthFirst( + final T root, final Function> nextDiscoverer) { + return breadthFirst(root, nextDiscoverer, t -> true); + } + + /** + * 获取一个迭代器,用于按深度优先迭代层级结构中的每一个结点 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 下一层级节点的获取方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + * @param 元素类型 + * @return 迭代器 + */ + public static HierarchyIterator depthFirst( + final T root, final Function> nextDiscoverer, final Predicate filter) { + return new DepthFirst<>(root, nextDiscoverer, filter); + } + + /** + * 获取一个迭代器,用于按深度优先迭代层级结构中的每一个结点 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 下一层级节点的获取方法 + * @param 元素类型 + * @return 迭代器 + */ + public static HierarchyIterator depthFirst( + final T root, final Function> nextDiscoverer) { + return depthFirst(root, nextDiscoverer, t -> true); + } + + /** + * 创建一个迭代器 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param elementDiscoverer 获取下一层级节点的方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + */ + HierarchyIterator(final T root, final Function> elementDiscoverer, final Predicate filter) { + // 根节点不允许被过滤 + Assert.isTrue(filter.test(root), "root node cannot be filtered!"); + queue.add(root); + this.elementDiscoverer = Assert.notNull(elementDiscoverer); + this.filter = Assert.notNull(filter); + } + + /** + * 是否仍有下一个节点 + * + * @return 是否 + */ + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + /** + * 获取下一个节点 + * + * @return 下一个节点 + */ + @Override + public T next() { + if (queue.isEmpty()) { + throw new NoSuchElementException(); + } + final T curr = queue.removeFirst(); + accessed.add(curr); + Collection nextElements = elementDiscoverer.apply(curr); + if (Objects.nonNull(nextElements) && !nextElements.isEmpty()) { + nextElements = nextElements.stream() + .filter(filter) + .collect(Collectors.toList()); + collectNextElementsToQueue(nextElements); + } + return curr; + } + + /** + * 将下一层级的节点搜集到队列 + * + * @param nextElements 待收集的下一层级的节点 + * @see #queue + * @see #accessed + */ + protected abstract void collectNextElementsToQueue(final Collection nextElements); + + /** + * 深度优先遍历 + * + * @param 元素 + */ + static class DepthFirst extends HierarchyIterator { + + /** + * 创建一个迭代器 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 获取下一层级节点的方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + */ + DepthFirst(final T root, final Function> nextDiscoverer, final Predicate filter) { + super(root, nextDiscoverer, filter); + } + + /** + * 将下一层级的节点搜集到队列 + * + * @param nextElements 待收集的下一层级的节点 + */ + @Override + protected void collectNextElementsToQueue(final Collection nextElements) { + int idx = 0; + for (final T nextElement : nextElements) { + if (!accessed.contains(nextElement)) { + queue.add(idx++, nextElement); + accessed.add(nextElement); + } + } + } + } + + /** + * 广度优先遍历 + * + * @param 元素类型 + */ + static class BreadthFirst extends HierarchyIterator { + + /** + * 创建一个迭代器 + * + * @param root 根节点,根节点不允许被{@code filter}过滤 + * @param nextDiscoverer 获取下一层级节点的方法 + * @param filter 节点过滤器,不匹配的节点与以其作为根节点的子树将将会被忽略 + */ + BreadthFirst(final T root, final Function> nextDiscoverer, final Predicate filter) { + super(root, nextDiscoverer, filter); + } + + /** + * 将下一层级的节点搜集到队列 + * + * @param nextElements 待收集的下一层级的节点 + */ + @Override + protected void collectNextElementsToQueue(final Collection nextElements) { + for (final T nextElement : nextElements) { + if (!accessed.contains(nextElement)) { + queue.addLast(nextElement); + accessed.add(nextElement); + } + } + } + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIterator.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIterator.java deleted file mode 100644 index a61f398b1..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIterator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.core.tree.hierarchy; - -import java.util.Collection; - -/** - * 用于遍历层级结构的迭代器,提供了遍历过程中的回调方法: - *
    - *
  • {@link #isBreak}: 是否中断遍历,其在{@link #nextHierarchies}之前调用;
  • - *
  • {@link #nextHierarchies}: 获得下一需要遍历的层级,当为空时结束;
  • - *
  • {@link #getResult}: 当遍历完成后,获取迭代器结果;
  • - *
- * - *

默认提供了三类{@code HierarchyIterator}实现: - *

    - *
  • scan: 遍历所有的层级结构;
  • - *
  • collector: 收集层级结构中所有符合条件的结果,并在结束遍历后返回所有结果;
  • - *
  • find: 找到层级结构中符合条件的结果后立刻中断遍历,并返回该结果;
  • - *
- * 可以实现自定义的{@code HierarchyIterator}来支持更多的遍历模式。 - * - * @param 层级类型 - * @param 结果类型 - * @author huangchengxing - * @since 6.0.0 - */ -@FunctionalInterface -public interface HierarchyIterator { - - /** - * 获取下一层级 - * - * @param result 结果 - * @param hierarchy 当前层级 - * @return 下一需要遍历的层级 - */ - Collection nextHierarchies(R result, H hierarchy); - - /** - * 是否中断遍历 - * - * @param hierarchy 当前层级 - * @return 是否中断遍历 - */ - default boolean isBreak(final H hierarchy) { - return false; - } - - /** - * 获取结果 - * - * @return 结果 - */ - default R getResult() { - return null; - } -} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorImpl.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorImpl.java deleted file mode 100644 index 447ca86ea..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.core.tree.hierarchy; - -import java.util.Collection; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.function.Supplier; - -/** - * {@code HierarchyIterator}的基本实现。 - * - * @param 层级类型 - * @param 结果类型 - * @author huangchengxing - */ -public class HierarchyIteratorImpl implements HierarchyIterator { - - private final Supplier resultFactory; - private final Predicate hierarchyFilter; - private final BiFunction> hierarchyFinder; - - /** - * 构造 - * - * @param resultFactory 结果创建类 - * @param hierarchyFilter 层级过滤器 - * @param hierarchyFinder 层级查找器 - */ - public HierarchyIteratorImpl( - final Supplier resultFactory, final Predicate hierarchyFilter, - final BiFunction> hierarchyFinder) { - this.resultFactory = resultFactory; - this.hierarchyFilter = hierarchyFilter; - this.hierarchyFinder = hierarchyFinder; - } - - /** - * 获取下一层级 - * - * @param result 结果 - * @param hierarchy 当前层级 - * @return 下一需要遍历的层级 - */ - @Override - public Collection nextHierarchies(final R result, final H hierarchy) { - return hierarchyFinder.apply(result, hierarchy); - } - - /** - * 是否中断遍历 - * - * @param hierarchy 当前层级 - * @return 是否中断遍历 - */ - @Override - public boolean isBreak(final H hierarchy) { - return hierarchyFilter.test(hierarchy); - } - - /** - * 获取结果 - * - * @return 结果 - */ - @Override - public R getResult() { - return resultFactory.get(); - } -} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorUtil.java deleted file mode 100644 index c76c5f0a9..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyIteratorUtil.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.core.tree.hierarchy; - -import org.dromara.hutool.core.lang.mutable.Mutable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; - -/** - * {@link HierarchyIterator}创建工具类 - * - * @author huangchengxing - * @since 6.0.0 - */ -public class HierarchyIteratorUtil { - /** - * 创建一个{@code HierarchyIterator}对象, 当{@code finder}返回非空时, 迭代器立刻中断, 返回结果 - * - * @param function 迭代器处理函数 - * @param finder 查找器 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return {@code HierarchyIterator} - */ - public static HierarchyIterator find( - final Function> function, final Function finder) { - Objects.requireNonNull(function); - Objects.requireNonNull(finder); - final Mutable mutable = Mutable.of(null); - final Predicate terminator = h -> { - final R r = finder.apply(h); - if (r != null) { - mutable.set(r); - return true; - } - return false; - }; - return new HierarchyIteratorImpl<>(mutable::get, terminator, (r, h) -> function.apply(h)); - } - - /** - * 创建一个{@code HierarchyIterator}对象, 迭代器结果总是为{@code null} - * - * @param function 迭代器处理函数 - * @param terminator 是否终止遍历 - * @param 层级结构类型 - * @return {@code HierarchyIterator} - */ - public static HierarchyIterator scan( - final Function> function, final Predicate terminator) { - Objects.requireNonNull(function); - return new HierarchyIteratorImpl<>(() -> null, terminator, (r, h) -> function.apply(h)); - } - - /** - * 创建一个{@code HierarchyIterator}对象, 迭代器结果总是为{@code null} - * - * @param function 迭代器处理函数 - * @param 层级结构类型 - * @return {@code HierarchyIterator} - */ - public static HierarchyIterator scan(final Function> function) { - return scan(function, h -> false); - } - - /** - * 创建一个{@code HierarchyIterator}对象, 若{@code mapper}返回非空, 则将结果添加到集合中,最终返回集合 - * - * @param function 迭代器处理函数 - * @param collFactory 集合工厂 - * @param mapper 迭代器结果映射函数 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @param 集合类型 - * @return {@code HierarchyIterator} - */ - public static > HierarchyIterator collect( - final Function> function, final Supplier collFactory, final Function mapper) { - Objects.requireNonNull(function); - Objects.requireNonNull(collFactory); - Objects.requireNonNull(mapper); - final C collection = collFactory.get(); - return new HierarchyIteratorImpl<>(() -> collection, h -> false, (r, h) -> { - final R apply = mapper.apply(h); - if (Objects.nonNull(apply)) { - collection.add(apply); - } - return function.apply(h); - }); - } - - /** - * 创建一个{@code HierarchyIterator}对象, 若{@code mapper}返回非空, 则将结果添加到集合中,最终返回集合 - * - * @param function 迭代器处理函数 - * @param mapper 迭代器结果映射函数 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return {@code HierarchyIterator} - */ - public static HierarchyIterator> collect( - final Function> function, final Function mapper) { - return collect(function, ArrayList::new, mapper); - } - - /** - * 创建一个{@code HierarchyIterator}对象, 则将非空结果添加到集合中,最终返回集合 - * - * @param function 迭代器处理函数 - * @param 层级结构类型 - * @return {@code HierarchyIterator} - */ - public static HierarchyIterator> collect( - final Function> function) { - return collect(function, Function.identity()); - } -} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtil.java deleted file mode 100644 index ec69fd8a8..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtil.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.core.tree.hierarchy; - -import org.dromara.hutool.core.collection.CollUtil; - -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Predicate; - -/** - *

通用层级结构扫描器工具类。
- * - *

用于根据广度优先或深度优先或搜索遍历如树或图等特定数据结构的层级,如:文件夹层级、类层级、方法或注解层级等。 - * 使用时指定遍历的起始节点,以及遍历算法,并配合{@link HierarchyIterator}即可完成遍历中的业务逻辑处理。 - * - *

比如,我们可以使用其来访问树结构: - *

{@code
- * // 构建一个树形结构
- * Node root = Tree.build();
- * // 从树结构中通过深度优先查找某个节点
- * Node target = HierarchyUtil.traverseByDepthFirst(
- * 	root, HierarchyIteratorUtil.find(Node::getChildren, node -> Objects.equals(node.getId(), "target") ? node : null)
- * );
- * // 从树结构中通过广度优先获取其所有的子节点
- * List nodes = HierarchyUtil.traverseByBreadthFirst(
- * 	root, HierarchyIteratorUtil.collect(Node::getChildren)
- * );
- * }
- * - * @author huangchengxing - * @see HierarchyIterator - * @since 6.0.0 - */ -public class HierarchyUtil { - - // ================ breadth first ================ - - /** - * 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构 - * - * @param hierarchy 层级结构 - * @param filter 过滤器,校验不通过的层级结构不会被查找 - * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return 迭代器结果 - */ - public static R traverseByBreadthFirst( - final H hierarchy, final Predicate filter, final HierarchyIterator hierarchyIterator) { - return scanHierarchies(hierarchy, filter, hierarchyIterator, Collection::addAll); - } - - /** - * 按广度优先遍历指定包括{@code hierarchy}本身在内层级结构 - * - * @param hierarchy 层级结构 - * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return 迭代器结果 - */ - public static R traverseByBreadthFirst(final H hierarchy, final HierarchyIterator hierarchyIterator) { - return traverseByBreadthFirst(hierarchy, h -> true, hierarchyIterator); - } - - // ================ depth first ================ - - /** - * 按深度优先遍历指定包括{@code hierarchy}本身在内层级结构 - * - * @param hierarchy 层级结构 - * @param filter 过滤器,校验不通过的层级结构不会被查找 - * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return 迭代器结果 - */ - public static R traverseByDepthFirst( - final H hierarchy, final Predicate filter, final HierarchyIterator hierarchyIterator) { - return scanHierarchies(hierarchy, filter, hierarchyIterator, (queue, hierarchies) -> { - // 跳跃式添加,保证顺序 - int index = 0; - for (final H h : hierarchies) { - queue.add(index++, h); - } - }); - } - - /** - * 按深度优先遍历指定包括{@code hierarchy}本身在内层级结构 - * - * @param hierarchy 层级结构 - * @param hierarchyIterator 遍历过程中的迭代器, 若迭代器返回空, 则不会继续遍历下一层级 - * @param 层级结构类型 - * @param 迭代器结果类型 - * @return 迭代器结果 - */ - public static R traverseByDepthFirst(final H hierarchy, final HierarchyIterator hierarchyIterator) { - return traverseByDepthFirst(hierarchy, h -> true, hierarchyIterator); - } - - private static R scanHierarchies( - final H hierarchy, final Predicate filter, final HierarchyIterator hierarchyIterator, - final BiConsumer, Collection> appender) { - Objects.requireNonNull(hierarchy); - Objects.requireNonNull(filter); - Objects.requireNonNull(hierarchyIterator); - - // 遍历层级结构 - final LinkedList queue = new LinkedList<>(); - final Set accessed = new HashSet<>(); - queue.add(hierarchy); - while (!queue.isEmpty()) { - // 跳过已经访问过或者被过滤的层级结构 - final H curr = queue.removeFirst(); - if (!accessed.add(curr) || !filter.test(curr)) { - continue; - } - - // 若迭代器返回true,则结束遍历 - if (hierarchyIterator.isBreak(curr)) { - return hierarchyIterator.getResult(); - } - - // 获取下一需要遍历的层级 - final Collection nextHierarchies = hierarchyIterator.nextHierarchies(hierarchyIterator.getResult(), curr); - if (CollUtil.isEmpty(nextHierarchies)) { - continue; - } - appender.accept(queue, nextHierarchies); - } - return hierarchyIterator.getResult(); - } -} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/package-info.java deleted file mode 100644 index 5d27cf951..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/tree/hierarchy/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -/** - * 用于处理层级结构的通用工具列,结构类似 Stream + Collector。
- * HierarchyUtil 提供深度优先和广度优先算法实现,HierarchyIterator 则提供具体的操作逻辑 - * - * @author huangchengxing - */ -package org.dromara.hutool.core.tree.hierarchy; diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/stream/EasyStreamTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/stream/EasyStreamTest.java index d5a64ff84..b85e3f2be 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/stream/EasyStreamTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/stream/EasyStreamTest.java @@ -12,6 +12,7 @@ package org.dromara.hutool.core.stream; +import static java.util.Collections.singletonList; import lombok.Builder; import lombok.Data; import org.dromara.hutool.core.collection.ListUtil; @@ -28,13 +29,39 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Collections.singletonList; - /** * @author VampireAchao */ public class EasyStreamTest { + + @Test + void testIterateHierarchies() { + // 创建一个三层的树结构,每个节点都有两个子节点 + Node node = new Node("1", Arrays.asList( + new Node("1-1", Arrays.asList( + new Node("1-1-1", null), + new Node("1-1-2", null) + )), + new Node("1-2", Arrays.asList( + new Node("1-2-1", null), + new Node("1-2-2", null) + )) + )); + + // 按广度度优先遍历树结构 + List allNodes = new ArrayList<>(); + EasyStream.iterateHierarchies(node, node1 -> node1.children) + .forEach(node1 -> allNodes.add(node1.id)); + Assertions.assertEquals(Arrays.asList("1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"), allNodes); + + // 按广度度优先遍历树结构,忽略id为1-1的节点与以其为根节点的子树 + List filteredNodes = new ArrayList<>(); + EasyStream.iterateHierarchies(node, node1 -> node1.children, node1 -> !node1.id.equals("1-1")) + .forEach(node1 -> filteredNodes.add(node1.id)); + Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), filteredNodes); + } + @Test public void testConcat() { final Stream stream1 = Stream.of(1, 2); @@ -568,4 +595,19 @@ public class EasyStreamTest { private String name; private BigDecimal count; } + + /** + * 节点 + */ + private static class Node { + private final String id; + private List children; + private Node(String id, List children) { + this.id = id; + this.children = children; + } + public Node(String id) { + this.id = id; + } + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/stream/StreamUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/stream/StreamUtilTest.java index b82eb7d82..c42f2263b 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/stream/StreamUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/stream/StreamUtilTest.java @@ -17,12 +17,44 @@ import org.dromara.hutool.core.collection.set.SetUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class StreamUtilTest { + @Test + void testIterateHierarchies() { + // 创建一个三层的树结构,每个节点都有两个子节点 + Node node = new Node("1", Arrays.asList( + new Node("1-1", Arrays.asList( + new Node("1-1-1", null), + new Node("1-1-2", null) + )), + new Node("1-2", Arrays.asList( + new Node("1-2-1", null), + new Node("1-2-2", null) + )) + )); + + // 按广度度优先遍历树结构 + List allNodes = new ArrayList<>(); + StreamUtil.iterateHierarchies(node, node1 -> node1.children) + .forEach(node1 -> allNodes.add(node1.id)); + Assertions.assertEquals(Arrays.asList("1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"), allNodes); + + // 按广度度优先遍历树结构,忽略id为1-1的节点与以其为根节点的子树 + List filteredNodes = new ArrayList<>(); + StreamUtil.iterateHierarchies(node, node1 -> node1.children, node1 -> !node1.id.equals("1-1")) + .forEach(node1 -> filteredNodes.add(node1.id)); + Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), filteredNodes); + } + @Test public void ofTest(){ final Stream stream = StreamUtil.of(2, x -> x * 2, 4); @@ -61,4 +93,19 @@ public class StreamUtilTest { Assertions.assertEquals(0, stream.toArray().length); } // ================ stream test end ================ + + /** + * 节点 + */ + private static class Node { + private final String id; + private List children; + private Node(String id, List children) { + this.id = id; + this.children = children; + } + public Node(String id) { + this.id = id; + } + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/tree/HierarchyIteratorTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/tree/HierarchyIteratorTest.java new file mode 100644 index 000000000..3947ab45d --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/tree/HierarchyIteratorTest.java @@ -0,0 +1,126 @@ +package org.dromara.hutool.core.tree; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * test for {@link HierarchyIterator} + * + * @author huangchengxing + */ +class HierarchyIteratorTest { + + @Test + void testNoSuchElementException() { + Node node = createTree(); + // 根节点也被忽略,将会抛出异常 + Assertions.assertThrows(IllegalArgumentException.class, () -> HierarchyIterator.depthFirst(node, node1 -> node1.children, node1 -> false)); + // 忽略所有节点,将会抛出NoSuchElementException + HierarchyIterator iterator = HierarchyIterator.depthFirst(node, node1 -> node1.children, node1 -> node1.id.equals("1")); + Assertions.assertSame(node, iterator.next()); + Assertions.assertThrows(NoSuchElementException.class, iterator::next); + } + + @Test + void testDepthFirstWithFilter() { + Node node = createTree(); + // 按深度度优先遍历树结构,忽略id为1-1的节点与以其为根节点的子树 + List nodes = new ArrayList<>(); + HierarchyIterator iterator = HierarchyIterator.depthFirst( + node, node1 -> node1.children, node1 -> !node1.id.equals("1-1") + ); + while (iterator.hasNext()) { + Node node1 = iterator.next(); + nodes.add(node1.id); + } + Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), nodes); + } + + @Test + void testDepthFirst() { + Node node = createTree(); + // 按深度度优先遍历树结构 + List nodes = new ArrayList<>(); + HierarchyIterator iterator = HierarchyIterator.depthFirst(node, node1 -> node1.children); + while (iterator.hasNext()) { + Node node1 = iterator.next(); + nodes.add(node1.id); + } + Assertions.assertEquals(Arrays.asList("1", "1-1", "1-1-1", "1-1-2", "1-2", "1-2-1", "1-2-2"), nodes); + } + + @Test + void testBreadthFirstWithFilter() { + Node node = createTree(); + // 按深度度优先遍历树结构,忽略id为1-1的节点与以其为根节点的子树 + List nodes = new ArrayList<>(); + HierarchyIterator iterator = HierarchyIterator.breadthFirst( + node, node1 -> node1.children, node1 -> !node1.id.equals("1-1") + ); + while (iterator.hasNext()) { + Node node1 = iterator.next(); + nodes.add(node1.id); + } + Assertions.assertEquals(Arrays.asList("1", "1-2", "1-2-1", "1-2-2"), nodes); + } + + @Test + void testBreadthFirst() { + Node root = createGraph(); + // 按深度度优先遍历图结构 + List nodes = new ArrayList<>(); + HierarchyIterator iterator = HierarchyIterator.breadthFirst(root, node -> node.children); + while (iterator.hasNext()) { + Node node = iterator.next(); + nodes.add(node.id); + } + Assertions.assertEquals(Arrays.asList("1", "4", "2", "3"), nodes); + } + + private static Node createGraph() { + // 构建一个包含四个节点的图,每一个节点都有两个相邻节点 + Node node1 = new Node("1"); + Node node2 = new Node("2"); + Node node3 = new Node("3"); + Node node4 = new Node("4"); + node1.children = Arrays.asList(node4, node2); + node2.children = Arrays.asList(node1, node3); + node3.children = Arrays.asList(node2, node4); + node4.children = Arrays.asList(node3, node1); + return node1; + } + + private static Node createTree() { + // 构建一个三层的树,每一个节点都有两个子节点 + return new Node("1", Arrays.asList( + new Node("1-1", Arrays.asList( + new Node("1-1-1", null), + new Node("1-1-2", null) + )), + new Node("1-2", Arrays.asList( + new Node("1-2-1", null), + new Node("1-2-2", null) + )) + )); + } + + /** + * 节点 + */ + private static class Node { + private final String id; + private List children; + private Node(String id, List children) { + this.id = id; + this.children = children; + } + public Node(String id) { + this.id = id; + } + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtilTest.java deleted file mode 100644 index bc62c36fe..000000000 --- a/hutool-core/src/test/java/org/dromara/hutool/core/tree/hierarchy/HierarchyUtilTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.core.tree.hierarchy; - -import lombok.Data; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.*; - -/** - * test for {@link HierarchyUtil} - * - * @author huangchengxing - */ -class HierarchyUtilTest { - - @Data - static class Node { - private String parent; - private String value; - private List children; - - public Node(final String parent, final String value) { - this.parent = parent; - this.value = value; - } - } - - private Node root; - - @BeforeEach - void init() { - // 根节点 - root = new Node(null, "0"); - - // 第二层 - final Node first1 = new Node(root.value, "0-1"); - final Node first2 = new Node(root.value, "0-2"); - final Node first3 = new Node(root.value, "0-3"); - root.setChildren(Arrays.asList(first1, first2, first3)); - - // 第三层 - final Node second11 = new Node(first1.value, "0-1-1"); - final Node second12 = new Node(first1.value, "0-1-2"); - final Node second13 = new Node(first1.value, "0-1-3"); - first1.setChildren(Arrays.asList(second11, second12, second13)); - - final Node second21 = new Node(first2.value, "0-2-1"); - final Node second22 = new Node(first2.value, "0-2-2"); - final Node second23 = new Node(first2.value, "0-2-3"); - first2.setChildren(Arrays.asList(second21, second22, second23)); - - final Node second31 = new Node(first3.value, "0-3-1"); - final Node second32 = new Node(first3.value, "0-3-2"); - final Node second33 = new Node(first3.value, "0-3-3"); - first3.setChildren(Arrays.asList(second31, second32, second33)); - } - - @Test - void testTraverseByBreadthFirst() { - // // 按广度优先遍历所有节点 - final List nodes = new ArrayList<>(); - HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.scan(t -> { - nodes.add(t.getValue()); - return t.getChildren(); - })); - Assertions.assertEquals(13, nodes.size()); - Assertions.assertEquals( - Arrays.asList("0", "0-1", "0-2", "0-3", "0-1-1", "0-1-2", "0-1-3", "0-2-1", "0-2-2", "0-2-3", "0-3-1", "0-3-2", "0-3-3"), - nodes - ); - - // 按广度优先寻找 0-2-3 - final String target = HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.find(Node::getChildren, - t -> Objects.equals(t.getValue(), "0-2-3") ? t.getValue() : null - )); - Assertions.assertEquals("0-2-3", target); - - // 按广度优先获取 0-2 的所有子节点 - final List children = HierarchyUtil.traverseByBreadthFirst(root, HierarchyIteratorUtil.collect(Node::getChildren, - t -> Objects.equals(t.getParent(), "0-2") ? t.getValue() : null - )); - Assertions.assertEquals(3, children.size()); - Assertions.assertEquals(new ArrayList<>(Arrays.asList("0-2-1", "0-2-2", "0-2-3")), children); - } - - @Test - void testTraverseByDepthFirst() { - // 按深度优先遍历所有节点 - final Set nodes = new LinkedHashSet<>(); - HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.scan(t -> { - nodes.add(t.getValue()); - return t.getChildren(); - })); - Assertions.assertEquals(13, nodes.size()); - Assertions.assertEquals( - new LinkedHashSet<>(Arrays.asList("0", "0-1", "0-1-1", "0-1-2", "0-1-3", "0-2", "0-2-1", "0-2-2", "0-2-3", "0-3", "0-3-1", "0-3-2", "0-3-3")), - nodes - ); - - // 按深度优先寻找 0-2-3 - final String target = HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.find(Node::getChildren, - t -> Objects.equals(t.getValue(), "0-2-3") ? t.getValue() : null - )); - Assertions.assertEquals("0-2-3", target); - - // 按深度优先获取 0-2 的所有子节点 - final List children = HierarchyUtil.traverseByDepthFirst(root, HierarchyIteratorUtil.collect(Node::getChildren, - t -> Objects.equals(t.getParent(), "0-2") ? t.getValue() : null - )); - Assertions.assertEquals(3, children.size()); - Assertions.assertEquals(new ArrayList<>(Arrays.asList("0-2-1", "0-2-2", "0-2-3")), children); - } - -}