mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
feat(5.4.1): 增加TreeConvert
增加TreeConvert,方便的构建树形数据结构
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user