From d3b19c6cb0c0bd9d425c1e53ec7644f5c2a4a902 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Tue, 16 Apr 2024 21:06:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=81=E8=AE=B8=E6=9E=84=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E6=8E=92=E5=BA=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plusone/commons/util/TreeBuilder.java | 70 ++++++++++++++- .../commons/util/TreeBuilderTests.java | 85 +++++++++++-------- 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java b/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java index 1caa73a..6c781fd 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java @@ -1,6 +1,7 @@ package xyz.zhouxy.plusone.commons.util; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -8,25 +9,88 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; + +/** + * TreeBuilder + * + * @author ZhouXY + * @since 1.0 + */ public class TreeBuilder { private final Function identityGetter; private final Function> parentIdentityGetter; private final BiConsumer addChildMethod; + private final Comparator defaultComparator; public TreeBuilder(Function identityGetter, Function> parentIdentityGetter, BiConsumer addChild) { + this(identityGetter, parentIdentityGetter, addChild, null); + } + + public TreeBuilder(Function identityGetter, Function> parentIdentityGetter, + BiConsumer addChild, Comparator defaultComparator) { this.identityGetter = identityGetter; this.parentIdentityGetter = parentIdentityGetter; this.addChildMethod = addChild; + this.defaultComparator = defaultComparator; } + /** + * 将节点构建成树。使用 {@link #defaultComparator} 进行排序。如果 {@link #defaultComparator} + *

+ * 注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, + * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 + * + * @param nodes 平铺的节点列表 + */ public List buildTree(Collection nodes) { - Map identityNodeMap = nodes.stream() + Preconditions.checkNotNull(nodes); + return buildTreeInternal(nodes, this.defaultComparator); + } + + /** + * 将节点构建成树。 + *

+ * 注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, + * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 + * + * @param nodes 平铺的节点列表 + * @param comparator 用于节点的排序。 + * 若为 {@code null},则使用 {@link #defaultComparator}; + * 若 {@link #defaultComparator} 也为 {@code null},则不排序。 + */ + public List buildTree(Collection nodes, @Nullable Comparator comparator) { + Preconditions.checkNotNull(nodes); + final Comparator c = (comparator != null) ? comparator : this.defaultComparator; + return buildTreeInternal(nodes, c); + } + + /** + * 将节点构建成树。 + *

+ * 注意,该方法会直接操作 nodes 列表中的节点,并没有做深拷贝, + * 注意避免 nodes 中的元素产生变化所带来的意料之外的影响。 + * + * @param nodes 平铺的节点列表 + * @param comparator 用于节点的排序。若为 {@code null},则不排序 + */ + private List buildTreeInternal(Collection nodes, @Nullable Comparator comparator) { + final Collection allNodes; + if (comparator == null) { + allNodes = nodes; + } else { + allNodes = nodes.stream().sorted(comparator).collect(Collectors.toList()); + } + + final Map identityNodeMap = allNodes.stream() .collect(Collectors.toMap(identityGetter, Function.identity(), (n1, n2) -> n1)); - List result = nodes.stream() + final List result = allNodes.stream() .filter(node -> !this.parentIdentityGetter.apply(node).isPresent()) .collect(Collectors.toList()); - nodes.forEach(node -> parentIdentityGetter.apply(node).ifPresent(parentIdentity -> { + allNodes.forEach(node -> parentIdentityGetter.apply(node).ifPresent(parentIdentity -> { if (identityNodeMap.containsKey(parentIdentity)) { @SuppressWarnings("unchecked") TSubTree parentNode = (TSubTree) identityNodeMap.get(parentIdentity); diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java index 14b3828..7b7b842 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java @@ -17,29 +17,34 @@ class TreeBuilderTests { private static final Logger log = LoggerFactory.getLogger(TreeBuilderTests.class); private final TreeBuilder treeBuilder = new TreeBuilder<>( Menu::getMenuCode, - Menu::getParentMenuCode, - MenuList::addChild); + menu -> Optional.ofNullable(menu.parentMenuCode), + MenuList::addChild, + (a, b) -> Integer.compare(a.getOrderNum(), b.getOrderNum())); @Test void testBuildTree() { List

menus = Lists.newArrayList( - MenuItem.of("A", "首页", "/home"), - MenuList.of("B", "系统管理"), - MenuItem.of("B", "B001", "功能管理", "/sys/function-mgmt"), - MenuItem.of("B", "B002", "角色管理", "/sys/role-mgmt"), - MenuItem.of("B", "B003", "账号管理", "/sys/account-mgmt"), - MenuItem.of("B", "B004", "系统参数管理", "/sys/param-mgmt"), - MenuList.of("C", "一级菜单C"), - MenuList.of("C", "C1", "二级菜单C1"), - MenuItem.of("C1", "C1001", "三级菜单C1001", "/c/c1/c1001"), - MenuItem.of("C1", "C1002", "三级菜单C1002", "/c/c1/c1002"), - MenuItem.of("C", "C2", "二级菜单C2", "/c/c2"), - MenuItem.of("C", "C3", "二级菜单C3", "/c/c3") + MenuList.of("B", "系统管理", 3), + MenuItem.of("A", "首页", "/home", 1), + /**/MenuItem.of("B", "B002", "角色管理", "/sys/role-mgmt", 3), + /**/MenuItem.of("B", "B001", "功能管理", "/sys/function-mgmt", 4), + /**/MenuItem.of("B", "B004", "系统参数管理", "/sys/param-mgmt", 1), + /**/MenuItem.of("B", "B003", "账号管理", "/sys/account-mgmt", 2), + MenuList.of("C", "一级菜单C", 2), + /**/MenuItem.of("C", "C3", "二级菜单C3", "/c/c3", 2), + /**/MenuList.of("C", "C1", "二级菜单C1", 2), + /**//**/MenuItem.of("C1", "C1001", "三级菜单C1001", "/c/c1/c1001", 1), + /**//**/MenuItem.of("C1", "C1002", "三级菜单C1002", "/c/c1/c1002", 2), + /**/MenuItem.of("C", "C2", "二级菜单C2", "/c/c2", 1)); + + List menuTreeSortedByOrderNum = treeBuilder.buildTree(menus); + log.info("menuTreeSortedByOrderNum: {}", new Gson().toJson(menuTreeSortedByOrderNum)); + + List menuTreeSortedByMenuCode = treeBuilder.buildTree( + menus, + (a, b) -> a.getMenuCode().compareTo(b.getMenuCode()) ); - - List menuTree = treeBuilder.buildTree(menus); - log.info("menuTree: {}", new Gson().toJson(menuTree)); - + log.info("menuTreeSortedByMenuCode: {}", new Gson().toJson(menuTreeSortedByMenuCode)); } } @@ -48,24 +53,30 @@ abstract class Menu { protected final String parentMenuCode; protected final String menuCode; protected final String title; + protected final int orderNum; - public Menu(String parentMenuCode, String menuCode, String title) { + public Menu(String parentMenuCode, String menuCode, String title, int orderNum) { this.parentMenuCode = parentMenuCode; this.menuCode = menuCode; this.title = title; + this.orderNum = orderNum; } public String getMenuCode() { return menuCode; } - public Optional getParentMenuCode() { - return Optional.ofNullable(parentMenuCode); + public String getParentMenuCode() { + return parentMenuCode; } public String getTitle() { return title; } + + public int getOrderNum() { + return orderNum; + } } @ToString(callSuper = true) @@ -73,17 +84,17 @@ class MenuItem extends Menu { private final String url; - private MenuItem(String parentMenuCode, String menuCode, String title, String url) { - super(parentMenuCode, menuCode, title); + private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) { + super(parentMenuCode, menuCode, title, orderNum); this.url = url; } - static MenuItem of(String parentMenuCode, String menuCode, String title, String url) { - return new MenuItem(parentMenuCode, menuCode, title, url); + static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) { + return new MenuItem(parentMenuCode, menuCode, title, url, orderNum); } - static MenuItem of(String menuCode, String title, String url) { - return new MenuItem(null, menuCode, title, url); + static MenuItem of(String menuCode, String title, String url, int orderNum) { + return new MenuItem(null, menuCode, title, url, orderNum); } public String getUrl() { @@ -96,24 +107,24 @@ class MenuList extends Menu { private List children; - private MenuList(String parentMenuCode, String menuCode, String title) { - super(parentMenuCode, menuCode, title); + private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) { + super(parentMenuCode, menuCode, title, orderNum); } - static MenuList of(String parentMenuCode, String menuCode, String title) { - return new MenuList(parentMenuCode, menuCode, title); + static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) { + return new MenuList(parentMenuCode, menuCode, title, orderNum); } - static MenuList of(String menuCode, String title) { - return new MenuList(null, menuCode, title); + static MenuList of(String menuCode, String title, int orderNum) { + return new MenuList(null, menuCode, title, orderNum); } - static MenuList of(String menuCode, String title, Iterable children) { - return of(null, menuCode, title, children); + static MenuList of(String menuCode, String title, Iterable children, int orderNum) { + return of(null, menuCode, title, children, orderNum); } - static MenuList of(String parentMenuCode, String menuCode, String title, Iterable children) { - MenuList instance = of(parentMenuCode, menuCode, title); + static MenuList of(String parentMenuCode, String menuCode, String title, Iterable children, int orderNum) { + MenuList instance = of(parentMenuCode, menuCode, title, orderNum); for (Menu child : children) { instance.addChild(child); }