mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
feat(5.4.1): 增加TreeConvert
增加TreeConvert,方便的构建树形数据结构
This commit is contained in:
parent
a0f7ec1d0e
commit
a92d442c26
@ -0,0 +1,18 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 叶容器-当对象存在树型结构,通过此注解标注属性告知存储位置
|
||||
* 一般使用容器存储Set,List等
|
||||
*
|
||||
* @author shadow
|
||||
* @version 5.4.1
|
||||
* @since 5.4.1
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface LeafCollection {
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
/**
|
||||
* 子节点判断函数接口
|
||||
*
|
||||
* @author shadow
|
||||
* @version 5.4.1
|
||||
* @since 5.4.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LeafDecide<T> {
|
||||
|
||||
/**
|
||||
* 是否为子节点
|
||||
*
|
||||
* @param root root target
|
||||
* @param leaf compare target
|
||||
* @return 是否从属于 root的子节点
|
||||
*/
|
||||
boolean isLeaf(T root, T leaf);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
/**
|
||||
* 根判定函数接口
|
||||
*
|
||||
* @author shadow
|
||||
* @version 5.4.1
|
||||
* @since 5.4.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RootDecide<T> {
|
||||
|
||||
/**
|
||||
* 判断是否为根
|
||||
*
|
||||
* @param root 根节点
|
||||
* @return 是否是根节点
|
||||
*/
|
||||
boolean isRoot(T root);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package cn.hutool.core.exceptions;
|
||||
|
||||
/**
|
||||
* 叶子容器未找到异常
|
||||
*
|
||||
* @author shadow
|
||||
* @version 5.4.1
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public class LeafCollectionNotFoundException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* @param message message
|
||||
*/
|
||||
public LeafCollectionNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package cn.hutool.core.lang.tree;
|
||||
|
||||
import cn.hutool.core.annotation.LeafCollection;
|
||||
import cn.hutool.core.annotation.LeafDecide;
|
||||
import cn.hutool.core.annotation.RootDecide;
|
||||
import cn.hutool.core.exceptions.LeafCollectionNotFoundException;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 树结构转换工具
|
||||
* <p>
|
||||
* 由{@link LeafCollection}注解标注实体,用于存放子节点的容器
|
||||
* 函数式两个接口{@link RootDecide} {@link LeafDecide} 判断父子节点定义
|
||||
* <p>
|
||||
* 这样可以尽量不更改接口返回的泛型结构
|
||||
*
|
||||
* @author shadow (https://github.com/SHADOW-LI0327)
|
||||
* @version 5.4.1
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public class TreeConvert {
|
||||
|
||||
/**
|
||||
* 生成树结构
|
||||
* 通过反射检测LeafCollection注解,转换为父子结构容器,子数据装入LeafCollection容器中
|
||||
* 2020-05-20 修复泛型问题,方便调用
|
||||
*
|
||||
* @param elements 元素组
|
||||
* @param clazz 反射类型 !警告! 不可传入包装类型,后续判断失效将抛出异常
|
||||
* @param rootDecide 根元素判断函数
|
||||
* @param leafDecide 叶子元素判断函数
|
||||
* @param <T> 泛型
|
||||
* @return List<T>
|
||||
*/
|
||||
public static <T> List<T> convert(Collection<T> elements, Class<?> clazz, RootDecide<T> rootDecide, LeafDecide<T> leafDecide) {
|
||||
List<T> treeList = new ArrayList<>();
|
||||
//叶子容器
|
||||
Field leafCollection = null;
|
||||
if (!elements.isEmpty()) {
|
||||
Field[] fields = ReflectUtil.getFields(clazz);
|
||||
//一般扩展属性会写在成员变量的后面,倒着找比较快
|
||||
for (int i = fields.length - 1; i > 0; i--) {
|
||||
if (fields[i].getAnnotation(LeafCollection.class) != null) {
|
||||
//找到既退出迭代查找
|
||||
leafCollection = fields[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
//缺少注解抛出异常
|
||||
if (leafCollection == null) {
|
||||
throw new LeafCollectionNotFoundException("注解LeafCollection未找到,请确认子容器");
|
||||
}
|
||||
}
|
||||
//迭代容器
|
||||
for (T element : elements) {
|
||||
if (rootDecide.isRoot(element)) {
|
||||
//设立根目录
|
||||
treeList.add(element);
|
||||
//递归
|
||||
try {
|
||||
sort(element, elements, leafDecide, clazz, leafCollection);
|
||||
} catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return treeList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 递归排序
|
||||
*
|
||||
* @param root 根元素
|
||||
* @param elements 元素组
|
||||
* @param leafDecide 叶子判断函数
|
||||
* @param leafField 叶子容器
|
||||
* @param clazz 类型
|
||||
* @param <T> 泛型
|
||||
* @return List<T>
|
||||
* @throws IntrospectionException e
|
||||
* @throws InvocationTargetException e
|
||||
* @throws IllegalAccessException e
|
||||
*/
|
||||
private static <T> List<T> sort(T root, Collection<T> elements, LeafDecide<T> leafDecide, Class<?> clazz, Field leafField) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
|
||||
List<T> subMenu = null;
|
||||
for (T element : elements) {
|
||||
if (leafDecide.isLeaf(root, element)) {
|
||||
if (subMenu == null) {
|
||||
subMenu = new ArrayList<>();
|
||||
}
|
||||
List<T> leaf = sort(element, elements, leafDecide, clazz, leafField);
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(leafField.getName(), clazz);
|
||||
propertyDescriptor.getWriteMethod().invoke(element, leaf);
|
||||
subMenu.add(element);
|
||||
}
|
||||
}
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(leafField.getName(), clazz);
|
||||
propertyDescriptor.getWriteMethod().invoke(root, subMenu);
|
||||
return subMenu;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package cn.hutool.core.lang.tree;
|
||||
|
||||
import cn.hutool.core.annotation.LeafCollection;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 树转换测试
|
||||
*/
|
||||
public class TreeConvertTest {
|
||||
|
||||
// 子父级测试数据
|
||||
private List<Dept> parentChildMaterials = Arrays.asList(
|
||||
new Dept("00000001", "0", "xxx公司"),
|
||||
new Dept("00000002", "00000001", "市场部"),
|
||||
new Dept("00000003", "00000001", "行政部"),
|
||||
new Dept("00000004", "00000001", "IT部"),
|
||||
new Dept("00000005", "00000002", "华南"),
|
||||
new Dept("00000006", "00000002", "华北"),
|
||||
new Dept("00000007", "00000002", "华东")
|
||||
);
|
||||
|
||||
// 排序号测试数据
|
||||
private List<Dept> sortNoMaterials = Arrays.asList(
|
||||
new Dept("00", "xxx公司"),
|
||||
new Dept("0010", "市场部"),
|
||||
new Dept("0020", "行政部"),
|
||||
new Dept("0030", "IT部"),
|
||||
new Dept("001010", "华南"),
|
||||
new Dept("001020", "华北"),
|
||||
new Dept("001030", "华东")
|
||||
);
|
||||
|
||||
// 父子结构测试
|
||||
@Test
|
||||
public void testParentChild() {
|
||||
List<Dept> tree = TreeConvert.convert(parentChildMaterials, Dept.class,
|
||||
root -> "0".equals(root.getParentId()),
|
||||
(root, leaf) -> leaf.getParentId().equals(root.getDeptId())
|
||||
);
|
||||
Assert.assertEquals("0", tree.get(0).getParentId());
|
||||
}
|
||||
|
||||
// 排序号测试
|
||||
@Test
|
||||
public void testSortNo() {
|
||||
List<Dept> tree = TreeConvert.convert(sortNoMaterials, Dept.class,
|
||||
root -> "00".equals(root.getSortNo()),
|
||||
(root, leaf) ->
|
||||
leaf.getSortNo().startsWith(root.getSortNo()) &&
|
||||
!leaf.getSortNo().equals(root.getSortNo()) &&
|
||||
leaf.getSortNo().length() - root.getSortNo().length() == 2
|
||||
);
|
||||
Assert.assertEquals("0", tree.get(0).getParentId());
|
||||
}
|
||||
|
||||
// 测试实体类
|
||||
class Dept {
|
||||
private String deptId;
|
||||
private String sortNo;
|
||||
private String parentId;
|
||||
private String deptName;
|
||||
@LeafCollection
|
||||
private List<Dept> child;
|
||||
public Dept() {
|
||||
}
|
||||
|
||||
public Dept(String sortNo, String deptName) {
|
||||
this.deptName = deptName;
|
||||
this.sortNo = sortNo;
|
||||
}
|
||||
|
||||
public Dept(String deptId, String parentId, String deptName) {
|
||||
this.deptId = deptId;
|
||||
this.parentId = parentId;
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public String getDeptId() {
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setDeptId(String deptId) {
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(String parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public List<Dept> getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(List<Dept> child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public String getSortNo() {
|
||||
return sortNo;
|
||||
}
|
||||
|
||||
public void setSortNo(String sortNo) {
|
||||
this.sortNo = sortNo;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user