From f42d18baa264875f8065050c1bf3473bccf27451 Mon Sep 17 00:00:00 2001 From: Geoffroy Jamgotchian Date: Fri, 8 Nov 2024 16:07:41 +0100 Subject: [PATCH] UndirectedGraph notification deactivation (#3202) Signed-off-by: Geoffroy Jamgotchian --- .../powsybl/math/graph/UndirectedGraph.java | 95 +++++++++++++-- .../math/graph/UndirectedGraphImpl.java | 115 ++++++++++++++---- .../math/graph/UndirectedGraphImplTest.java | 32 +++++ 3 files changed, 208 insertions(+), 34 deletions(-) diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java index faf5534b916..077d6cf3f1d 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraph.java @@ -36,20 +36,31 @@ public interface UndirectedGraph { */ int addVertex(); + /** + * Create a new vertex and notify the {@link UndirectedGraphListener}s. + * + * @param notify notify the {@link UndirectedGraphListener}s if true. + * @return the index of the new vertex. + */ + int addVertex(boolean notify); + /** * If the specified vertex does not exist or is null, create it and notify the {@link UndirectedGraphListener} */ - default void addVertexIfNotPresent(int v) { - throw new UnsupportedOperationException(); - } + void addVertexIfNotPresent(int v); + + /** + * If the specified vertex does not exist or is null, create it and notify the {@link UndirectedGraphListener} + * + * @param notify notify the {@link UndirectedGraphListener}s if true. + */ + void addVertexIfNotPresent(int v, boolean notify); /** * Check if a specified vertex exists. * This method throws a {@link com.powsybl.commons.PowsyblException} if the vertex index is invalid (negative). */ - default boolean vertexExists(int v) { - throw new UnsupportedOperationException(); - } + boolean vertexExists(int v); /** * Remove the specified vertex and notify the {@link UndirectedGraphListener}s. @@ -60,6 +71,16 @@ default boolean vertexExists(int v) { */ V removeVertex(int v); + /** + * Remove the specified vertex and notify the {@link UndirectedGraphListener}s. + * This method throws a {@link com.powsybl.commons.PowsyblException} if the vertex doesn't exist or if an edge is connected to this vertex. + * + * @param v the vertex index to remove. + * @param notify notify the {@link UndirectedGraphListener}s if true. + * @return the value attached to the vertex. + */ + V removeVertex(int v, boolean notify); + /** * Return the number of non-null vertices. * As the contiguity of vertices is not mandatory, the number of vertices can be less than the highest vertex index. @@ -79,6 +100,18 @@ default boolean vertexExists(int v) { */ int addEdge(int v1, int v2, E obj); + /** + * Create an edge between the two specified vertices and notify the {@link UndirectedGraphListener}s. + * This method throws a {@link com.powsybl.commons.PowsyblException} if one of the vertices doesn't exist. + * + * @param v1 the first end of the edge. + * @param v2 the second end of the edge. + * @param obj the value attached to the edge. + * @param notify notify the {@link UndirectedGraphListener}s if true. + * @return the index of the new edge. + */ + int addEdge(int v1, int v2, E obj, boolean notify); + /** * Remove the specified edge and notify the {@link UndirectedGraphListener}s. * This method thows a {@link com.powsybl.commons.PowsyblException} if the edge doesn't exist. @@ -88,11 +121,28 @@ default boolean vertexExists(int v) { */ E removeEdge(int e); + /** + * Remove the specified edge and notify the {@link UndirectedGraphListener}s. + * This method thows a {@link com.powsybl.commons.PowsyblException} if the edge doesn't exist. + * + * @param e the edge index to remove. + * @param notify notify the {@link UndirectedGraphListener}s if true. + * @return the value attached to the edge. + */ + E removeEdge(int e, boolean notify); + /** * Remove all the edges and notify the {@link UndirectedGraphListener}s. */ void removeAllEdges(); + /** + * Remove all the edges and notify the {@link UndirectedGraphListener}s. + * + * @param notify notify the {@link UndirectedGraphListener}s if true. + */ + void removeAllEdges(boolean notify); + /** * Return the number of edges. * @@ -147,7 +197,7 @@ default boolean vertexExists(int v) { V getVertexObject(int v); /** - * Set the value attached to the specified vertex. + * Set the value attached to the specified vertex and notify the {@link UndirectedGraphListener}s. * This method throws a {@link com.powsybl.commons.PowsyblException} if the vertex doesn't exist. * * @param v the vertex index. @@ -155,6 +205,16 @@ default boolean vertexExists(int v) { */ void setVertexObject(int v, V obj); + /** + * Set the value attached to the specified vertex and notify the {@link UndirectedGraphListener}s. + * This method throws a {@link com.powsybl.commons.PowsyblException} if the vertex doesn't exist. + * + * @param v the vertex index. + * @param obj the value to attach to the vertex. + * @param notify notify the {@link UndirectedGraphListener}s if true. + */ + void setVertexObject(int v, V obj, boolean notify); + /** * Return the index of the first vertex that the specified edge is connected to. * This method throws a {@link com.powsybl.commons.PowsyblException} if the edge doesn't exist. @@ -210,11 +270,19 @@ default boolean vertexExists(int v) { int getEdgeVertex2(int e); /** - * Remove all the vertices of this graph. + * Remove all the vertices of this graph and notify the {@link UndirectedGraphListener}s. * This method throws a {@link com.powsybl.commons.PowsyblException} if edges exist. */ void removeAllVertices(); + /** + * Remove all the vertices of this graph and notify the {@link UndirectedGraphListener}s. + * This method throws a {@link com.powsybl.commons.PowsyblException} if edges exist. + * + * @param notify notify the {@link UndirectedGraphListener}s if true. + */ + void removeAllVertices(boolean notify); + /** * Return an {@link Iterable} to iterate over the values attached to the edges. * @@ -330,7 +398,16 @@ default boolean vertexExists(int v) { void print(PrintStream out, Function vertexToString, Function edgeToString); /** - * Remove from the vertices which are not connected to any edge, and which have no associated object. + * Remove from the vertices which are not connected to any edge, and which have no associated object + * and notify the {@link UndirectedGraphListener}s. */ void removeIsolatedVertices(); + + /** + * Remove from the vertices which are not connected to any edge, and which have no associated object + * and notify the {@link UndirectedGraphListener}s. + * + * @param notify notify the {@link UndirectedGraphListener}s if true. + */ + void removeIsolatedVertices(boolean notify); } diff --git a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java index e998b1c78e0..9d504362ff5 100644 --- a/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java +++ b/math/src/main/java/com/powsybl/math/graph/UndirectedGraphImpl.java @@ -129,8 +129,12 @@ private void checkEdge(int e) { } } - @Override public int addVertex() { + return addVertex(true); + } + + @Override + public int addVertex(boolean notify) { int v; if (availableVertices.isEmpty()) { v = vertices.size(); @@ -141,12 +145,19 @@ public int addVertex() { vertices.set(v, new Vertex<>()); } invalidateAdjacencyList(); - notifyVertexAdded(v); + if (notify) { + notifyVertexAdded(v); + } return v; } @Override public void addVertexIfNotPresent(int v) { + addVertexIfNotPresent(v, true); + } + + @Override + public void addVertexIfNotPresent(int v, boolean notify) { if (v < 0) { throw new PowsyblException("Invalid vertex " + v); } @@ -158,7 +169,9 @@ public void addVertexIfNotPresent(int v) { vertices.set(v, new Vertex<>()); availableVertices.remove(v); invalidateAdjacencyList(); - notifyVertexAdded(v); + if (notify) { + notifyVertexAdded(v); + } } } else { for (int i = vertices.size(); i < v; i++) { @@ -167,7 +180,9 @@ public void addVertexIfNotPresent(int v) { } vertices.add(new Vertex<>()); invalidateAdjacencyList(); - notifyVertexAdded(v); + if (notify) { + notifyVertexAdded(v); + } } } @@ -179,7 +194,7 @@ public boolean vertexExists(int v) { return v < vertices.size() && vertices.get(v) != null; } - private V removeVertexInternal(int v) { + private V removeVertexInternal(int v, boolean notify) { V obj = vertices.get(v).getObject(); if (v == vertices.size() - 1) { vertices.remove(v); @@ -188,19 +203,26 @@ private V removeVertexInternal(int v) { vertices.set(v, null); availableVertices.add(v); } - notifyVertexRemoved(v, obj); + if (notify) { + notifyVertexRemoved(v, obj); + } return obj; } @Override public V removeVertex(int v) { + return removeVertex(v, true); + } + + @Override + public V removeVertex(int v, boolean notify) { checkVertex(v); for (Edge e : edges) { if (e != null && (e.getV1() == v || e.getV2() == v)) { throw new PowsyblException("An edge is connected to vertex " + v); } } - V obj = removeVertexInternal(v); + V obj = removeVertexInternal(v, notify); invalidateAdjacencyList(); return obj; } @@ -222,17 +244,29 @@ public int getVertexCount() { @Override public void removeAllVertices() { + removeAllVertices(true); + } + + @Override + public void removeAllVertices(boolean notify) { if (!edges.isEmpty()) { throw new PowsyblException("Cannot remove all vertices because there is still some edges in the graph"); } vertices.clear(); availableVertices.clear(); invalidateAdjacencyList(); - notifyAllVerticesRemoved(); + if (notify) { + notifyAllVerticesRemoved(); + } } @Override public int addEdge(int v1, int v2, E obj) { + return addEdge(v1, v2, obj, true); + } + + @Override + public int addEdge(int v1, int v2, E obj, boolean notify) { checkVertex(v1); checkVertex(v2); int e; @@ -245,39 +279,59 @@ public int addEdge(int v1, int v2, E obj) { edges.set(e, edge); } invalidateAdjacencyList(); - notifyEdgeAdded(e, obj); + if (notify) { + notifyEdgeAdded(e, obj); + } return e; } - private E removeEdgeInternal(int e) { + private E removeEdgeInternal(int e, boolean notify) { E obj = edges.get(e).getObject(); - notifyEdgeBeforeRemoval(e, obj); + if (notify) { + notifyEdgeBeforeRemoval(e, obj); + } if (e == edges.size() - 1) { edges.remove(e); } else { edges.set(e, null); removedEdges.add(e); } - notifyEdgeRemoved(e, obj); + if (notify) { + notifyEdgeRemoved(e, obj); + } return obj; } @Override public E removeEdge(int e) { + return removeEdge(e, true); + } + + @Override + public E removeEdge(int e, boolean notify) { checkEdge(e); - E obj = removeEdgeInternal(e); + E obj = removeEdgeInternal(e, notify); invalidateAdjacencyList(); return obj; } @Override public void removeAllEdges() { + removeAllEdges(true); + } + + @Override + public void removeAllEdges(boolean notify) { Collection allEdges = edges.stream().filter(Objects::nonNull).map(Edge::getObject).collect(Collectors.toList()); - notifyAllEdgesBeforeRemoval(allEdges); + if (notify) { + notifyAllEdgesBeforeRemoval(allEdges); + } edges.clear(); removedEdges.clear(); invalidateAdjacencyList(); - notifyAllEdgesRemoved(allEdges); + if (notify) { + notifyAllEdgesRemoved(allEdges); + } } @Override @@ -332,9 +386,16 @@ public V getVertexObject(int v) { @Override public void setVertexObject(int v, V obj) { + setVertexObject(v, obj, true); + } + + @Override + public void setVertexObject(int v, V obj, boolean notify) { checkVertex(v); vertices.get(v).setObject(obj); - notifyVertexObjectSet(v, obj); + if (notify) { + notifyVertexObjectSet(v, obj); + } } @Override @@ -718,25 +779,24 @@ public void print(PrintStream out, Function vertexToString, Function< } } - public void removeIsolatedVertices(int v, TIntArrayList[] adjacencyList) { - + private void removeIsolatedVertices(int v, TIntArrayList[] adjacencyList, boolean notify) { Vertex vertex = vertices.get(v); if (vertex != null && vertex.getObject() == null) { TIntArrayList adjacentEdges = adjacencyList[v]; if (adjacentEdges.isEmpty()) { - removeVertexInternal(v); + removeVertexInternal(v, notify); adjacencyList[v] = null; if (!adjacentEdges.isEmpty()) { int e = adjacentEdges.getQuick(0); - removeDanglingEdgeAndPropagate(e, v, adjacencyList); + removeDanglingEdgeAndPropagate(e, v, adjacencyList, notify); } } } } - private void removeDanglingEdgeAndPropagate(int edgeToRemove, int vFrom, TIntArrayList[] adjacencyList) { + private void removeDanglingEdgeAndPropagate(int edgeToRemove, int vFrom, TIntArrayList[] adjacencyList, boolean notify) { Edge edge = edges.get(edgeToRemove); int v1 = edge.getV1(); int v2 = edge.getV2(); @@ -745,7 +805,7 @@ private void removeDanglingEdgeAndPropagate(int edgeToRemove, int vFrom, TIntArr // updating adjacency list of vFrom & vTo is not done here, as: // - vFrom adjacency list has been set to null when vertex vFrom has been removed // - vTo adjacency list is updated hereafter - removeEdgeInternal(edgeToRemove); + removeEdgeInternal(edgeToRemove, notify); Vertex vertex = vertices.get(vTo); TIntArrayList adjacentEdges = adjacencyList[vTo]; @@ -758,7 +818,7 @@ private void removeDanglingEdgeAndPropagate(int edgeToRemove, int vFrom, TIntArr // propagate: we know that one of the neighbours (vFrom) of this vertex has been removed, hence: // - if only one adjacent edge, this is a newly isolated vertex // - if only two adjacent edges, this is a newly dangling vertex - removeVertexInternal(vTo); + removeVertexInternal(vTo, notify); adjacencyList[vTo] = null; // find the other edge to remove if dangling vertex @@ -766,16 +826,21 @@ private void removeDanglingEdgeAndPropagate(int edgeToRemove, int vFrom, TIntArr int otherEdgeToRemove = adjacentEdges.getQuick(0) == edgeToRemove ? adjacentEdges.getQuick(1) : adjacentEdges.getQuick(0); - removeDanglingEdgeAndPropagate(otherEdgeToRemove, vTo, adjacencyList); + removeDanglingEdgeAndPropagate(otherEdgeToRemove, vTo, adjacencyList, notify); } } @Override public void removeIsolatedVertices() { + removeIsolatedVertices(true); + } + + @Override + public void removeIsolatedVertices(boolean notify) { TIntArrayList[] adjacencyList = getAdjacencyList(); for (int v = 0; v < vertices.size(); v++) { - removeIsolatedVertices(v, adjacencyList); + removeIsolatedVertices(v, adjacencyList, notify); } } } diff --git a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java index 43af43db8a5..998534feb17 100644 --- a/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java +++ b/math/src/test/java/com/powsybl/math/graph/UndirectedGraphImplTest.java @@ -172,6 +172,14 @@ void testAddEdge() { assertEquals(1, graph.getEdgeVertex2(e)); } + @Test + void testVertexNotFoundWhenAddingEdge() { + graph.addVertex(); + graph.addVertex(); + var e = assertThrows(PowsyblException.class, () -> graph.addEdge(0, 2, null)); + assertEquals("Vertex 2 not found", e.getMessage()); + } + @Test void testRemoveEdge() { graph.addVertex(); @@ -227,6 +235,12 @@ void testGetEdges() { assertArrayEquals(new int[]{0, 1}, graph.getEdges()); } + @Test + void testEdgeNotFound() { + var e = assertThrows(PowsyblException.class, () -> graph.getEdgeObject(0)); + assertEquals("Edge 0 not found", e.getMessage()); + } + @Test void testGetEdgesFromVertex() { graph.addVertex(); @@ -398,6 +412,24 @@ void testAddListener() { Mockito.verify(listener2, Mockito.atLeastOnce()).vertexAdded(Mockito.anyInt()); } + @Test + void testAddListenerButDisableNotification() { + UndirectedGraphListener listener = Mockito.mock(UndirectedGraphListener.class); + graph.addListener(listener); + Mockito.verify(listener, Mockito.never()).vertexAdded(Mockito.anyInt()); + int v1 = graph.addVertex(false); + int v2 = graph.addVertex(false); + Mockito.verify(listener, Mockito.never()).vertexAdded(Mockito.anyInt()); + int e = graph.addEdge(v1, v2, "test", false); + Mockito.verify(listener, Mockito.never()).edgeAdded(Mockito.anyInt(), Mockito.any()); + graph.removeEdge(e, false); + Mockito.verify(listener, Mockito.never()).edgeRemoved(Mockito.anyInt(), Mockito.any()); + graph.removeVertex(v1, false); + Mockito.verify(listener, Mockito.never()).vertexRemoved(Mockito.anyInt(), Mockito.any()); + graph.removeAllVertices(false); + Mockito.verify(listener, Mockito.never()).allVerticesRemoved(); + } + @Test void testRemoveListener() { UndirectedGraphListener listener1 = Mockito.mock(UndirectedGraphListener.class);