diff --git a/src/main/java/org/gephi/graph/api/Graph.java b/src/main/java/org/gephi/graph/api/Graph.java index 5d64337c..ad56cc8d 100644 --- a/src/main/java/org/gephi/graph/api/Graph.java +++ b/src/main/java/org/gephi/graph/api/Graph.java @@ -89,6 +89,24 @@ public interface Graph { */ public boolean removeAllNodes(Collection nodes); + /** + * Retains only nodes in this graph that are contained in the specified + * collection. + * + * @param nodes the node collection + * @return true if at least one node has been removed, false otherwise + */ + public boolean retainNodes(Collection nodes); + + /** + * Retains only edges in this graph that are contained in the specified + * collection. + * + * @param edges the edge collection + * @return true if at least one edge has been removed, false otherwise + */ + public boolean retainEdges(Collection edges); + /** * Returns true if node is contained in this graph. * diff --git a/src/main/java/org/gephi/graph/api/Subgraph.java b/src/main/java/org/gephi/graph/api/Subgraph.java index 00e88a8b..8d8c4cd6 100644 --- a/src/main/java/org/gephi/graph/api/Subgraph.java +++ b/src/main/java/org/gephi/graph/api/Subgraph.java @@ -110,6 +110,17 @@ public interface Subgraph extends Graph { @Override public boolean removeAllNodes(Collection nodes); + /** + * Retains only nodes in this subgraph that are contained in the specified + * collection. + *

+ * The nodes should be part of the root graph. + * + * @param nodes the node collection + * @return true if at least one node has been removed, false otherwise + */ + public boolean retainNodes(Collection nodes); + /** * Removes an edge from this subgraph. *

@@ -132,6 +143,17 @@ public interface Subgraph extends Graph { @Override public boolean removeAllEdges(Collection edges); + /** + * Retains only edges in this subgraph that are contained in the specified + * collection. + *

+ * The edges should be part of the root graph. + * + * @param edges the edge collection + * @return true if at least one edge has been removed, false otherwise + */ + public boolean retainEdges(Collection edges); + /** * Fills the subgraph so all elements in the graph are in the subgraph. */ diff --git a/src/main/java/org/gephi/graph/impl/EdgeStore.java b/src/main/java/org/gephi/graph/impl/EdgeStore.java index 0120304f..3707b6fc 100644 --- a/src/main/java/org/gephi/graph/impl/EdgeStore.java +++ b/src/main/java/org/gephi/graph/impl/EdgeStore.java @@ -1021,8 +1021,9 @@ public boolean retainAll(Collection c) { } } return changed; - } else { + } else if (size > 0) { clear(); + return true; } return false; } diff --git a/src/main/java/org/gephi/graph/impl/GraphStore.java b/src/main/java/org/gephi/graph/impl/GraphStore.java index 09cb3c07..77386112 100644 --- a/src/main/java/org/gephi/graph/impl/GraphStore.java +++ b/src/main/java/org/gephi/graph/impl/GraphStore.java @@ -16,6 +16,7 @@ package org.gephi.graph.impl; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -297,6 +298,16 @@ public boolean removeAllNodes(Collection nodes) { } } + @Override + public boolean retainNodes(Collection nodes) { + autoWriteLock(); + try { + return nodeStore.retainAll(nodes); + } finally { + autoWriteUnlock(); + } + } + @Override public boolean removeAllEdges(Collection edges) { autoWriteLock(); @@ -307,6 +318,16 @@ public boolean removeAllEdges(Collection edges) { } } + @Override + public boolean retainEdges(Collection edges) { + autoWriteLock(); + try { + return edgeStore.retainAll(edges); + } finally { + autoWriteUnlock(); + } + } + public NodeStore getNodeStore() { return nodeStore; } diff --git a/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java index d3bfdc94..f949c7f0 100644 --- a/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java +++ b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java @@ -264,6 +264,26 @@ public boolean removeAllNodes(Collection nodes) { } } + @Override + public boolean retainNodes(Collection nodes) { + graphStore.autoWriteLock(); + try { + return view.retainNodes(nodes); + } finally { + graphStore.autoWriteUnlock(); + } + } + + @Override + public boolean retainEdges(Collection edges) { + graphStore.autoWriteLock(); + try { + return view.retainEdges(edges); + } finally { + graphStore.autoWriteUnlock(); + } + } + @Override public boolean contains(Node node) { checkValidNodeObject(node); diff --git a/src/main/java/org/gephi/graph/impl/GraphViewImpl.java b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java index 083c7d66..42b8f592 100644 --- a/src/main/java/org/gephi/graph/impl/GraphViewImpl.java +++ b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java @@ -17,6 +17,9 @@ import cern.colt.bitvector.BitVector; import cern.colt.bitvector.QuickBitVector; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -264,6 +267,61 @@ public boolean removeNodeAll(final Collection nodes) { return false; } + public boolean retainNodes(final Collection c) { + if (nodeView) { + if (!c.isEmpty()) { + IntOpenHashSet set = new IntOpenHashSet(c.size()); + for (Node o : c) { + checkValidNodeObject(o); + set.add(o.getStoreId()); + } + + boolean changed = false; + int nodeSize = nodeBitVector.size(); + for (int i = 0; i < nodeSize; i++) { + boolean t = nodeBitVector.get(i); + if (t && !set.contains(i)) { + if (removeNode(getNode(i))) { + changed = true; + } + } + } + return changed; + } else if (nodeCount != 0) { + clear(); + return true; + } + } + return false; + } + + public boolean retainEdges(final Collection c) { + if (edgeView) { + if (!c.isEmpty()) { + IntOpenHashSet set = new IntOpenHashSet(c.size()); + for (Edge o : c) { + checkValidEdgeObject(o); + set.add(o.getStoreId()); + } + + boolean changed = false; + int edgeSize = edgeBitVector.size(); + for (int i = 0; i < edgeSize; i++) { + boolean t = edgeBitVector.get(i); + if (t && !set.contains(i)) { + removeEdge(getEdge(i)); + changed = true; + } + } + return changed; + } else if (edgeCount != 0) { + clearEdges(); + return true; + } + } + return false; + } + public boolean removeEdge(final Edge edge) { checkEdgeView(); diff --git a/src/main/java/org/gephi/graph/impl/NodeStore.java b/src/main/java/org/gephi/graph/impl/NodeStore.java index 841eb1a7..9c2c8add 100644 --- a/src/main/java/org/gephi/graph/impl/NodeStore.java +++ b/src/main/java/org/gephi/graph/impl/NodeStore.java @@ -443,8 +443,9 @@ public boolean retainAll(final Collection c) { } } return changed; - } else { + } else if (size > 0) { clear(); + return true; } return false; } diff --git a/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java index 189d5885..13ea499a 100644 --- a/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java +++ b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java @@ -87,6 +87,16 @@ public boolean removeAllNodes(Collection nodes) { return store.removeAllNodes(nodes); } + @Override + public boolean retainNodes(Collection nodes) { + return store.retainNodes(nodes); + } + + @Override + public boolean retainEdges(Collection edges) { + return store.retainEdges(edges); + } + @Override public boolean contains(Node node) { return store.contains(node); diff --git a/src/test/java/org/gephi/graph/impl/BasicGraphStore.java b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java index 84818c0a..ddd0fc68 100644 --- a/src/test/java/org/gephi/graph/impl/BasicGraphStore.java +++ b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java @@ -172,6 +172,28 @@ public boolean removeAllNodes(Collection nodes) { return true; } + @Override + public boolean retainNodes(Collection nodes) { + Set nodeSet = new HashSet<>(nodes); + for (Node n : nodes) { + if (!nodeSet.contains(n)) { + removeNode(n); + } + } + return true; + } + + @Override + public boolean retainEdges(Collection edges) { + Set edgeSet = new HashSet<>(edges); + for (Edge e : edges) { + if (!edgeSet.contains(e)) { + removeEdge(e); + } + } + return true; + } + @Override public boolean contains(Node node) { return nodeStore.contains(node); diff --git a/src/test/java/org/gephi/graph/impl/GraphGenerator.java b/src/test/java/org/gephi/graph/impl/GraphGenerator.java index eb6cd4e5..e81601c1 100644 --- a/src/test/java/org/gephi/graph/impl/GraphGenerator.java +++ b/src/test/java/org/gephi/graph/impl/GraphGenerator.java @@ -477,12 +477,20 @@ public static GraphStore generateTinyGraphStoreWithMutualEdge() { } public static GraphStore generateSmallGraphStore() { + return generateSmallGraphStore(true); + } + + public static GraphStore generateSmallGraphStoreWithoutSelfLoop() { + return generateSmallGraphStore(false); + } + + private static GraphStore generateSmallGraphStore(boolean allowSelfLoops) { int edgeCount = 100; GraphStore graphStore = new GraphModelImpl().store; NodeImpl[] nodes = generateNodeList(Math .max((int) Math.ceil(Math.sqrt(edgeCount * 2)), (int) (edgeCount / 10.0)), graphStore); graphStore.addAllNodes(Arrays.asList(nodes)); - EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, edgeCount, 0, true, true, false); + EdgeImpl[] edges = generateEdgeList(graphStore.nodeStore, edgeCount, 0, true, allowSelfLoops, false); graphStore.addAllEdges(Arrays.asList(edges)); return graphStore; } diff --git a/src/test/java/org/gephi/graph/impl/GraphStoreTest.java b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java index e791e79e..f732630c 100644 --- a/src/test/java/org/gephi/graph/impl/GraphStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java @@ -772,6 +772,40 @@ public void testRemoveAllEdges() { } } + @Test + public void testRetainNodes() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Node[] nodes = graphStore.getNodes().toArray(); + Assert.assertFalse(graphStore.retainNodes(Arrays.asList(nodes))); + Assert.assertEquals(graphStore.getNodeCount(), nodes.length); + + Assert.assertTrue(graphStore.retainNodes(Collections.EMPTY_LIST)); + Assert.assertEquals(graphStore.getNodeCount(), 0); + + graphStore = GraphGenerator.generateSmallGraphStore(); + nodes = graphStore.getNodes().toArray(); + graphStore.retainNodes(Collections.singletonList(nodes[0])); + Assert.assertEquals(graphStore.getNodeCount(), 1); + Assert.assertTrue(graphStore.contains(nodes[0])); + } + + @Test + public void testRetainEdges() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStore(); + Edge[] edges = graphStore.getEdges().toArray(); + Assert.assertFalse(graphStore.retainEdges(Arrays.asList(edges))); + Assert.assertEquals(graphStore.getEdgeCount(), edges.length); + + Assert.assertTrue(graphStore.retainEdges(Collections.EMPTY_LIST)); + Assert.assertEquals(graphStore.getEdgeCount(), 0); + + graphStore = GraphGenerator.generateSmallGraphStore(); + edges = graphStore.getEdges().toArray(); + graphStore.retainEdges(Collections.singletonList(edges[0])); + Assert.assertEquals(graphStore.getEdgeCount(), 1); + Assert.assertTrue(graphStore.contains(edges[0])); + } + @Test public void testGetOpposite() { GraphStore graphStore = GraphGenerator.generateTinyGraphStore(); diff --git a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java index 453e0abd..09c7674a 100644 --- a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java @@ -17,6 +17,8 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; +import java.util.Arrays; +import java.util.Collections; import java.util.Random; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; @@ -356,6 +358,53 @@ public void testUndirectedRemoveNodesFirst() { Assert.assertEquals(graph.getEdgeCount(), 0); } + @Test + public void testDirectedRetainNodes() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStoreWithoutSelfLoop(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + view.fill(); + + Assert.assertFalse(graph.retainNodes(graphStore.getNodes().toCollection())); + Assert.assertEquals(graph.getNodeCount(), graphStore.getNodeCount()); + + Assert.assertTrue(graph.retainNodes(Collections.EMPTY_LIST)); + Assert.assertEquals(graph.getNodeCount(), 0); + + view.fill(); + Edge edge = graphStore.getEdges().toArray()[0]; + Assert.assertTrue(graph.retainNodes(Arrays.asList(edge.getSource(), edge.getTarget()))); + Assert.assertEquals(graph.getNodeCount(), 2); + Assert.assertTrue(graph.contains(edge.getSource())); + Assert.assertTrue(graph.contains(edge.getTarget())); + Assert.assertTrue(graph.contains(edge)); + } + + @Test + public void testDirectedRetainEdges() { + GraphStore graphStore = GraphGenerator.generateSmallGraphStoreWithoutSelfLoop(); + GraphViewStore store = graphStore.viewStore; + GraphViewImpl view = store.createView(); + + DirectedSubgraph graph = store.getDirectedGraph(view); + view.fill(); + + Assert.assertFalse(graph.retainEdges(graphStore.getEdges().toCollection())); + Assert.assertEquals(graph.getEdgeCount(), graphStore.getEdgeCount()); + + Assert.assertTrue(graph.retainEdges(Collections.EMPTY_LIST)); + Assert.assertEquals(graph.getEdgeCount(), 0); + + view.fill(); + Edge edge = graphStore.getEdges().toArray()[0]; + Assert.assertTrue(graph.retainEdges(Collections.singletonList(edge))); + Assert.assertEquals(graph.getNodeCount(), graphStore.getNodeCount()); + Assert.assertEquals(graph.getEdgeCount(), 1); + Assert.assertTrue(graph.contains(edge)); + } + @Test public void testDirectedClearEdges() { GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();