diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java index 943b783cb..afdc9c543 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java @@ -2,8 +2,7 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.multi.MultiValueMap; -import cn.hutool.core.map.multi.SetValueMap; +import cn.hutool.core.map.multi.Graph; import cn.hutool.core.reflect.ClassUtil; import cn.hutool.core.reflect.MethodUtil; import cn.hutool.core.text.CharSequenceUtil; @@ -479,8 +478,8 @@ public class ResolvedAnnotationMapping implements AnnotationMapping private void resolveAliasAttributes() { final Map attributeIndexes = new HashMap<>(attributes.length); + final Graph methodGraph = new Graph<>(); // 解析被作为别名的关联属性,根据节点关系构建邻接表 - final MultiValueMap aliasedMethods = new SetValueMap<>(); for (int i = 0; i < attributes.length; i++) { // 获取属性上的@Alias注解 final Method attribute = attributes[i]; @@ -492,15 +491,14 @@ public class ResolvedAnnotationMapping implements AnnotationMapping // 获取别名属性 final Method aliasAttribute = getAliasAttribute(attribute, attributeAnnotation); Objects.requireNonNull(aliasAttribute); - aliasedMethods.putValue(aliasAttribute, attribute); - aliasedMethods.putValue(attribute, aliasAttribute); + methodGraph.putEdge(aliasAttribute, attribute); } // 按广度优先遍历邻接表,将属于同一张图上的节点分为一组,并为其建立AliasSet final Set accessed = new HashSet<>(attributes.length); final Set group = new LinkedHashSet<>(); final Deque deque = new LinkedList<>(); - for (final Method target : aliasedMethods.keySet()) { + for (final Method target : methodGraph.keySet()) { group.clear(); deque.addLast(target); while (!deque.isEmpty()) { @@ -512,7 +510,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping accessed.add(curr); // 将其添加到关系组 group.add(curr); - Collection aliases = aliasedMethods.get(curr); + final Collection aliases = methodGraph.getAdjacentPoints(curr); if (CollUtil.isNotEmpty(aliases)) { deque.addAll(aliases); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Graph.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Graph.java new file mode 100644 index 000000000..681b6b6f0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Graph.java @@ -0,0 +1,141 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.CollUtil; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +/** + * 支持处理无向图结构的{@link Map},本质上是基于{@link SetValueMap}实现的邻接表 + * + * @param 节点类型 + * @author huangchengxing + * @since 6.0.0 + */ +public class Graph extends SetValueMap { + + /** + * 添加边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void putEdge(final T target1, final T target2) { + this.putValue(target1, target2); + this.putValue(target2, target1); + } + + /** + * 是否存在边 + * + * @param target1 节点 + * @param target2 节点 + * @return 是否 + */ + public boolean containsEdge(final T target1, final T target2) { + return this.getValues(target1).contains(target2) + && this.getValues(target2).contains(target1); + } + + /** + * 移除边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void removeEdge(final T target1, final T target2) { + this.removeValue(target1, target2); + this.removeValue(target2, target1); + } + + /** + * 移除节点,并删除该节点与其他节点之间连成的边 + * + * @param target 目标对象 + */ + public void removePoint(final T target) { + final Collection associatedPoints = this.remove(target); + if (CollUtil.isNotEmpty(associatedPoints)) { + associatedPoints.forEach(p -> this.removeValue(p, target)); + } + } + + /** + * 两节点是否存在直接或间接的关联 + * + * @param target1 节点 + * @param target2 节点 + * @return 两节点是否存在关联 + */ + public boolean containsAssociation(final T target1, final T target2) { + if (!this.containsKey(target1) || !this.containsKey(target2)) { + return false; + } + final AtomicBoolean flag = new AtomicBoolean(false); + visitAssociatedPoints(target1, t -> { + if (Objects.equals(t, target2)) { + flag.set(true); + return true; + } + return false; + }); + return flag.get(); + } + + /** + * 按广度优先,获得节点的所有直接或间接关联的节点,节点默认按添加顺序排序 + * + * @param target 节点 + * @param includeTarget 是否包含查询节点 + * @return 节点的所有关联节点 + */ + public Collection getAssociatedPoints(final T target, final boolean includeTarget) { + final Set points = visitAssociatedPoints(target, t -> false); + if (!includeTarget) { + points.remove(target); + } + return points; + } + + /** + * 获取节点的邻接节点 + * + * @param target 节点 + * @return 邻接节点 + */ + public Collection getAdjacentPoints(final T target) { + return this.getValues(target); + } + + /** + * 按广度优先,访问节点的所有关联节点 + */ + private Set visitAssociatedPoints(final T key, final Predicate breaker) { + if (!this.containsKey(key)) { + return Collections.emptySet(); + } + final Set accessed = new HashSet<>(); + final Deque deque = new LinkedList<>(); + deque.add(key); + while (!deque.isEmpty()) { + // 访问节点 + final T t = deque.removeFirst(); + if (accessed.contains(t)) { + continue; + } + accessed.add(t); + // 若符合条件则中断循环 + if (breaker.test(t)) { + break; + } + // 获取邻接节点 + final Collection neighbours = this.getValues(t); + if (!neighbours.isEmpty()) { + deque.addAll(neighbours); + } + } + return accessed; + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/GraphTest.java b/hutool-core/src/test/java/cn/hutool/core/map/GraphTest.java new file mode 100644 index 000000000..ae031c59f --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/GraphTest.java @@ -0,0 +1,155 @@ +package cn.hutool.core.map; + +import cn.hutool.core.map.multi.Graph; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * test for {@link Graph} + */ +public class GraphTest { + + @Test + public void testPutEdge() { + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 0); + + Assert.assertEquals(asSet(1, 2), graph.getValues(0)); + Assert.assertEquals(asSet(0, 2), graph.getValues(1)); + Assert.assertEquals(asSet(0, 1), graph.getValues(2)); + } + + @Test + public void testContainsEdge() { + // 0 -- 1 + // | | + // 3 -- 2 + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 3); + graph.putEdge(3, 0); + + Assert.assertTrue(graph.containsEdge(0, 1)); + Assert.assertTrue(graph.containsEdge(1, 0)); + + Assert.assertTrue(graph.containsEdge(1, 2)); + Assert.assertTrue(graph.containsEdge(2, 1)); + + Assert.assertTrue(graph.containsEdge(2, 3)); + Assert.assertTrue(graph.containsEdge(3, 2)); + + Assert.assertTrue(graph.containsEdge(3, 0)); + Assert.assertTrue(graph.containsEdge(0, 3)); + + Assert.assertFalse(graph.containsEdge(1, 3)); + } + + @Test + public void removeEdge() { + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + Assert.assertTrue(graph.containsEdge(0, 1)); + + graph.removeEdge(0, 1); + Assert.assertFalse(graph.containsEdge(0, 1)); + } + + @Test + public void testContainsAssociation() { + // 0 -- 1 + // | | + // 3 -- 2 + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 3); + graph.putEdge(3, 0); + + Assert.assertTrue(graph.containsAssociation(0, 2)); + Assert.assertTrue(graph.containsAssociation(2, 0)); + + Assert.assertTrue(graph.containsAssociation(1, 3)); + Assert.assertTrue(graph.containsAssociation(3, 1)); + + Assert.assertFalse(graph.containsAssociation(-1, 1)); + Assert.assertFalse(graph.containsAssociation(1, -1)); + } + + @Test + public void testGetAssociationPoints() { + // 0 -- 1 + // | | + // 3 -- 2 + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 3); + graph.putEdge(3, 0); + + Assert.assertEquals(asSet(0, 1, 2, 3), graph.getAssociatedPoints(0, true)); + Assert.assertEquals(asSet(1, 2, 3), graph.getAssociatedPoints(0, false)); + + Assert.assertEquals(asSet(1, 2, 3, 0), graph.getAssociatedPoints(1, true)); + Assert.assertEquals(asSet(2, 3, 0), graph.getAssociatedPoints(1, false)); + + Assert.assertEquals(asSet(2, 3, 0, 1), graph.getAssociatedPoints(2, true)); + Assert.assertEquals(asSet(3, 0, 1), graph.getAssociatedPoints(2, false)); + + Assert.assertEquals(asSet(3, 0, 1, 2), graph.getAssociatedPoints(3, true)); + Assert.assertEquals(asSet(0, 1, 2), graph.getAssociatedPoints(3, false)); + + Assert.assertTrue(graph.getAssociatedPoints(-1, false).isEmpty()); + } + + @Test + public void testGetAdjacentPoints() { + // 0 -- 1 + // | | + // 3 -- 2 + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 3); + graph.putEdge(3, 0); + + Assert.assertEquals(asSet(1, 3), graph.getAdjacentPoints(0)); + Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(1)); + Assert.assertEquals(asSet(1, 3), graph.getAdjacentPoints(2)); + Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(3)); + } + + @Test + public void testRemovePoint() { + // 0 -- 1 + // | | + // 3 -- 2 + Graph graph = new Graph<>(); + graph.putEdge(0, 1); + graph.putEdge(1, 2); + graph.putEdge(2, 3); + graph.putEdge(3, 0); + + // 0 + // | + // 3 -- 2 + graph.removePoint(1); + + Assert.assertEquals(asSet(3), graph.getAdjacentPoints(0)); + Assert.assertTrue(graph.getAdjacentPoints(1).isEmpty()); + Assert.assertEquals(asSet(3), graph.getAdjacentPoints(2)); + Assert.assertEquals(asSet(2, 0), graph.getAdjacentPoints(3)); + } + + + private static Set asSet(T... ts) { + return new LinkedHashSet<>(Arrays.asList(ts)); + } + +}