diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/LeafCollection.java b/hutool-core/src/main/java/cn/hutool/core/annotation/LeafCollection.java new file mode 100644 index 000000000..a05852149 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/LeafCollection.java @@ -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 { + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/LeafDecide.java b/hutool-core/src/main/java/cn/hutool/core/annotation/LeafDecide.java new file mode 100644 index 000000000..27e568c49 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/LeafDecide.java @@ -0,0 +1,21 @@ +package cn.hutool.core.annotation; + +/** + * 子节点判断函数接口 + * + * @author shadow + * @version 5.4.1 + * @since 5.4.1 + */ +@FunctionalInterface +public interface LeafDecide { + + /** + * 是否为子节点 + * + * @param root root target + * @param leaf compare target + * @return 是否从属于 root的子节点 + */ + boolean isLeaf(T root, T leaf); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/RootDecide.java b/hutool-core/src/main/java/cn/hutool/core/annotation/RootDecide.java new file mode 100644 index 000000000..02c4d5db0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/RootDecide.java @@ -0,0 +1,20 @@ +package cn.hutool.core.annotation; + +/** + * 根判定函数接口 + * + * @author shadow + * @version 5.4.1 + * @since 5.4.1 + */ +@FunctionalInterface +public interface RootDecide { + + /** + * 判断是否为根 + * + * @param root 根节点 + * @return 是否是根节点 + */ + boolean isRoot(T root); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/exceptions/LeafCollectionNotFoundException.java b/hutool-core/src/main/java/cn/hutool/core/exceptions/LeafCollectionNotFoundException.java new file mode 100644 index 000000000..1364dcdf4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/exceptions/LeafCollectionNotFoundException.java @@ -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); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeConvert.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeConvert.java new file mode 100644 index 000000000..d50dfb21c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeConvert.java @@ -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.*; + +/** + * 树结构转换工具 + *

+ * 由{@link LeafCollection}注解标注实体,用于存放子节点的容器 + * 函数式两个接口{@link RootDecide} {@link LeafDecide} 判断父子节点定义 + *

+ * 这样可以尽量不更改接口返回的泛型结构 + * + * @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 泛型 + * @return List + */ + public static List convert(Collection elements, Class clazz, RootDecide rootDecide, LeafDecide leafDecide) { + List 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 泛型 + * @return List + * @throws IntrospectionException e + * @throws InvocationTargetException e + * @throws IllegalAccessException e + */ + private static List sort(T root, Collection elements, LeafDecide leafDecide, Class clazz, Field leafField) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + List subMenu = null; + for (T element : elements) { + if (leafDecide.isLeaf(root, element)) { + if (subMenu == null) { + subMenu = new ArrayList<>(); + } + List 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; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeConvertTest.java new file mode 100644 index 000000000..8dce50835 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeConvertTest.java @@ -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 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 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 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 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 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 getChild() { + return child; + } + + public void setChild(List child) { + this.child = child; + } + + public String getSortNo() { + return sortNo; + } + + public void setSortNo(String sortNo) { + this.sortNo = sortNo; + } + } +}