!884 【6.x】添加BeanTree,用于满足条件的Bean进行树转换与操作

Merge pull request !884 from 阿超/v6-dev
This commit is contained in:
Looly 2022-11-30 01:22:19 +00:00 committed by Gitee
commit 2a48fe02b9
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 346 additions and 320 deletions

View File

@ -430,124 +430,6 @@ public class CollectorUtil {
return toMap(Map.Entry::getKey, Map.Entry::getValue);
}
/**
* <p>将集合转换为树默认用 {@code parentId == null} 来判断树的根节点
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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 <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
* }</pre>
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final boolean isParallel) {
return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel);
}
/**
* <p>将集合转换为树默认用 {@code parentId == pidValue} 来判断树的根节点可以为null
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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 <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
* }</pre>
* @author VampireAchao
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final R pidValue,
final BiConsumer<T, List<T>> 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 <T> 此处是元素类型
* @param <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
* @author VampireAchao
*/
public static <R extends Comparable<R>, T> Collector<T, ?, List<T>> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate,
final boolean isParallel) {
final List<T> 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 <T> 此处是元素类型
* @param <R> 此处是id的泛型限制
* @return list 组装好的树
* @author VampireAchao
*/
private static <R extends Comparable<R>, T> Function<Map<R, List<T>>, List<T>> getChildrenFromMapByPidAndSet(
final Function<T, R> idGetter,
final Function<Map<R, List<T>>, List<T>> parentFactory,
final BiConsumer<T, List<T>> childrenSetter,
final boolean isParallel) {
return pIdValuesMap -> {
EasyStream.of(pIdValuesMap.values(), isParallel).flat(Function.identity())
.forEach(value -> {
final List<T> children = pIdValuesMap.get(idGetter.apply(value));
if (children != null) {
childrenSetter.accept(value, children);
}
});
return parentFactory.apply(pIdValuesMap);
};
}
/**
* <p>过滤</p >
*

View File

@ -205,83 +205,6 @@ public interface TerminableWrappedStream<T, S extends TerminableWrappedStream<T,
return EasyStream.of(toList()).toMap(e -> index.incrementAndGet(), valueMapper, (l, r) -> r);
}
/**
* <p>将集合转换为树默认用 {@code parentId == null} 来判断树的根节点
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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 <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* toTree(Student::getId, Student::getParentId, Student::setChildren);
* }</pre>
* @author VampireAchao
*/
default <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel()));
}
/**
* <p>将集合转换为树传入 {@code parentId == pidValue} 来判断树的根节点
* 因为需要在当前传入数据里查找所以这是一个结束操作 <br>
*
* @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 <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* toTree(Student::getId, Student::getParentId, 0L, Student::setChildren);
* }</pre>
* @author VampireAchao
*/
default <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final R pIdValue,
final BiConsumer<T, List<T>> 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 <R> 此处是idparentId的泛型限制
* @return list 组装好的树 <br>
* eg:
* <pre>{@code
* List<Student> studentTree = EasyStream.of(students).
* .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
* }</pre>
* @author VampireAchao
*/
default <R extends Comparable<R>> List<T> toTree(
final Function<T, R> idGetter,
final Function<T, R> pIdGetter,
final BiConsumer<T, List<T>> childrenSetter,
final Predicate<T> parentPredicate) {
return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel()));
}
// endregion
// region ============ to zip ============
/**

View File

@ -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需要满足三个属性
* <ul>
* <li>包含不为null的主键(例如id)</li>
* <li>包含容许为null的关联外键(例如parentId)</li>
* <li>包含自身的子集例如类型为List的children</li>
* </ul>
* 本类的构建方法是通过{@code BeanTree.of} 进行构建例如
* <pre>{@code final BeanTree beanTree = BeanTree.of(JavaBean::getId, JavaBean::getParentId, null, JavaBean::getChildren, JavaBean::setChildren);}</pre>
* 得到的BeanTree实例可以调用toTree方法将集合转换为树例如
* <pre>{@code final List<JavaBean> javaBeanTree = beanTree.toTree(originJavaBeanList);}</pre>
* 也可以将已有的树转换为集合例如
* <pre>{@code final List<JavaBean> javaBeanList = beanTree.flat(originJavaBeanTree);}</pre>
*
* @author VampireAchao
* @author emptypoint
* @author CreateSequence
* 最后引用一句电影经典台词 无处安放的双手以及无处安放的灵魂Hello!树先生
*/
public class BeanTree<T, R extends Comparable<R>> {
/**
* 主键getter
*/
private final SerFunction<T, R> idGetter;
/**
* 外键getter
*/
private final SerFunction<T, R> pidGetter;
/**
* 外键匹配值(保留此属性主要是性能较外键条件匹配稍微好一点)
*/
private final R pidValue;
/**
* 外键匹配条件
*/
private final SerPredicate<T> parentPredicate;
/**
* 子集getter
*/
private final SerFunction<T, List<T>> childrenGetter;
/**
* 子集setter
*/
private final SerBiConsumer<T, List<T>> childrenSetter;
private BeanTree(final SerFunction<T, R> idGetter,
final SerFunction<T, R> pidGetter,
final R pidValue,
final SerPredicate<T> parentPredicate,
final SerFunction<T, List<T>> childrenGetter,
final SerBiConsumer<T, List<T>> 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 <T> Bean类型
* @param <R> 主键外键类型
* @return BeanTree
*/
public static <T, R extends Comparable<R>> BeanTree<T, R> of(final SerFunction<T, R> idGetter,
final SerFunction<T, R> pidGetter,
final R pidValue,
final SerFunction<T, List<T>> childrenGetter,
final SerBiConsumer<T, List<T>> 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 <T> Bean类型
* @param <R> 主键外键类型
* @return BeanTree
*/
public static <T, R extends Comparable<R>> BeanTree<T, R> ofMatch(final SerFunction<T, R> idGetter,
final SerFunction<T, R> pidGetter,
final SerPredicate<T> parentPredicate,
final SerFunction<T, List<T>> childrenGetter,
final SerBiConsumer<T, List<T>> childrenSetter) {
return new BeanTree<>(idGetter, pidGetter, null, Objects.requireNonNull(parentPredicate, "parentPredicate must not be null"), childrenGetter, childrenSetter);
}
/**
* 将集合转换为树
*
* @param list 集合
* @return 转换后的树
*/
public List<T> toTree(final List<T> list) {
if (CollUtil.isEmpty(list)) {
return ListUtil.zero();
}
if (Objects.isNull(parentPredicate)) {
final Map<R, List<T>> 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<T> parents = pIdValuesMap.getOrDefault(pidValue, new ArrayList<>());
findChildren(list, pIdValuesMap);
return parents;
}
final List<T> parents = new ArrayList<>();
final Map<R, List<T>> 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;
}
/**
* 将树扁平化为集合相当于将树里的所有节点都放到一个集合里
* <p>本方法会主动将节点的子集合字段置为null</p>
*
* @param tree
* @return 集合
*/
@SuppressWarnings("unchecked")
public List<T> flat(final List<T> tree) {
final AtomicReference<Function<T, EasyStream<T>>> recursiveRef = new AtomicReference<>();
final Function<T, EasyStream<T>> 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}指定的过滤规则本节点或子节点满足过滤条件则保留当前节点否则抛弃节点及其子节点<br>
* 一条路径上只要有一个节点符合条件就保留整条路径上的节点
*
* @param tree
* @param condition 节点过滤规则函数只需处理本级节点本身即可{@link SerPredicate#test(Object)}{@code true}保留
* @return 过滤后的树
*/
public List<T> filter(final List<T> tree, final SerPredicate<T> condition) {
Objects.requireNonNull(condition, "filter condition must be not null");
final AtomicReference<Predicate<T>> recursiveRef = new AtomicReference<>();
final Predicate<T> 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<T> forEach(final List<T> tree, final SerConsumer<T> action) {
Objects.requireNonNull(action, "action must be not null");
final AtomicReference<Consumer<T>> recursiveRef = new AtomicReference<>();
final Consumer<T> 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<T> list, final Map<R, List<T>> pIdValuesMap) {
for (T node : list) {
final List<T> children = pIdValuesMap.get(idGetter.apply(node));
if (children != null) {
childrenSetter.accept(node, children);
}
}
}
}

View File

@ -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<Object> test = o -> {
final List<Student> 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<Student> 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<Student> 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<Student> children;
private Boolean matchParent;
@Tolerate
public Student() {
// this is an accessible parameterless constructor.
}
}
}

View File

@ -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<JavaBean> children;
private Boolean matchParent;
}
List<JavaBean> originJavaBeanList;
List<JavaBean> originJavaBeanTree;
BeanTree<JavaBean, Long> 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<JavaBean> javaBeanTree = beanTree.toTree(originJavaBeanList);
Assert.assertEquals(originJavaBeanTree, javaBeanTree);
final BeanTree<JavaBean, Long> 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<JavaBean> javaBeanList = beanTree.flat(originJavaBeanTree);
javaBeanList.sort(Comparator.comparing(JavaBean::getId));
Assert.assertEquals(originJavaBeanList, javaBeanList);
}
@Test
public void testFilter() {
final List<JavaBean> 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<JavaBean> 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);
}
}