diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 71bdc4ff6..20732ff6d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -430,124 +430,6 @@ public class CollectorUtil { return toMap(Map.Entry::getKey, Map.Entry::getValue); } - /** - *

将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点 - * 因为需要在当前传入数据里查找,所以这是一个结束操作
- * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} 会过滤掉id为null的元素 - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} - * @param isParallel 是否并行去组装,数据量特别大时使用 - * @param 此处是元素类型 - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *

{@code
-	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
-	 * }
- */ - public static , T> Collector> toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final boolean isParallel) { - return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel); - } - - /** - *

将集合转换为树,默认用 {@code parentId == pidValue} 来判断树的根节点,可以为null - * 因为需要在当前传入数据里查找,所以这是一个结束操作
- * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} 会过滤掉id为null的元素 - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param pidValue pid的值 - * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} - * @param isParallel 是否并行去组装,数据量特别大时使用 - * @param 此处是元素类型 - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *

{@code
-	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
-	 * }
- * @author VampireAchao - */ - public static , T> Collector> toTree( - final Function idGetter, - final Function pIdGetter, - final R pidValue, - final BiConsumer> childrenSetter, - final boolean isParallel) { - return Collectors.collectingAndThen(filtering(e -> idGetter.apply(e) != null, groupingBy(pIdGetter, Collectors.toList())), - getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> pIdValuesMap.get(pidValue), childrenSetter, isParallel)); - } - - /** - * 将集合转换为树,自定义根节点的判断条件 - * 因为需要在当前传入数据里查找,所以这是一个结束操作 - * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} 会过滤掉id为null的元素 - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} - * @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> Objects.equals(s.getParentId(),0L) } - * @param isParallel 是否并行处理 - * @param 此处是元素类型 - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *
{@code
-	 * List studentTree = EasyStream.of(students).
-	 * 	.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
-	 * }
- * @author VampireAchao - */ - public static , T> Collector> toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final Predicate parentPredicate, - final boolean isParallel) { - final List parents = new ArrayList<>(); - return Collectors.collectingAndThen(filtering(e -> { - if (parentPredicate.test(e)) { - parents.add(e); - } - return idGetter.apply(e) != null; - }, groupingBy(pIdGetter)), - getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> parents, childrenSetter, isParallel)); - } - - /** - * toTree的内联函数 - * 因为需要在当前传入数据里查找,所以这是一个结束操作 - * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} - * @param parentFactory 顶部数据工厂方法 - * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} - * @param isParallel 是否并行处理 - * @param 此处是元素类型 - * @param 此处是id的泛型限制 - * @return list 组装好的树 - * @author VampireAchao - */ - private static , T> Function>, List> getChildrenFromMapByPidAndSet( - final Function idGetter, - final Function>, List> parentFactory, - final BiConsumer> childrenSetter, - final boolean isParallel) { - return pIdValuesMap -> { - EasyStream.of(pIdValuesMap.values(), isParallel).flat(Function.identity()) - .forEach(value -> { - final List children = pIdValuesMap.get(idGetter.apply(value)); - if (children != null) { - childrenSetter.accept(value, children); - } - }); - return parentFactory.apply(pIdValuesMap); - }; - } - - /** *

过滤

* diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java index 5c61433a0..258518624 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java @@ -205,83 +205,6 @@ public interface TerminableWrappedStream index.incrementAndGet(), valueMapper, (l, r) -> r); } - - /** - *

将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点 - * 因为需要在当前传入数据里查找,所以这是一个结束操作
- * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *

{@code
-	 * List studentTree = EasyStream.of(students).
-	 * 	toTree(Student::getId, Student::getParentId, Student::setChildren);
-	 * }
- * @author VampireAchao - */ - default > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter) { - return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel())); - } - - /** - *

将集合转换为树,传入 {@code parentId == pidValue} 来判断树的根节点 - * 因为需要在当前传入数据里查找,所以这是一个结束操作
- * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param pIdValue parentId的值,支持 {@code null} - * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *

{@code
-	 * List studentTree = EasyStream.of(students).
-	 * 	toTree(Student::getId, Student::getParentId, 0L, Student::setChildren);
-	 * }
- * @author VampireAchao - */ - default > List toTree( - final Function idGetter, - final Function pIdGetter, - final R pIdValue, - final BiConsumer> childrenSetter) { - return collect(CollectorUtil.toTree(idGetter, pIdGetter, pIdValue, childrenSetter, isParallel())); - } - - /** - * 将集合转换为树,自定义根节点的判断条件 - * 因为需要在当前传入数据里查找,所以这是一个结束操作 - * - * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} - * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} - * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} - * @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> Objects.equals(s.getParentId(),0L) } - * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树
- * eg: - *
{@code
-	 * List studentTree = EasyStream.of(students).
-	 * 	.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
-	 * }
- * @author VampireAchao - */ - default > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final Predicate parentPredicate) { - return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel())); - } - - - // endregion - // region ============ to zip ============ /** diff --git a/hutool-core/src/main/java/cn/hutool/core/tree/BeanTree.java b/hutool-core/src/main/java/cn/hutool/core/tree/BeanTree.java new file mode 100644 index 000000000..aa0cf12df --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/tree/BeanTree.java @@ -0,0 +1,217 @@ +package cn.hutool.core.tree; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.func.*; +import cn.hutool.core.stream.EasyStream; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 本类是用于构建树的工具类,特点是采取lambda,以及满足指定类型的Bean进行树操作 + * Bean需要满足三个属性: + *
    + *
  • 包含不为null的主键(例如id)
  • + *
  • 包含容许为null的关联外键(例如parentId)
  • + *
  • 包含自身的子集,例如类型为List的children
  • + *
