feat(5.4.1): 增加TreeConvert

增加TreeConvert,方便的构建树形数据结构
This commit is contained in:
晴雨夜 2020-08-26 11:26:44 +08:00
parent a0f7ec1d0e
commit a92d442c26
6 changed files with 307 additions and 0 deletions

View File

@ -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 {
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}