cqyt/shapelight-admin/src/main/java/net/shapelight/common/utils/TreeUtils.java

449 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package net.shapelight.common.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import net.shapelight.modules.sys.entity.SysDeptEntity;
import org.apache.commons.lang.StringUtils;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.util.*;
public class TreeUtils {
/*public static boolean isEmpty(String str) {
if (str != null && !"".equals(str.trim())) {
return false;
}
return true;
}*/
/**
* 集合转树结构
*
* @param collection 目标集合
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection, @NotNull Class<T> clazz) {
return toTree(collection, null, null, null, clazz);
}
/**
* 集合转树结构,注意,使用此方法,则集合元素必须继承ITree接口
*
* @param collection 目标集合
* @param <T>
* @return
*/
public static <T extends ITree> Collection<T> toTree(@NotNull Collection<T> collection) {
try {
if (collection == null || collection.isEmpty()) return null;// 如果目标集合为空,直接返回一个空树
// 找出所有的根节点
Collection<T> roots = null;
if (collection.getClass().isAssignableFrom(Set.class)) roots = new HashSet<>();
else roots = new ArrayList<>();
for (T tree : collection) {
Object o = ITree.class.getMethod("getParent").invoke(tree);
if (o instanceof String) {
if (StringUtils.isEmpty((String) o)) {
roots.add(tree);
}
} else if (o == null) {
roots.add(tree);
}
}
// 从目标集合移除所有的根节点
collection.removeAll(roots);
// 为根节点添加孩子节点
for (T tree : roots) {
addChild(tree, collection);
}
return roots;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 集合转树结构
*
* @param collection 目标集合
* @param id 被依赖字段名称
* @param parent 依赖字段名称
* @param children 子节点集合属性名称
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection, String id, String parent, String children, @NotNull Class<T> clazz) {
try {
if (collection == null || collection.isEmpty()) return null;// 如果目标集合为空,直接返回一个空树
if (StringUtils.isEmpty(id)) id = "id";// 如果被依赖字段名称为空则默认为id
if (StringUtils.isEmpty(parent)) parent = "parent";// 如果依赖字段为空则默认为parent
if (StringUtils.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Collection<T> roots = null;// 初始化根节点集合
if (collection.getClass().isAssignableFrom(Set.class))
roots = new HashSet<>();// 如果目标节点是一个set集合,则初始化根节点集合为hashset
else roots = new ArrayList<>();// 否则初始化为Arraylist,
// 这里集合初始化只分2中,要么是hashset,要么ArrayList,因为这两种最常用,其他不常用的摒弃
Field idField = null;
try {
idField = clazz.getDeclaredField(id);// 获取依赖字段
} catch (NoSuchFieldException e1) {
idField = clazz.getSuperclass().getDeclaredField(id);
}
Field parentField = null;
try {
parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
} catch (NoSuchFieldException e1) {
parentField = clazz.getSuperclass().getDeclaredField(parent);
}
Field childrenField = null;// 获取孩子字段
try {
childrenField = clazz.getDeclaredField(children);
} catch (NoSuchFieldException e1) {
childrenField = clazz.getSuperclass().getDeclaredField(children);
}
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
// 找出所有的根节点
/*for (T c : collection) {
Object o = parentField.get(c);
if (o instanceof String) {
if (StringUtils.isEmpty((String) o)) {// 如果父节点为空则说明是根节点,添加到根节点集合
roots.add(c);
}
} else {
if (o == null) {
roots.add(c);
}
}
}*/
for (T c : collection) {
Object o = parentField.get(c);
if (o instanceof Long) {
if (((Long)o).longValue() == 0L) {// 如果父节点为空则说明是根节点,添加到根节点集合
roots.add(c);
}
} else {
if (o == null) {
roots.add(c);
}
}
}
// 从目标集合移除所有根节点
collection.removeAll(roots);
for (T c : roots) {// 遍历根节点,依次添加子节点
addChild(c, collection, idField, parentField, childrenField);
}
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
return roots;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static <T extends ITree> void addChild(T tree, Collection<T> collection) {
try {
Object id = ITree.class.getMethod("getId").invoke(tree);
Collection<T> children = (Collection<T>) ITree.class.getMethod("getChildren").invoke(tree);
for (T cc : collection) {
Object o = ITree.class.getMethod("getParent").invoke(cc);
if (id.equals(o)) {// 如果当前节点的被依赖值和目标节点的被依赖值相等,则说明,当前节点是目标节点的子节点
if (children == null) {// 如果目标节点的孩子集合为null,初始化目标节点的孩子集合
if (collection.getClass().isAssignableFrom(Set.class)) {// 如果目标集合是一个set集合,则初始化目标节点的孩子节点集合为set
children = new HashSet<>();
} else children = new ArrayList<>();// 否则初始化为list
}
// 将当前节点添加到目标节点的孩子节点
children.add(cc);
// 重设目标节点的孩子节点集合,这里必须重设,因为如果目标节点的孩子节点是null的话,这样是没有地址的,就会造成数据丢失,所以必须重设,如果目标节点所在类的孩子节点初始化为一个空集合,而不是null,则可以不需要这一步,因为java一切皆指针
ITree.class.getMethod("setChildren", Collection.class).invoke(tree, children);
// 递归添加孩子节点
addChild(cc, collection);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子节点,此方法为私有,不能为公开,否则类修改信息无法恢复,后面有公开方法,其专门为目标节点添加子节点
*
* @param c 目标节点
* @param collection 目标集合
* @param idField
* @param parentField
* @param childrenField
* @param <T>
* @throws IllegalAccessException
*/
private static <T> void addChild(@NotNull T c, @NotNull Collection<T> collection, @NotNull Field idField, @NotNull Field parentField, @NotNull Field childrenField) throws IllegalAccessException {
Object id = idField.get(c);// 获取目标节点的被依赖值
Collection<T> children = (Collection<T>) childrenField.get(c);// 获取目标节点的孩子列表
for (T cc : collection) {// 遍历目标集合
Object o = parentField.get(cc);// 获取当前节点的依赖值
if (id.equals(o)) {// 如果当前节点的被依赖值和目标节点的被依赖值相等,则说明,当前节点是目标节点的子节点
if (children == null) {// 如果目标节点的孩子集合为null,初始化目标节点的孩子集合
if (collection.getClass().isAssignableFrom(Set.class)) {// 如果目标集合是一个set集合,则初始化目标节点的孩子节点集合为set
children = new HashSet<>();
} else children = new ArrayList<>();// 否则初始化为list
}
// 将当前节点添加到目标节点的孩子节点
children.add(cc);
// 重设目标节点的孩子节点集合,这里必须重设,因为如果目标节点的孩子节点是null的话,这样是没有地址的,就会造成数据丢失,所以必须重设,如果目标节点所在类的孩子节点初始化为一个空集合,而不是null,则可以不需要这一步,因为java一切皆指针
childrenField.set(c, children);
// 递归添加孩子节点
addChild(cc, collection, idField, parentField, childrenField);
}
}
// 特别说明:大家可以看到此递归没有明显出口,其出口就是是否当前节点的依赖值和目标节点的被依赖值一样,一样就递归,不一样进不了if,自然出递归
// 此工具类自我感觉是最简单的,最实用的工具类,我看网上许多人写的,都是云的雾的,本来也想借鉴,但是实在没一个能看的感觉思路清晰,没办法,自己动手造轮子
}
/**
* 为目标节点添加孩子
*
* @param c 目标节点
* @param collection 目标集合
* @param id 被依赖字段名
* @param parent 依赖字段名
* @param children 孩子节点字段名
* @param clazz 集合元素所在类别
* @param <T>
*/
public static <T> void addChild(@NotNull T c, @NotNull Collection<T> collection, String id, String parent, String children, @NotNull Class<T> clazz) {
try {
if (collection == null || collection.isEmpty()) return;// 如果目标集合为空,直接返回一个空树
if (StringUtils.isEmpty(id)) id = "id";// 如果被依赖字段名称为空则默认为id
if (StringUtils.isEmpty(parent)) parent = "parent";// 如果依赖字段为空则默认为parent
if (StringUtils.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Field idField = null;
try {
idField = clazz.getDeclaredField(id);// 获取依赖字段
} catch (NoSuchFieldException e1) {
idField = clazz.getSuperclass().getDeclaredField(id);
}
Field parentField = null;
try {
parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
} catch (NoSuchFieldException e1) {
parentField = clazz.getSuperclass().getDeclaredField(parent);
}
Field childrenField = null;// 获取孩子字段
try {
childrenField = clazz.getDeclaredField(children);
} catch (NoSuchFieldException e1) {
childrenField = clazz.getSuperclass().getDeclaredField(children);
}
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
addChild(c, collection, idField, parentField, childrenField);
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子
*
* @param c 目标节点
* @param collection 目标集合
* @param clazz 集合元素所在类型
* @param <T>
*/
public static <T> void addChild(@NotNull T c, @NotNull Collection<T> collection, @NotNull Class<T> clazz) {
addChild(c, collection, null, null, null, clazz);
}
/**
- listToTree
- <p>方法说明<p>
- 将JSONArray数组转为树状结构
- @param arr 需要转化的数据
- @param id 数据唯一的标识键值
- @param pid 父id唯一标识键值
- @param child 子节点键值
- @return JSONArray
*/
public static JSONArray listToTree(JSONArray arr, String id, String pid, String child){
JSONArray r = new JSONArray();
JSONObject hash = new JSONObject();
//将数组转为Object的形式key为数组中的id
for(int i=0;i<arr.size();i++){
JSONObject json = (JSONObject) arr.get(i);
hash.put(json.getString(id), json);
}
//遍历结果集
for(int j=0;j<arr.size();j++){
//单条记录
JSONObject aVal = (JSONObject) arr.get(j);
//在hash中取出key为单条记录中pid的值
JSONObject hashVP = (JSONObject) hash.get(aVal.get(pid).toString());
//如果记录的pid存在则说明它有父节点将她添加到孩子节点的集合中
if(hashVP!=null){
//检查是否有child属性
if(hashVP.get(child)!=null){
JSONArray ch = (JSONArray) hashVP.get(child);
ch.add(aVal);
hashVP.put(child, ch);
}else{
JSONArray ch = new JSONArray();
ch.add(aVal);
hashVP.put(child, ch);
}
}else{
r.add(aVal);
}
}
return r;
}
/**
* 树形数据转换
* @param {*} data
* @param {*} id
* @param {*} pid
*/
public static void main(String[] args) {
List<Menu> list = new ArrayList<>();
list.add(new Menu(1, 100));
list.add(new Menu(2, 100));
list.add(new Menu(3, 100));
list.add(new Menu(4, 1));
list.add(new Menu(5, 1));
list.add(new Menu(6, 1));
list.add(new Menu(7, 2));
list.add(new Menu(8, 2));
list.add(new Menu(9, 3));
list.add(new Menu(10, 4));
list.add(new Menu(11, 7));
list.add(new Menu(12, 5));
list.add(new Menu(13, 10));
list.add(new Menu(14, 8));
list.add(new Menu(15, 11));
list.add(new Menu(16, 12));
list.add(new Menu(17, 13));
List<SysDeptEntity> deptList = new ArrayList<>();
SysDeptEntity s = new SysDeptEntity();
s.setDeptId(2L);
s.setParentId(7L);
s.setName("系统管理");
deptList.add(s);
s = new SysDeptEntity();
s.setDeptId(6L);
s.setParentId(2L);
deptList.add(s);
s = new SysDeptEntity();
s.setDeptId(8L);
s.setParentId(6L);
deptList.add(s);
s = new SysDeptEntity();
s.setDeptId(9L);
s.setParentId(8L);
deptList.add(s);
//Collection<Menu> menus = TreeUtils.toTree(list, null, null, null, Menu.class);
//System.out.println(JSON.toJSONString(menus));
//Collection<SysDeptEntity> depts = TreeUtils.toTree(deptList, "deptId", "parentId", "childrenList", SysDeptEntity.class);
//System.out.println(JSON.toJSONString(depts));
/*List<SysDeptEntity> deptList = new ArrayList<>();
SysDeptEntity s = new SysDeptEntity();
s.setDeptId(2L);
s.setParentId(7L);
s.setName("系统管理");
deptList.add(s);
s = new SysDeptEntity();
s.setDeptId(6L);
s.setParentId(2L);
s.setName("用户管理");
deptList.add(s);*/
System.out.println(JSON.toJSONString(deptList));
JSONArray deptTree = listToTree(JSONArray.parseArray(JSON.toJSONString(deptList)),"deptId","parentId","childrenList");
System.out.println(JSON.toJSONString(deptTree));
System.out.println(JSON.toJSONString(list));
JSONArray listTree = listToTree(JSONArray.parseArray(JSON.toJSONString(list)),"id","parent","children");
System.out.println(JSON.toJSONString(listTree));
}
}
class Menu {
private Integer id;
private Integer parent;
private List<Menu> children;
public Menu() {
}
public Menu(Integer id, Integer parent) {
this.id = id;
this.parent = parent;
}
public List<Menu> getChildren() {
return children;
}
public void setChildren(List<Menu> children) {
this.children = children;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParent() {
return parent;
}
public void setParent(Integer parent) {
this.parent = parent;
}
}