+ * 本类的构建方法是通过{@code BeanTree.of} 进行构建,例如: + *
{@code final BeanTree beanTree = BeanTree.of(JavaBean::getId, JavaBean::getParentId, null, JavaBean::getChildren, JavaBean::setChildren);}
+ * 得到的BeanTree实例可以调用toTree方法,将集合转换为树,例如: + *
{@code final List javaBeanTree = beanTree.toTree(originJavaBeanList);}
+ * 也可以将已有的树转换为集合,例如: + *
{@code final List javaBeanList = beanTree.flat(originJavaBeanTree);}
+ * + * @author VampireAchao + * @author emptypoint + * @author CreateSequence + * 最后,引用一句电影经典台词: 无处安放的双手,以及无处安放的灵魂。——《Hello!树先生》 + */ +public class BeanTree> { + + /** + * 主键getter + */ + private final SerFunction idGetter; + /** + * 外键getter + */ + private final SerFunction pidGetter; + /** + * 外键匹配值(保留此属性主要是性能较外键条件匹配稍微好一点) + */ + private final R pidValue; + /** + * 外键匹配条件 + */ + private final SerPredicate parentPredicate; + /** + * 子集getter + */ + private final SerFunction> childrenGetter; + /** + * 子集setter + */ + private final SerBiConsumer> childrenSetter; + + private BeanTree(final SerFunction idGetter, + final SerFunction pidGetter, + final R pidValue, + final SerPredicate parentPredicate, + final SerFunction> childrenGetter, + final SerBiConsumer> childrenSetter) { + this.idGetter = Objects.requireNonNull(idGetter, "idGetter must not be null"); + this.pidGetter = Objects.requireNonNull(pidGetter, "pidGetter must not be null"); + this.pidValue = pidValue; + this.parentPredicate = parentPredicate; + this.childrenGetter = Objects.requireNonNull(childrenGetter, "childrenGetter must not be null"); + this.childrenSetter = Objects.requireNonNull(childrenSetter, "childrenSetter must not be null"); + } + + /** + * 构建BeanTree + * + * @param idGetter 主键getter,例如 {@code JavaBean::getId} + * @param pidGetter 外键getter,例如 {@code JavaBean::getParentId} + * @param pidValue 根节点的外键值,例如 {@code null} + * @param childrenGetter 子集getter,例如 {@code JavaBean::getChildren} + * @param childrenSetter 子集setter,例如 {@code JavaBean::setChildren} + * @param Bean类型 + * @param 主键、外键类型 + * @return BeanTree + */ + public static > BeanTree of(final SerFunction idGetter, + final SerFunction pidGetter, + final R pidValue, + final SerFunction> childrenGetter, + final SerBiConsumer> childrenSetter) { + return new BeanTree<>(idGetter, pidGetter, pidValue, null, childrenGetter, childrenSetter); + } + + /** + * 构建BeanTree + * + * @param idGetter 主键getter,例如 {@code JavaBean::getId} + * @param pidGetter 外键getter,例如 {@code JavaBean::getParentId} + * @param parentPredicate 根节点判断条件,例如 {@code o -> Objects.isNull(o.getParentId())} + * @param childrenGetter 子集getter,例如 {@code JavaBean::getChildren} + * @param childrenSetter 子集setter,例如 {@code JavaBean::setChildren} + * @param Bean类型 + * @param 主键、外键类型 + * @return BeanTree + */ + public static > BeanTree ofMatch(final SerFunction idGetter, + final SerFunction pidGetter, + final SerPredicate parentPredicate, + final SerFunction> childrenGetter, + final SerBiConsumer> childrenSetter) { + return new BeanTree<>(idGetter, pidGetter, null, Objects.requireNonNull(parentPredicate, "parentPredicate must not be null"), childrenGetter, childrenSetter); + } + + /** + * 将集合转换为树 + * + * @param list 集合 + * @return 转换后的树 + */ + public List toTree(final List list) { + if (CollUtil.isEmpty(list)) { + return ListUtil.zero(); + } + if (Objects.isNull(parentPredicate)) { + final Map> pIdValuesMap = EasyStream.of(list) + .peek(e -> Objects.requireNonNull(idGetter.apply(e), () -> "The id of tree node must not be null " + e)) + .group(pidGetter); + final List parents = pIdValuesMap.getOrDefault(pidValue, new ArrayList<>()); + findChildren(list, pIdValuesMap); + return parents; + } + final List parents = new ArrayList<>(); + final Map> pIdValuesMap = EasyStream.of(list).peek(e -> { + if (parentPredicate.test(e)) { + parents.add(e); + } + Objects.requireNonNull(idGetter.apply(e)); + }).group(pidGetter); + findChildren(list, pIdValuesMap); + return parents; + } + + /** + * 将树扁平化为集合,相当于将树里的所有节点都放到一个集合里 + *

本方法会主动将节点的子集合字段置为null

+ * + * @param tree 树 + * @return 集合 + */ + @SuppressWarnings("unchecked") + public List flat(final List tree) { + final AtomicReference>> recursiveRef = new AtomicReference<>(); + final Function> recursive = e -> EasyStream.of(childrenGetter.apply(e)).flat(recursiveRef.get()).unshift(e); + recursiveRef.set(recursive); + return EasyStream.of(tree).flat(recursive).peek(e -> childrenSetter.accept(e, null)).toList(); + } + + /** + * 树的过滤操作,本方法一般适用于寻找某人所在部门以及所有上级部门类似的逻辑 + * 通过{@link SerPredicate}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点
+ * 即,一条路径上只要有一个节点符合条件,就保留整条路径上的节点 + * + * @param tree 树 + * @param condition 节点过滤规则函数,只需处理本级节点本身即可,{@link SerPredicate#test(Object)}为{@code true}保留 + * @return 过滤后的树 + */ + public List filter(final List tree, final SerPredicate condition) { + Objects.requireNonNull(condition, "filter condition must be not null"); + final AtomicReference> recursiveRef = new AtomicReference<>(); + final Predicate recursive = SerPredicate.multiOr(condition::test, + e -> Opt.ofEmptyAble(childrenGetter.apply(e)) + .map(children -> EasyStream.of(children).filter(recursiveRef.get()).toList()) + .peek(children -> childrenSetter.accept(e, children)) + .filter(s -> !s.isEmpty()).isPresent()); + recursiveRef.set(recursive); + return EasyStream.of(tree).filter(recursive).toList(); + } + + /** + * 树节点遍历操作 + * + * @param tree 数 + * @param action 操作 + * @return 树 + */ + public List forEach(final List tree, final SerConsumer action) { + Objects.requireNonNull(action, "action must be not null"); + final AtomicReference> recursiveRef = new AtomicReference<>(); + final Consumer recursive = SerConsumer.multi(action::accept, + e -> Opt.ofEmptyAble(childrenGetter.apply(e)) + .peek(children -> EasyStream.of(children).forEach(recursiveRef.get()))); + recursiveRef.set(recursive); + EasyStream.of(tree).forEach(recursive); + return tree; + } + + /** + * 内联函数,设置每个节点的子集 + * + * @param list 集合 + * @param pIdValuesMap 父id与子集的映射 + */ + private void findChildren(final List list, final Map> pIdValuesMap) { + for (T node : list) { + final List children = pIdValuesMap.get(idGetter.apply(node)); + if (children != null) { + childrenSetter.accept(node, children); + } + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java index a891fc2f4..0f50b8337 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java @@ -2,19 +2,14 @@ package cn.hutool.core.stream; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.BooleanUtil; -import lombok.Data; -import lombok.experimental.Tolerate; import org.junit.Assert; import org.junit.Test; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; /** @@ -438,124 +433,4 @@ public class EasyStreamTest { Assert.assertTrue(EasyStream.of(1).isNotEmpty()); } - @Test - public void testToTree() { - Consumer test = o -> { - final List studentTree = EasyStream - .of( - Student.builder().id(1L).name("dromara").build(), - Student.builder().id(2L).name("baomidou").build(), - Student.builder().id(3L).name("hutool").parentId(1L).build(), - Student.builder().id(4L).name("sa-token").parentId(1L).build(), - Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(), - Student.builder().id(6L).name("looly").parentId(3L).build(), - Student.builder().id(7L).name("click33").parentId(4L).build(), - Student.builder().id(8L).name("jobob").parentId(5L).build() - ) - // just 3 lambda,top parentId is null - .toTree(Student::getId, Student::getParentId, Student::setChildren); - Assert.assertEquals(asList( - Student.builder().id(1L).name("dromara") - .children(asList(Student.builder().id(3L).name("hutool").parentId(1L) - .children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build())) - .build(), - Student.builder().id(4L).name("sa-token").parentId(1L) - .children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build())) - .build())) - .build(), - Student.builder().id(2L).name("baomidou") - .children(singletonList( - Student.builder().id(5L).name("mybatis-plus").parentId(2L) - .children(singletonList( - Student.builder().id(8L).name("jobob").parentId(5L).build() - )) - .build())) - .build() - ), studentTree); - }; - test = test.andThen(o -> { - final List studentTree = EasyStream - .of( - Student.builder().id(1L).name("dromara").matchParent(true).build(), - Student.builder().id(2L).name("baomidou").matchParent(true).build(), - Student.builder().id(3L).name("hutool").parentId(1L).build(), - Student.builder().id(4L).name("sa-token").parentId(1L).build(), - Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(), - Student.builder().id(6L).name("looly").parentId(3L).build(), - Student.builder().id(7L).name("click33").parentId(4L).build(), - Student.builder().id(8L).name("jobob").parentId(5L).build() - ) - // just 4 lambda ,top by condition - .toTree(Student::getId, Student::getParentId, Student::setChildren, s -> BooleanUtil.isTrue(s.getMatchParent())); - Assert.assertEquals(asList( - Student.builder().id(1L).name("dromara").matchParent(true) - .children(asList(Student.builder().id(3L).name("hutool").parentId(1L) - .children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build())) - .build(), - Student.builder().id(4L).name("sa-token").parentId(1L) - .children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build())) - .build())) - .build(), - Student.builder().id(2L).name("baomidou").matchParent(true) - .children(singletonList( - Student.builder().id(5L).name("mybatis-plus").parentId(2L) - .children(singletonList( - Student.builder().id(8L).name("jobob").parentId(5L).build() - )) - .build())) - .build() - ), studentTree); - }); - test.accept(new Object()); - } - - @Test - public void testFlatTree() { - final List studentTree = asList( - Student.builder().id(1L).name("dromara") - .children(asList(Student.builder().id(3L).name("hutool").parentId(1L) - .children(singletonList(Student.builder().id(6L).name("looly").parentId(3L).build())) - .build(), - Student.builder().id(4L).name("sa-token").parentId(1L) - .children(singletonList(Student.builder().id(7L).name("click33").parentId(4L).build())) - .build())) - .build(), - Student.builder().id(2L).name("baomidou") - .children(singletonList( - Student.builder().id(5L).name("mybatis-plus").parentId(2L) - .children(singletonList( - Student.builder().id(8L).name("jobob").parentId(5L).build() - )) - .build())) - .build() - ); - Assert.assertEquals(asList( - Student.builder().id(1L).name("dromara").build(), - Student.builder().id(2L).name("baomidou").build(), - Student.builder().id(3L).name("hutool").parentId(1L).build(), - Student.builder().id(4L).name("sa-token").parentId(1L).build(), - Student.builder().id(5L).name("mybatis-plus").parentId(2L).build(), - Student.builder().id(6L).name("looly").parentId(3L).build(), - Student.builder().id(7L).name("click33").parentId(4L).build(), - Student.builder().id(8L).name("jobob").parentId(5L).build() - ), EasyStream.of(studentTree).flatTree(Student::getChildren, Student::setChildren).sorted(Comparator.comparingLong(Student::getId)).toList()); - - } - - @Data - @lombok.Builder - public static class Student { - private String name; - private Integer age; - private Long id; - private Long parentId; - private List children; - private Boolean matchParent; - - @Tolerate - public Student() { - // this is an accessible parameterless constructor. - } - } - } diff --git a/hutool-core/src/test/java/cn/hutool/core/tree/BeanTreeTest.java b/hutool-core/src/test/java/cn/hutool/core/tree/BeanTreeTest.java new file mode 100644 index 000000000..5db467bd9 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/tree/BeanTreeTest.java @@ -0,0 +1,129 @@ +package cn.hutool.core.tree; + +import cn.hutool.core.stream.EasyStream; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.Tolerate; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Comparator; +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +/** + * TreeHelperTest + * + * @author VampireAchao + */ +public class BeanTreeTest { + + @Data + @Builder + private static class JavaBean { + @Tolerate + public JavaBean() { + // this is an accessible parameterless constructor. + } + + private String name; + private Integer age; + private Long id; + private Long parentId; + private List children; + private Boolean matchParent; + } + + List originJavaBeanList; + List originJavaBeanTree; + BeanTree beanTree; + + @Before + public void setUp() { + originJavaBeanList = EasyStream + .of( + JavaBean.builder().id(1L).name("dromara").matchParent(true).build(), + JavaBean.builder().id(2L).name("baomidou").matchParent(true).build(), + JavaBean.builder().id(3L).name("hutool").parentId(1L).build(), + JavaBean.builder().id(4L).name("sa-token").parentId(1L).build(), + JavaBean.builder().id(5L).name("mybatis-plus").parentId(2L).build(), + JavaBean.builder().id(6L).name("looly").parentId(3L).build(), + JavaBean.builder().id(7L).name("click33").parentId(4L).build(), + JavaBean.builder().id(8L).name("jobob").parentId(5L).build() + ).toList(); + originJavaBeanTree = asList( + JavaBean.builder().id(1L).name("dromara").matchParent(true) + .children(asList( + JavaBean.builder().id(3L).name("hutool").parentId(1L) + .children(singletonList(JavaBean.builder().id(6L).name("looly").parentId(3L).build())) + .build(), + JavaBean.builder().id(4L).name("sa-token").parentId(1L) + .children(singletonList(JavaBean.builder().id(7L).name("click33").parentId(4L).build())) + .build())) + .build(), + JavaBean.builder().id(2L).name("baomidou").matchParent(true) + .children(singletonList( + JavaBean.builder().id(5L).name("mybatis-plus").parentId(2L) + .children(singletonList( + JavaBean.builder().id(8L).name("jobob").parentId(5L).build() + )) + .build())) + .build() + ); + beanTree = BeanTree.of(JavaBean::getId, JavaBean::getParentId, null, JavaBean::getChildren, JavaBean::setChildren); + } + + @Test + public void testToTree() { + final List javaBeanTree = beanTree.toTree(originJavaBeanList); + Assert.assertEquals(originJavaBeanTree, javaBeanTree); + final BeanTree conditionBeanTree = BeanTree.ofMatch(JavaBean::getId, JavaBean::getParentId, s -> Boolean.TRUE.equals(s.getMatchParent()), JavaBean::getChildren, JavaBean::setChildren); + Assert.assertEquals(originJavaBeanTree, conditionBeanTree.toTree(originJavaBeanList)); + } + + @Test + public void testFlat() { + final List javaBeanList = beanTree.flat(originJavaBeanTree); + javaBeanList.sort(Comparator.comparing(JavaBean::getId)); + Assert.assertEquals(originJavaBeanList, javaBeanList); + } + + @Test + public void testFilter() { + final List javaBeanTree = beanTree.filter(originJavaBeanTree, s -> "looly".equals(s.getName())); + Assert.assertEquals(singletonList( + JavaBean.builder().id(1L).name("dromara").matchParent(true) + .children(singletonList(JavaBean.builder().id(3L).name("hutool").parentId(1L) + .children(singletonList(JavaBean.builder().id(6L).name("looly").parentId(3L).build())) + .build())) + .build()), + javaBeanTree); + } + + @Test + public void testForeach() { + final List javaBeanList = beanTree.forEach(originJavaBeanTree, s -> s.setName("【open source】" + s.getName())); + Assert.assertEquals(asList( + JavaBean.builder().id(1L).name("【open source】dromara").matchParent(true) + .children(asList(JavaBean.builder().id(3L).name("【open source】hutool").parentId(1L) + .children(singletonList(JavaBean.builder().id(6L).name("【open source】looly").parentId(3L).build())) + .build(), + JavaBean.builder().id(4L).name("【open source】sa-token").parentId(1L) + .children(singletonList(JavaBean.builder().id(7L).name("【open source】click33").parentId(4L).build())) + .build())) + .build(), + JavaBean.builder().id(2L).name("【open source】baomidou").matchParent(true) + .children(singletonList( + JavaBean.builder().id(5L).name("【open source】mybatis-plus").parentId(2L) + .children(singletonList( + JavaBean.builder().id(8L).name("【open source】jobob").parentId(5L).build() + )) + .build())) + .build() + ), javaBeanList); + } + +}