diff --git a/README.md b/README.md
index 5276ba33..5652e64b 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ compile 'org.gephi:graphstore:0.8.0'
## Dependencies
-GraphStore is built for JRE 17+ and depends on FastUtil and Colt.
+GraphStore is built for JRE 17+ and depends on FastUtil.
For a complete list of dependencies, consult the `pom.xml` file.
diff --git a/pom.xml b/pom.xml
index 462e27ad..14e2c0da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,11 +67,6 @@
fastutil
8.5.16
-
- colt
- colt
- 1.2.0
-
diff --git a/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
index d90aea76..f6a38ef4 100644
--- a/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
+++ b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
@@ -15,8 +15,7 @@
*/
package org.gephi.graph.impl;
-import cern.colt.bitvector.BitVector;
-import cern.colt.bitvector.QuickBitVector;
+import java.util.BitSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectLists;
@@ -40,7 +39,7 @@ public class ColumnObserverImpl implements ColumnObserver {
protected boolean destroyed;
// Config
protected final boolean withDiff;
- protected BitVector bitVector;
+ protected BitSet bitVector;
// Cache
protected ColumnDiffImpl columnDiff;
@@ -109,7 +108,7 @@ private void refreshDiff() {
boolean node = AttributeUtils.isNodeColumn(column);
columnDiff = node ? new NodeColumnDiffImpl() : new EdgeColumnDiffImpl();
- int size = bitVector.size();
+ int size = bitVector.length();
for (int i = 0; i < size; i++) {
boolean t = bitVector.get(i);
@@ -179,19 +178,11 @@ public EdgeIterable getTouchedElements() {
private void ensureVectorSize(ElementImpl element) {
int sid = element.getStoreId();
if (bitVector == null) {
- bitVector = new BitVector(sid + 1);
- } else if (sid >= bitVector.size()) {
- int newSize = Math.min(Math
+ int initialSize = Math.min(Math
.max(sid + 1, (int) (sid * GraphStoreConfiguration.COLUMNDIFF_GROWING_FACTOR)), Integer.MAX_VALUE);
- bitVector = growBitVector(bitVector, newSize);
+ bitVector = new BitSet(initialSize);
}
- }
-
- private BitVector growBitVector(BitVector bitVector, int size) {
- long[] elements = bitVector.elements();
- long[] newElements = QuickBitVector.makeBitVector(size, 1);
- System.arraycopy(elements, 0, newElements, 0, elements.length);
- return new BitVector(newElements, size);
+ // BitSet grows automatically when setting bits, no need to manually grow
}
private void readLock() {
diff --git a/src/main/java/org/gephi/graph/impl/GraphViewImpl.java b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java
index 6842d67a..ae02fb75 100644
--- a/src/main/java/org/gephi/graph/impl/GraphViewImpl.java
+++ b/src/main/java/org/gephi/graph/impl/GraphViewImpl.java
@@ -15,11 +15,9 @@
*/
package org.gephi.graph.impl;
-import cern.colt.bitvector.BitVector;
-import cern.colt.bitvector.QuickBitVector;
-import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -40,8 +38,8 @@ public class GraphViewImpl implements GraphView {
protected final boolean nodeView;
protected final boolean edgeView;
protected final GraphAttributesImpl attributes;
- protected BitVector nodeBitVector;
- protected BitVector edgeBitVector;
+ protected BitSet nodeBitVector;
+ protected BitSet edgeBitVector;
protected int storeId;
// Version
protected final GraphVersion version;
@@ -64,11 +62,11 @@ public GraphViewImpl(final GraphStore store, boolean nodes, boolean edges) {
this.edgeView = edges;
this.attributes = new GraphAttributesImpl();
if (nodes) {
- this.nodeBitVector = new BitVector(store.nodeStore.maxStoreId());
+ this.nodeBitVector = new BitSet(store.nodeStore.maxStoreId());
} else {
this.nodeBitVector = null;
}
- this.edgeBitVector = new BitVector(store.edgeStore.maxStoreId());
+ this.edgeBitVector = new BitSet(store.edgeStore.maxStoreId());
this.typeCounts = new int[GraphStoreConfiguration.VIEW_DEFAULT_TYPE_COUNT];
this.mutualEdgeTypeCounts = new int[GraphStoreConfiguration.VIEW_DEFAULT_TYPE_COUNT];
@@ -85,17 +83,18 @@ public GraphViewImpl(final GraphViewImpl view, boolean nodes, boolean edges) {
this.edgeView = edges;
this.attributes = new GraphAttributesImpl();
if (nodes) {
- this.nodeBitVector = view.nodeBitVector.copy();
+ this.nodeBitVector = (BitSet) view.nodeBitVector.clone();
this.nodeCount = view.nodeCount;
} else {
this.nodeBitVector = null;
}
this.edgeCount = view.edgeCount;
- this.edgeBitVector = view.edgeBitVector.copy();
+ this.edgeBitVector = (BitSet) view.edgeBitVector.clone();
this.typeCounts = new int[view.typeCounts.length];
System.arraycopy(view.typeCounts, 0, typeCounts, 0, view.typeCounts.length);
this.mutualEdgeTypeCounts = new int[view.mutualEdgeTypeCounts.length];
System.arraycopy(view.mutualEdgeTypeCounts, 0, mutualEdgeTypeCounts, 0, view.mutualEdgeTypeCounts.length);
+ this.mutualEdgesCount = view.mutualEdgesCount;
this.directedDecorator = new GraphViewDecorator(graphStore, this, false);
this.undirectedDecorator = new GraphViewDecorator(graphStore, this, true);
this.version = graphStore.version != null ? new GraphVersion(directedDecorator) : null;
@@ -144,9 +143,6 @@ public boolean addNode(final Node node) {
int edgeid = edge.storeId;
boolean edgeisSet = edgeBitVector.get(edgeid);
if (!edgeisSet) {
-
- incrementEdgeVersion();
-
addEdge(edge);
}
// End
@@ -268,23 +264,25 @@ public boolean removeNodeAll(final Collection extends Node> nodes) {
public boolean retainNodes(final Collection extends Node> c) {
if (nodeView) {
if (!c.isEmpty()) {
- IntOpenHashSet set = new IntOpenHashSet(c.size());
+ // Build BitSet of nodes to retain
+ BitSet retainSet = new BitSet(graphStore.nodeStore.maxStoreId());
for (Node o : c) {
checkValidNodeObject(o);
- set.add(o.getStoreId());
+ retainSet.set(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;
- }
- }
+ // Find nodes to remove: nodes in this view but NOT in retain set
+ // This is equivalent to: nodeBitVector AND NOT retainSet
+ BitSet nodesToRemove = (BitSet) nodeBitVector.clone();
+ nodesToRemove.andNot(retainSet);
+
+ if (nodesToRemove.isEmpty()) {
+ return false;
}
- return changed;
+
+ // Bulk remove nodes
+ bulkRemoveNodes(nodesToRemove);
+ return true;
} else if (nodeCount != 0) {
clear();
return true;
@@ -296,22 +294,25 @@ public boolean retainNodes(final Collection extends Node> c) {
public boolean retainEdges(final Collection extends Edge> c) {
if (edgeView) {
if (!c.isEmpty()) {
- IntOpenHashSet set = new IntOpenHashSet(c.size());
+ // Build BitSet of edges to retain
+ BitSet retainSet = new BitSet(graphStore.edgeStore.maxStoreId());
for (Edge o : c) {
checkValidEdgeObject(o);
- set.add(o.getStoreId());
+ retainSet.set(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;
- }
+ // Find edges to remove: edges in this view but NOT in retain set
+ // This is equivalent to: edgeBitVector AND NOT retainSet
+ BitSet edgesToRemove = (BitSet) edgeBitVector.clone();
+ edgesToRemove.andNot(retainSet);
+
+ if (edgesToRemove.isEmpty()) {
+ return false;
}
- return changed;
+
+ // Bulk remove edges
+ bulkRemoveEdges(edgesToRemove);
+ return true;
} else if (edgeCount != 0) {
clearEdges();
return true;
@@ -413,16 +414,10 @@ public void clearEdges() {
public void fill() {
if (nodeView) {
- if (nodeCount > 0) {
- nodeBitVector = new BitVector(graphStore.nodeStore.maxStoreId());
- }
- nodeBitVector.not();
+ nodeBitVector.set(0, graphStore.nodeStore.maxStoreId(), true);
this.nodeCount = graphStore.nodeStore.size();
}
- if (edgeCount > 0) {
- edgeBitVector = new BitVector(graphStore.edgeStore.maxStoreId());
- }
- edgeBitVector.not();
+ edgeBitVector.set(0, graphStore.edgeStore.maxStoreId());
this.edgeCount = graphStore.edgeStore.size();
int typeLength = graphStore.edgeStore.longDictionary.length;
@@ -474,90 +469,126 @@ public boolean containsEdge(final Edge edge) {
}
public void intersection(final GraphViewImpl otherView) {
- BitVector nodeOtherBitVector = otherView.nodeBitVector;
- BitVector edgeOtherBitVector = otherView.edgeBitVector;
+ BitSet nodeOtherBitVector = otherView.nodeBitVector;
+ BitSet edgeOtherBitVector = otherView.edgeBitVector;
if (nodeView) {
- int nodeSize = nodeBitVector.size();
- for (int i = 0; i < nodeSize; i++) {
- boolean t = nodeBitVector.get(i);
- boolean o = nodeOtherBitVector.get(i);
- if (t && !o) {
- removeNode(getNode(i));
- }
+ // Find nodes to remove: nodes in this view but NOT in other view
+ BitSet nodesToRemove = (BitSet) nodeBitVector.clone();
+ nodesToRemove.andNot(nodeOtherBitVector);
+
+ if (!nodesToRemove.isEmpty()) {
+ // Bulk remove nodes
+ bulkRemoveNodes(nodesToRemove);
}
}
if (edgeView) {
- int edgeSize = edgeBitVector.size();
- for (int i = 0; i < edgeSize; i++) {
- boolean t = edgeBitVector.get(i);
- boolean o = edgeOtherBitVector.get(i);
- if (t && !o) {
- removeEdge(getEdge(i));
- }
+ // Find edges to remove: edges in this view but NOT in other view
+ BitSet edgesToRemove = (BitSet) edgeBitVector.clone();
+ edgesToRemove.andNot(edgeOtherBitVector);
+
+ if (!edgesToRemove.isEmpty()) {
+ // Bulk remove edges
+ bulkRemoveEdges(edgesToRemove);
}
}
}
public void union(final GraphViewImpl otherView) {
- BitVector nodeOtherBitVector = otherView.nodeBitVector;
- BitVector edgeOtherBitVector = otherView.edgeBitVector;
+ BitSet nodeOtherBitVector = otherView.nodeBitVector;
+ BitSet edgeOtherBitVector = otherView.edgeBitVector;
if (nodeView) {
- int nodeSize = nodeBitVector.size();
- for (int i = 0; i < nodeSize; i++) {
- boolean t = nodeBitVector.get(i);
- boolean o = nodeOtherBitVector.get(i);
- if (!t && o) {
- addNode(getNode(i));
- }
+ // Find nodes to add: nodes in other view but NOT in this view
+ BitSet nodesToAdd = (BitSet) nodeOtherBitVector.clone();
+ nodesToAdd.andNot(nodeBitVector);
+
+ if (!nodesToAdd.isEmpty()) {
+ // Bulk add nodes
+ bulkAddNodes(nodesToAdd);
}
}
if (edgeView) {
- int edgeSize = edgeBitVector.size();
- for (int i = 0; i < edgeSize; i++) {
- boolean t = edgeBitVector.get(i);
- boolean o = edgeOtherBitVector.get(i);
- if (!t && o) {
- addEdge(getEdge(i));
- }
+ // Find edges to add: edges in other view but NOT in this view
+ BitSet edgesToAdd = (BitSet) edgeOtherBitVector.clone();
+ edgesToAdd.andNot(edgeBitVector);
+
+ if (!edgesToAdd.isEmpty()) {
+ // Bulk add edges
+ bulkAddEdges(edgesToAdd);
}
}
}
public void not() {
+ // Flip node bits if this is a node view
if (nodeView) {
- nodeBitVector.not();
+ nodeBitVector.flip(0, graphStore.nodeStore.maxStoreId());
this.nodeCount = graphStore.nodeStore.size() - this.nodeCount;
+ incrementNodeVersion();
}
- edgeBitVector.not();
+ // Flip edge bits
+ edgeBitVector.flip(0, graphStore.edgeStore.maxStoreId());
+
+ // Update edge counts by subtracting from totals
this.edgeCount = graphStore.edgeStore.size() - this.edgeCount;
- for (int i = 0; i < typeCounts.length; i++) {
+
+ // Ensure type count arrays are sized to match the store
+ int storeTypeLength = graphStore.edgeStore.longDictionary.length;
+ if (typeCounts.length < storeTypeLength) {
+ int[] newTypeCounts = new int[storeTypeLength];
+ System.arraycopy(typeCounts, 0, newTypeCounts, 0, typeCounts.length);
+ typeCounts = newTypeCounts;
+
+ int[] newMutualCounts = new int[storeTypeLength];
+ System.arraycopy(mutualEdgeTypeCounts, 0, newMutualCounts, 0, mutualEdgeTypeCounts.length);
+ mutualEdgeTypeCounts = newMutualCounts;
+ }
+
+ // Invert all type counts (including types that weren't in the view before)
+ for (int i = 0; i < storeTypeLength; i++) {
this.typeCounts[i] = graphStore.edgeStore.longDictionary[i].size() - this.typeCounts[i];
}
- for (int i = 0; i < mutualEdgeTypeCounts.length; i++) {
+ for (int i = 0; i < graphStore.edgeStore.mutualEdgesTypeSize.length; i++) {
this.mutualEdgeTypeCounts[i] = graphStore.edgeStore.mutualEdgesTypeSize[i] - this.mutualEdgeTypeCounts[i];
}
this.mutualEdgesCount = graphStore.edgeStore.mutualEdgesSize - this.mutualEdgesCount;
- if (nodeView) {
- incrementNodeVersion();
- }
incrementEdgeVersion();
+ // If node view is enabled, remove edges with invalid endpoints
+ // Optimization: Only iterate through edges that are NOW in the view (after
+ // flip)
+ // instead of all edges in the store
if (nodeView) {
- for (Edge e : graphStore.edgeStore) {
- boolean t = edgeBitVector.get(e.getStoreId());
- if (t && (!nodeBitVector.get(e.getSource().getStoreId()) || !nodeBitVector
- .get(e.getTarget().getStoreId()))) {
- removeEdge((EdgeImpl) e);
+ BitSet edgesToRemove = new BitSet();
+
+ // Iterate only over edges that are set in the view (much faster for sparse
+ // views)
+ for (int edgeId = edgeBitVector.nextSetBit(0); edgeId >= 0; edgeId = edgeBitVector.nextSetBit(edgeId + 1)) {
+ EdgeImpl edge = getEdge(edgeId);
+ if (edge == null) {
+ // SAFETY: Edge no longer exists in store
+ edgesToRemove.set(edgeId);
+ continue;
}
+ // Check if both endpoints are in the node view
+ if (!nodeBitVector.get(edge.source.storeId) || !nodeBitVector.get(edge.target.storeId)) {
+ edgesToRemove.set(edgeId);
+ }
+ }
+
+ // Bulk remove invalid edges
+ if (!edgesToRemove.isEmpty()) {
+ bulkRemoveEdgesForNot(edgesToRemove);
}
}
+ // Rebuild indexes (necessary for NOT operation as the view content has
+ // completely changed)
if (nodeView) {
IndexStore nodeIndexStore = graphStore.nodeTable.store.indexStore;
if (nodeIndexStore != null) {
@@ -590,6 +621,251 @@ public void addEdgeInNodeView(EdgeImpl edge) {
}
}
+ /**
+ * Bulk remove nodes from the view. This is more efficient than removing nodes
+ * one by one as it batches index updates and increments version only once.
+ */
+ private void bulkRemoveNodes(BitSet nodesToRemove) {
+ // First pass: collect all edges to remove (incident to removed nodes)
+ BitSet edgesToRemove = new BitSet();
+ for (int nodeId = nodesToRemove.nextSetBit(0); nodeId >= 0; nodeId = nodesToRemove.nextSetBit(nodeId + 1)) {
+ NodeImpl node = getNode(nodeId);
+ if (node == null) {
+ continue; // SAFETY: Node was removed from store (storeId reused or deleted)
+ }
+
+ EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false);
+ while (itr.hasNext()) {
+ EdgeImpl edge = itr.next();
+ int edgeId = edge.storeId;
+ if (edgeBitVector.get(edgeId)) {
+ edgesToRemove.set(edgeId);
+ }
+ }
+ }
+
+ // Remove edges in bulk
+ if (!edgesToRemove.isEmpty()) {
+ bulkRemoveEdges(edgesToRemove);
+ }
+
+ // Update node bit vector
+ int removedCount = nodesToRemove.cardinality();
+ nodeBitVector.andNot(nodesToRemove);
+ nodeCount -= removedCount;
+ incrementNodeVersion();
+
+ // Bulk update indexes
+ IndexStore indexStore = graphStore.nodeTable.store.indexStore;
+ TimeIndexStore timeIndexStore = graphStore.timeStore.nodeIndexStore;
+
+ if (indexStore != null || timeIndexStore != null) {
+ for (int i = nodesToRemove.nextSetBit(0); i >= 0; i = nodesToRemove.nextSetBit(i + 1)) {
+ NodeImpl node = getNode(i);
+ if (node != null) { // SAFETY: Skip if node no longer exists
+ if (indexStore != null) {
+ indexStore.clearInView(node, this);
+ }
+ if (timeIndexStore != null) {
+ timeIndexStore.clearInView(node, this);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Bulk add nodes to the view. This is more efficient than adding nodes one by
+ * one as it batches index updates and increments version only once.
+ */
+ private void bulkAddNodes(BitSet nodesToAdd) {
+ // Update node bit vector
+ int addedCount = nodesToAdd.cardinality();
+ nodeBitVector.or(nodesToAdd);
+ nodeCount += addedCount;
+ incrementNodeVersion();
+
+ // Bulk update indexes
+ IndexStore indexStore = graphStore.nodeTable.store.indexStore;
+ TimeIndexStore timeIndexStore = graphStore.timeStore.nodeIndexStore;
+
+ if (indexStore != null || timeIndexStore != null) {
+ for (int i = nodesToAdd.nextSetBit(0); i >= 0; i = nodesToAdd.nextSetBit(i + 1)) {
+ NodeImpl node = getNode(i);
+ if (node != null) { // SAFETY: Skip if node no longer exists
+ if (indexStore != null) {
+ indexStore.indexInView(node, this);
+ }
+ if (timeIndexStore != null) {
+ timeIndexStore.indexInView(node, this);
+ }
+ }
+ }
+ }
+
+ // If nodeView && !edgeView, add edges between newly added nodes and existing
+ // nodes
+ if (nodeView && !edgeView) {
+ BitSet edgesToAdd = new BitSet();
+ for (int nodeId = nodesToAdd.nextSetBit(0); nodeId >= 0; nodeId = nodesToAdd.nextSetBit(nodeId + 1)) {
+ NodeImpl node = getNode(nodeId);
+ if (node == null) {
+ continue; // SAFETY: Skip if node no longer exists
+ }
+
+ EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, false);
+ while (itr.hasNext()) {
+ EdgeImpl edge = itr.next();
+ NodeImpl opposite = edge.source == node ? edge.target : edge.source;
+ // Check if opposite node is in view and edge is not already in view
+ if (nodeBitVector.get(opposite.storeId) && !edgeBitVector.get(edge.storeId)) {
+ edgesToAdd.set(edge.storeId);
+ }
+ }
+ }
+
+ if (!edgesToAdd.isEmpty()) {
+ bulkAddEdges(edgesToAdd);
+ }
+ }
+ }
+
+ /**
+ * Bulk remove edges from the view. This is more efficient than removing edges
+ * one by one as it updates stats in bulk and increments version only once.
+ */
+ private void bulkRemoveEdges(BitSet edgesToRemove) {
+ // Update edge bit vector
+ int removedCount = edgesToRemove.cardinality();
+ edgeBitVector.andNot(edgesToRemove);
+ edgeCount -= removedCount;
+
+ // Update type counts and mutual edge counts
+ for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) {
+ EdgeImpl edge = getEdge(i);
+ if (edge == null) {
+ continue; // SAFETY: Edge was removed from store (storeId reused or deleted)
+ }
+
+ int type = edge.type;
+ ensureTypeCountArrayCapacity(type);
+ typeCounts[type]--;
+
+ if (edge.isMutual() && !edge.isSelfLoop()) {
+ EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false);
+ if (reverseEdge != null && containsEdge(reverseEdge)) {
+ mutualEdgeTypeCounts[type]--;
+ mutualEdgesCount--;
+ }
+ }
+ }
+
+ incrementEdgeVersion();
+
+ // Bulk update indexes
+ IndexStore indexStore = graphStore.edgeTable.store.indexStore;
+ TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore;
+
+ if (indexStore != null || timeIndexStore != null) {
+ for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) {
+ EdgeImpl edge = getEdge(i);
+ if (edge != null) { // SAFETY: Skip if edge no longer exists
+ if (indexStore != null) {
+ indexStore.clearInView(edge, this);
+ }
+ if (timeIndexStore != null) {
+ timeIndexStore.clearInView(edge, this);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Bulk add edges to the view. This is more efficient than adding edges one by
+ * one as it updates stats in bulk and increments version only once.
+ */
+ private void bulkAddEdges(BitSet edgesToAdd) {
+ // Update edge bit vector
+ int addedCount = edgesToAdd.cardinality();
+ edgeBitVector.or(edgesToAdd);
+ edgeCount += addedCount;
+
+ // Update type counts and mutual edge counts
+ for (int i = edgesToAdd.nextSetBit(0); i >= 0; i = edgesToAdd.nextSetBit(i + 1)) {
+ EdgeImpl edge = getEdge(i);
+ if (edge == null) {
+ continue; // SAFETY: Skip if edge no longer exists in store
+ }
+
+ int type = edge.type;
+ ensureTypeCountArrayCapacity(type);
+ typeCounts[type]++;
+
+ if (edge.isMutual() && !edge.isSelfLoop()) {
+ EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false);
+ if (reverseEdge != null && containsEdge(reverseEdge)) {
+ mutualEdgeTypeCounts[type]++;
+ mutualEdgesCount++;
+ }
+ }
+ }
+
+ incrementEdgeVersion();
+
+ // Bulk update indexes
+ IndexStore indexStore = graphStore.edgeTable.store.indexStore;
+ TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore;
+
+ if (indexStore != null || timeIndexStore != null) {
+ for (int i = edgesToAdd.nextSetBit(0); i >= 0; i = edgesToAdd.nextSetBit(i + 1)) {
+ EdgeImpl edge = getEdge(i);
+ if (edge != null) { // SAFETY: Skip if edge no longer exists
+ if (indexStore != null) {
+ indexStore.indexInView(edge, this);
+ }
+ if (timeIndexStore != null) {
+ timeIndexStore.indexInView(edge, this);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Special bulk remove for the not() operation. This updates the bit vector and
+ * stats but does NOT increment version or update indexes (those are handled
+ * separately in not()).
+ */
+ private void bulkRemoveEdgesForNot(BitSet edgesToRemove) {
+ // Update edge bit vector
+ int removedCount = edgesToRemove.cardinality();
+ edgeBitVector.andNot(edgesToRemove);
+ edgeCount -= removedCount;
+
+ // Update type counts and mutual edge counts
+ for (int i = edgesToRemove.nextSetBit(0); i >= 0; i = edgesToRemove.nextSetBit(i + 1)) {
+ EdgeImpl edge = getEdge(i);
+ if (edge == null) {
+ continue; // SAFETY: Skip if edge no longer exists in store
+ }
+
+ int type = edge.type;
+ ensureTypeCountArrayCapacity(type);
+ typeCounts[type]--;
+
+ if (edge.isMutual() && !edge.isSelfLoop()) {
+ EdgeImpl reverseEdge = graphStore.edgeStore.get(edge.target, edge.source, edge.type, false);
+ if (reverseEdge != null && containsEdge(reverseEdge)) {
+ mutualEdgeTypeCounts[type]--;
+ mutualEdgesCount--;
+ }
+ }
+ }
+ // Note: Version increment and index updates are handled by the caller (not()
+ // method)
+ }
+
public int getNodeCount() {
if (nodeView) {
return nodeCount;
@@ -687,33 +963,11 @@ protected void destroyAllObservers() {
}
protected void ensureNodeVectorSize(NodeImpl node) {
- int sid = node.storeId;
- if (sid >= nodeBitVector.size()) {
- int newSize = Math.min(Math
- .max(sid + 1, (int) (sid * GraphStoreConfiguration.VIEW_GROWING_FACTOR)), Integer.MAX_VALUE);
- nodeBitVector = growBitVector(nodeBitVector, newSize);
- }
- }
-
- private void ensureNodeVectorSize(int size) {
- if (size > nodeBitVector.size()) {
- nodeBitVector = growBitVector(nodeBitVector, size);
- }
- }
-
- private void ensureEdgeVectorSize(int size) {
- if (size > edgeBitVector.size()) {
- edgeBitVector = growBitVector(edgeBitVector, size);
- }
+ // BitSet automatically grows as needed, no manual resizing required
}
protected void ensureEdgeVectorSize(EdgeImpl edge) {
- int sid = edge.storeId;
- if (sid >= edgeBitVector.size()) {
- int newSize = Math.min(Math
- .max(sid + 1, (int) (sid * GraphStoreConfiguration.VIEW_GROWING_FACTOR)), Integer.MAX_VALUE);
- edgeBitVector = growBitVector(edgeBitVector, newSize);
- }
+ // BitSet automatically grows as needed, no manual resizing required
}
protected void setEdgeType(EdgeImpl edgeImpl, int oldType, boolean wasMutual) {
@@ -779,13 +1033,10 @@ private void removeEdge(EdgeImpl edgeImpl) {
if (indexStore != null) {
indexStore.clearInView(edgeImpl, this);
}
- }
-
- private BitVector growBitVector(BitVector bitVector, int size) {
- long[] elements = bitVector.elements();
- long[] newElements = QuickBitVector.makeBitVector(size, 1);
- System.arraycopy(elements, 0, newElements, 0, elements.length);
- return new BitVector(newElements, size);
+ TimeIndexStore timeIndexStore = graphStore.timeStore.edgeIndexStore;
+ if (timeIndexStore != null) {
+ timeIndexStore.clearInView(edgeImpl, this);
+ }
}
private NodeImpl getNode(int id) {
diff --git a/src/main/java/org/gephi/graph/impl/Serialization.java b/src/main/java/org/gephi/graph/impl/Serialization.java
index f50449bb..721b41bd 100644
--- a/src/main/java/org/gephi/graph/impl/Serialization.java
+++ b/src/main/java/org/gephi/graph/impl/Serialization.java
@@ -15,7 +15,7 @@
*/
package org.gephi.graph.impl;
-import cern.colt.bitvector.BitVector;
+import java.util.BitSet;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import it.unimi.dsi.fastutil.booleans.BooleanOpenHashSet;
import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap;
@@ -667,8 +667,8 @@ private GraphViewImpl deserializeGraphView(final DataInput is) throws IOExceptio
int storeId = (Integer) deserialize(is);
int nodeCount = (Integer) deserialize(is);
int edgeCount = (Integer) deserialize(is);
- BitVector nodeCountVector = (BitVector) deserialize(is);
- BitVector edgeCountVector = (BitVector) deserialize(is);
+ BitSet nodeCountVector = (BitSet) deserialize(is);
+ BitSet edgeCountVector = (BitSet) deserialize(is);
int[] typeCounts = (int[]) deserialize(is);
int[] mutualEdgeTypeCounts = (int[]) deserialize(is);
int mutualEdgesCount = (Integer) deserialize(is);
@@ -695,15 +695,36 @@ private GraphViewImpl deserializeGraphView(final DataInput is) throws IOExceptio
return view;
}
- private void serializeBitVector(final DataOutput out, final BitVector bitVector) throws IOException {
- serialize(out, bitVector.size());
- serialize(out, bitVector.elements());
+ // Made compatible with legacy BitVector serialization, which was in place until
+ // version 0.8.1
+ public void serializeBitSet(final DataOutput out, final BitSet bitSet) throws IOException {
+ // BitSet.length() returns the index of the highest set bit + 1
+ // This gives us the logical size (0 if empty)
+ int size = bitSet.length();
+
+ serialize(out, size);
+
+ // Get the long array from BitSet
+ long[] words = bitSet.toLongArray();
+
+ // Calculate how many longs BitVector would use for this size
+ int requiredLongs = (size + 63) / 64;
+
+ // Create array with the exact required size (matching BitVector format)
+ long[] elements = new long[requiredLongs];
+
+ // Copy the BitSet data
+ System.arraycopy(words, 0, elements, 0, Math.min(words.length, requiredLongs));
+
+ serialize(out, elements);
}
- private BitVector deserializeBitVector(final DataInput is) throws IOException, ClassNotFoundException {
+ public BitSet deserializeBitSet(final DataInput is) throws IOException, ClassNotFoundException {
int size = (Integer) deserialize(is);
long[] elements = (long[]) deserialize(is);
- return new BitVector(elements, size);
+
+ // BitSet.valueOf() handles the long array correctly
+ return BitSet.valueOf(elements);
}
private void serializeGraphStoreConfiguration(final DataOutput out) throws IOException {
@@ -1558,10 +1579,10 @@ protected void serialize(final DataOutput out, final Object obj) throws IOExcept
GraphViewImpl b = (GraphViewImpl) obj;
out.write(GRAPH_VIEW);
serializeGraphView(out, b);
- } else if (obj instanceof BitVector) {
- BitVector bv = (BitVector) obj;
+ } else if (obj instanceof BitSet) {
+ BitSet bs = (BitSet) obj;
out.write(BIT_VECTOR);
- serializeBitVector(out, bv);
+ serializeBitSet(out, bs);
} else if (obj instanceof GraphVersion) {
GraphVersion b = (GraphVersion) obj;
out.write(GRAPH_VERSION);
@@ -2122,7 +2143,7 @@ protected Object deserialize(DataInput is) throws IOException, ClassNotFoundExce
ret = deserializeGraphView(is);
break;
case BIT_VECTOR:
- ret = deserializeBitVector(is);
+ ret = deserializeBitSet(is);
break;
case GRAPH_STORE_CONFIGURATION:
ret = deserializeGraphStoreConfiguration(is);
diff --git a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java
index ad340afb..a3352163 100644
--- a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java
+++ b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java
@@ -786,6 +786,7 @@ public void testIsIncident() {
Edge edge = graphStore.factory.newEdge("edge", n1, n1, EdgeTypeStore.NULL_LABEL, 1.0, true);
graphStore.addEdge(edge);
+ view.addEdge(edge); // Explicitly add edge to view
Assert.assertTrue(graph.isIncident(edge, graph.getEdge("0")));
}
diff --git a/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java b/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java
index 875f6b23..e9e46e84 100644
--- a/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java
+++ b/src/test/java/org/gephi/graph/impl/GraphViewImplTest.java
@@ -15,7 +15,10 @@
*/
package org.gephi.graph.impl;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import org.gephi.graph.api.DirectedSubgraph;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.Graph;
@@ -507,4 +510,673 @@ public void testEdgeViewSetEdgeTypeMutualEdges() {
Assert.assertEquals(view.getUndirectedEdgeCount(0), 0);
Assert.assertEquals(view.getUndirectedEdgeCount(1), 1);
}
+
+ @Test
+ public void testCopyConstructor() {
+ GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+ view.fill();
+
+ // Original view should have mutual edges counted
+ int originalEdgeCount = view.getEdgeCount();
+ int originalMutualCount = view.mutualEdgesCount;
+ int originalUndirectedCount = view.getUndirectedEdgeCount();
+
+ // Create a copy using the copy constructor
+ GraphViewImpl copiedView = new GraphViewImpl(view, true, true);
+
+ // Verify
+ Assert.assertEquals(copiedView.getNodeCount(), view
+ .getNodeCount(), "Node count should be the same in copied view");
+ Assert.assertEquals(copiedView.nodeBitVector, view.nodeBitVector, "Node bit vector should be the same in copied view");
+ Assert.assertEquals(copiedView.edgeBitVector, view.edgeBitVector, "Edge bit vector should be the same in copied view");
+
+ // Verify that mutualEdgesCount was copied correctly
+ Assert.assertEquals(copiedView.mutualEdgesCount, originalMutualCount, "mutualEdgesCount should be copied in copy constructor");
+ Assert.assertEquals(copiedView.getEdgeCount(), originalEdgeCount);
+ Assert.assertEquals(copiedView
+ .getUndirectedEdgeCount(), originalUndirectedCount, "getUndirectedEdgeCount() should return correct value after copy");
+ }
+
+ @Test
+ public void testFilledViewRequiresExplicitAdd() {
+ // Test that users must explicitly add edges to filled views
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+ int initialEdgeCount = view.getEdgeCount();
+
+ // Add a new edge to the main graph store
+ NodeImpl n1 = graphStore.getNode("0");
+ NodeImpl n2 = graphStore.getNode("1");
+ EdgeImpl newEdge = new EdgeImpl("newEdge", n1, n2, 0, 1.0, true);
+ graphStore.addEdge(newEdge);
+
+ // Edge should not be in view yet
+ Assert.assertFalse(view.containsEdge(newEdge));
+
+ // Explicitly add the edge to the view
+ boolean added = view.addEdge(newEdge);
+
+ // Now it should be in the view
+ Assert.assertTrue(added, "addEdge should return true");
+ Assert.assertTrue(view.containsEdge(newEdge), "Edge should be in view after explicit add");
+ Assert.assertEquals(view.getEdgeCount(), initialEdgeCount + 1);
+ }
+
+ // ========== Tests for Mutual Edge Counts in Bulk Operations ==========
+
+ @Test
+ public void testIntersectionWithMutualEdges() {
+ GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView();
+
+ // Fill both views
+ view1.fill();
+ view2.fill();
+
+ // Initial state: both views have mutual edges (mutual count is 1 for a pair)
+ Assert.assertEquals(view1.mutualEdgesCount, 1, "View1 should have mutual count of 1");
+ Assert.assertEquals(view1.getEdgeCount(), 2, "View1 should have 2 edges");
+ Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge");
+
+ // Remove one of the mutual edges from view2
+ EdgeImpl e1 = graphStore.getEdge("1");
+ view2.removeEdge(e1);
+
+ // After removing one mutual edge, view2 should have no mutual edges
+ Assert.assertEquals(view2.mutualEdgesCount, 0, "View2 should have 0 mutual edges after removal");
+ Assert.assertEquals(view2.getEdgeCount(), 1, "View2 should have 1 edge");
+ Assert.assertEquals(view2.getUndirectedEdgeCount(), 1, "View2 should have 1 undirected edge");
+
+ // Intersection should result in view1 losing its mutual edge status
+ view1.intersection(view2);
+
+ Assert.assertEquals(view1.mutualEdgesCount, 0, "View1 should have 0 mutual edges after intersection");
+ Assert.assertEquals(view1.getEdgeCount(), 1, "View1 should have 1 edge total");
+ Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge");
+ }
+
+ @Test
+ public void testUnionWithMutualEdges() {
+ GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView();
+
+ EdgeImpl e0 = graphStore.getEdge("0");
+ EdgeImpl e1 = graphStore.getEdge("1");
+ NodeImpl n1 = e0.getSource();
+ NodeImpl n2 = e0.getTarget();
+
+ // View1 has only one edge of the mutual pair
+ view1.addNode(n1);
+ view1.addNode(n2);
+ view1.addEdge(e0);
+
+ // View2 has only the other edge of the mutual pair
+ view2.addNode(n1);
+ view2.addNode(n2);
+ view2.addEdge(e1);
+
+ // Neither view should have mutual edges yet
+ Assert.assertEquals(view1.mutualEdgesCount, 0, "View1 should have 0 mutual edges initially");
+ Assert.assertEquals(view2.mutualEdgesCount, 0, "View2 should have 0 mutual edges initially");
+
+ // Union should create mutual edges (mutual count is 1 for a pair)
+ view1.union(view2);
+
+ Assert.assertEquals(view1.mutualEdgesCount, 1, "View1 should have mutual count of 1 after union");
+ Assert.assertEquals(view1.getEdgeCount(), 2, "View1 should have 2 edges total");
+ Assert.assertEquals(view1.getUndirectedEdgeCount(), 1, "View1 should have 1 undirected edge");
+ }
+
+ @Test
+ public void testNotWithMutualEdges() {
+ GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ EdgeImpl e0 = graphStore.getEdge("0");
+ EdgeImpl e1 = graphStore.getEdge("1");
+ NodeImpl n1 = e0.getSource();
+ NodeImpl n2 = e0.getTarget();
+
+ // Add only one edge of the mutual pair
+ view.addNode(n1);
+ view.addNode(n2);
+ view.addEdge(e0);
+
+ Assert.assertEquals(view.mutualEdgesCount, 0, "View should have 0 mutual edges initially");
+ Assert.assertEquals(view.getEdgeCount(), 1, "View should have 1 edge");
+ Assert.assertEquals(view.getNodeCount(), 2, "View should have 2 nodes");
+
+ // NOT operation flips both nodes and edges
+ // Since there are only 2 nodes total in the graph, after NOT we have 0 nodes
+ // Edges without endpoints get removed, so we end up with 0 edges
+ view.not();
+
+ Assert.assertEquals(view.getNodeCount(), 0, "View should have 0 nodes after NOT (graph has 2 nodes total)");
+ Assert.assertEquals(view.getEdgeCount(), 0, "View should have 0 edges after NOT (no nodes, so no edges)");
+ }
+
+ // ========== Tests for Multi-Type Edge Counts in Bulk Operations ==========
+
+ @Test
+ public void testIntersectionMultipleEdgeTypes() {
+ GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView();
+
+ // Fill both views
+ view1.fill();
+ view2.fill();
+
+ // Verify initial state has multiple edge types
+ int type0CountInitial = view1.getEdgeCount(0);
+ int type1CountInitial = view1.getEdgeCount(1);
+ Assert.assertTrue(type0CountInitial > 0, "Should have type 0 edges");
+ Assert.assertTrue(type1CountInitial > 0, "Should have type 1 edges");
+
+ // Remove all type 0 edges from view2
+ for (Edge e : graphStore.getEdges().toArray()) {
+ if (e.getType() == 0) {
+ view2.removeEdge(e);
+ }
+ }
+
+ Assert.assertEquals(view2.getEdgeCount(0), 0, "View2 should have 0 type 0 edges");
+ Assert.assertEquals(view2.getEdgeCount(1), type1CountInitial, "View2 should still have all type 1 edges");
+
+ // Intersection should remove all type 0 edges from view1
+ view1.intersection(view2);
+
+ Assert.assertEquals(view1.getEdgeCount(0), 0, "View1 should have 0 type 0 edges after intersection");
+ Assert.assertEquals(view1
+ .getEdgeCount(1), type1CountInitial, "View1 should have all type 1 edges after intersection");
+ int type2CountInitial = view1.getEdgeCount(2);
+ Assert.assertEquals(view1
+ .getEdgeCount(), type1CountInitial + type2CountInitial, "Total edge count should match sum of type 1 and type 2");
+ }
+
+ @Test
+ public void testUnionMultipleEdgeTypes() {
+ GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView();
+
+ // Add only type 0 edges to view1
+ for (Node n : graphStore.getNodes()) {
+ view1.addNode(n);
+ }
+ for (Edge e : graphStore.getEdges().toArray()) {
+ if (e.getType() == 0) {
+ view1.addEdge(e);
+ }
+ }
+
+ // Add only type 1 and type 2 edges to view2
+ for (Node n : graphStore.getNodes()) {
+ view2.addNode(n);
+ }
+ for (Edge e : graphStore.getEdges().toArray()) {
+ if (e.getType() == 1 || e.getType() == 2) {
+ view2.addEdge(e);
+ }
+ }
+
+ int type0Count = view1.getEdgeCount(0);
+ int type1Count = view2.getEdgeCount(1);
+ int type2Count = view2.getEdgeCount(2);
+
+ Assert.assertTrue(type0Count > 0, "View1 should have type 0 edges");
+ Assert.assertEquals(view2.getEdgeCount(0), 0, "View2 should have no type 0 edges");
+ Assert.assertTrue(type1Count > 0, "View2 should have type 1 edges");
+ Assert.assertTrue(type2Count > 0, "View2 should have type 2 edges");
+
+ // Union should combine both types
+ view1.union(view2);
+
+ Assert.assertEquals(view1.getEdgeCount(0), type0Count, "View1 should have all type 0 edges after union");
+ Assert.assertEquals(view1.getEdgeCount(1), type1Count, "View1 should have all type 1 edges after union");
+ Assert.assertEquals(view1.getEdgeCount(2), type2Count, "View1 should have all type 2 edges after union");
+ Assert.assertEquals(view1
+ .getEdgeCount(), type0Count + type1Count + type2Count, "Total should be sum of all types");
+ }
+
+ @Test
+ public void testNotMultipleEdgeTypes() {
+ GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ // Add all nodes but only type 0 edges
+ for (Node n : graphStore.getNodes()) {
+ view.addNode(n);
+ }
+ for (Edge e : graphStore.getEdges().toArray()) {
+ if (e.getType() == 0) {
+ view.addEdge(e);
+ }
+ }
+
+ int type0Count = view.getEdgeCount(0);
+ int nodeCount = view.getNodeCount();
+ int totalNodesInStore = graphStore.getNodeCount();
+
+ Assert.assertTrue(type0Count > 0, "View should have type 0 edges");
+
+ // NOT operation flips both nodes and edges
+ // Since we have all nodes, after NOT we have 0 nodes
+ // All edges get removed because they have no valid endpoints
+ view.not();
+
+ Assert.assertEquals(view
+ .getNodeCount(), totalNodesInStore - nodeCount, "View should have inverted node count after NOT");
+ Assert.assertEquals(view
+ .getEdgeCount(), 0, "View should have 0 edges after NOT (no nodes means no valid edges)");
+ }
+
+ // ========== Tests for Empty View Edge Cases ==========
+
+ @Test
+ public void testIntersectionWithEmptyView() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView(); // Empty view
+
+ // Fill view1
+ view1.fill();
+ int initialNodeCount = view1.getNodeCount();
+ int initialEdgeCount = view1.getEdgeCount();
+
+ Assert.assertTrue(initialNodeCount > 0, "View1 should have nodes");
+ Assert.assertTrue(initialEdgeCount > 0, "View1 should have edges");
+ Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty");
+ Assert.assertEquals(view2.getEdgeCount(), 0, "View2 should be empty");
+
+ // Intersection with empty view should result in empty view1
+ view1.intersection(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty after intersection with empty view");
+ Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should have no edges after intersection with empty view");
+ }
+
+ @Test
+ public void testIntersectionOfEmptyView() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView(); // Empty view
+ GraphViewImpl view2 = store.createView();
+
+ // Fill view2
+ view2.fill();
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty");
+ Assert.assertTrue(view2.getNodeCount() > 0, "View2 should have nodes");
+
+ // Intersection of empty view with filled view should stay empty
+ view1.intersection(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty after intersection");
+ Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges after intersection");
+ }
+
+ @Test
+ public void testUnionWithEmptyView() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView();
+ GraphViewImpl view2 = store.createView(); // Empty view
+
+ // Fill view1
+ view1.fill();
+ int initialNodeCount = view1.getNodeCount();
+ int initialEdgeCount = view1.getEdgeCount();
+
+ Assert.assertTrue(initialNodeCount > 0, "View1 should have nodes");
+ Assert.assertTrue(initialEdgeCount > 0, "View1 should have edges");
+ Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty");
+
+ // Union with empty view should not change view1
+ view1.union(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), initialNodeCount, "View1 node count should not change");
+ Assert.assertEquals(view1.getEdgeCount(), initialEdgeCount, "View1 edge count should not change");
+ }
+
+ @Test
+ public void testUnionOfEmptyView() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView(); // Empty view
+ GraphViewImpl view2 = store.createView();
+
+ // Fill view2
+ view2.fill();
+ int view2NodeCount = view2.getNodeCount();
+ int view2EdgeCount = view2.getEdgeCount();
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty");
+ Assert.assertTrue(view2NodeCount > 0, "View2 should have nodes");
+
+ // Union of empty view with filled view should fill view1
+ view1.union(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), view2NodeCount, "View1 should have same node count as view2");
+ Assert.assertEquals(view1.getEdgeCount(), view2EdgeCount, "View1 should have same edge count as view2");
+ }
+
+ @Test
+ public void testNotOnEmptyView() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView(); // Empty view
+
+ int totalNodes = graphStore.getNodeCount();
+ int totalEdges = graphStore.getEdgeCount();
+
+ Assert.assertEquals(view.getNodeCount(), 0, "View should be empty initially");
+ Assert.assertEquals(view.getEdgeCount(), 0, "View should have no edges initially");
+
+ // NOT on empty view should fill it completely
+ view.not();
+
+ Assert.assertEquals(view.getNodeCount(), totalNodes, "View should have all nodes after NOT");
+ Assert.assertEquals(view.getEdgeCount(), totalEdges, "View should have all edges after NOT");
+
+ // Verify all elements are present
+ for (Node n : graphStore.getNodes()) {
+ Assert.assertTrue(view.containsNode((NodeImpl) n), "View should contain all nodes after NOT");
+ }
+ for (Edge e : graphStore.getEdges()) {
+ Assert.assertTrue(view.containsEdge((EdgeImpl) e), "View should contain all edges after NOT");
+ }
+ }
+
+ @Test
+ public void testIntersectionBothEmpty() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView(); // Empty
+ GraphViewImpl view2 = store.createView(); // Empty
+
+ // Both views empty
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty");
+ Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty");
+
+ // Intersection of two empty views should stay empty
+ view1.intersection(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty");
+ Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges");
+ }
+
+ @Test
+ public void testUnionBothEmpty() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view1 = store.createView(); // Empty
+ GraphViewImpl view2 = store.createView(); // Empty
+
+ // Both views empty
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should be empty");
+ Assert.assertEquals(view2.getNodeCount(), 0, "View2 should be empty");
+
+ // Union of two empty views should stay empty
+ view1.union(view2);
+
+ Assert.assertEquals(view1.getNodeCount(), 0, "View1 should still be empty");
+ Assert.assertEquals(view1.getEdgeCount(), 0, "View1 should still have no edges");
+ }
+
+ // ========== Tests for Retain Operations ==========
+
+ @Test
+ public void testRetainNodesBasic() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+ int initialNodeCount = view.getNodeCount();
+ int initialEdgeCount = view.getEdgeCount();
+
+ // Retain all nodes - should return false (no change)
+ boolean changed = view.retainNodes(graphStore.getNodes().toCollection());
+ Assert.assertFalse(changed, "Retaining all nodes should return false");
+ Assert.assertEquals(view.getNodeCount(), initialNodeCount, "Node count should not change");
+ Assert.assertEquals(view.getEdgeCount(), initialEdgeCount, "Edge count should not change");
+
+ // Retain subset of nodes
+ NodeImpl n1 = graphStore.getNode("0");
+ NodeImpl n2 = graphStore.getNode("1");
+ changed = view.retainNodes(Arrays.asList(n1, n2));
+
+ Assert.assertTrue(changed, "Retaining subset should return true");
+ Assert.assertEquals(view.getNodeCount(), 2, "Should have exactly 2 nodes");
+ Assert.assertTrue(view.containsNode(n1), "Should contain node 0");
+ Assert.assertTrue(view.containsNode(n2), "Should contain node 1");
+ }
+
+ @Test
+ public void testRetainNodesEmpty() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+
+ // Retain empty collection - should clear everything
+ boolean changed = view.retainNodes(Collections.emptyList());
+
+ Assert.assertTrue(changed, "Retaining empty list should return true");
+ Assert.assertEquals(view.getNodeCount(), 0, "Should have no nodes");
+ Assert.assertEquals(view.getEdgeCount(), 0, "Should have no edges");
+ }
+
+ @Test
+ public void testRetainEdgesBasic() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView(false, true); // Edge view only
+
+ view.fill();
+ int initialEdgeCount = view.getEdgeCount();
+
+ // Retain all edges - should return false (no change)
+ boolean changed = view.retainEdges(graphStore.getEdges().toCollection());
+ Assert.assertFalse(changed, "Retaining all edges should return false");
+ Assert.assertEquals(view.getEdgeCount(), initialEdgeCount, "Edge count should not change");
+
+ // Retain subset of edges
+ EdgeImpl e1 = graphStore.getEdge("0");
+ EdgeImpl e2 = graphStore.getEdge("1");
+ changed = view.retainEdges(Arrays.asList(e1, e2));
+
+ Assert.assertTrue(changed, "Retaining subset should return true");
+ Assert.assertEquals(view.getEdgeCount(), 2, "Should have exactly 2 edges");
+ Assert.assertTrue(view.containsEdge(e1), "Should contain edge 0");
+ Assert.assertTrue(view.containsEdge(e2), "Should contain edge 1");
+ }
+
+ @Test
+ public void testRetainEdgesEmpty() {
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView(false, true); // Edge view only
+
+ view.fill();
+
+ // Retain empty collection - should clear all edges
+ boolean changed = view.retainEdges(Collections.emptyList());
+
+ Assert.assertTrue(changed, "Retaining empty list should return true");
+ Assert.assertEquals(view.getEdgeCount(), 0, "Should have no edges");
+ }
+
+ @Test
+ public void testRetainNodesWithMutualEdges() {
+ GraphStore graphStore = GraphGenerator.generateTinyGraphStoreWithMutualEdge();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+
+ EdgeImpl e0 = graphStore.getEdge("0");
+ EdgeImpl e1 = graphStore.getEdge("1");
+ NodeImpl n1 = e0.getSource();
+ NodeImpl n2 = e0.getTarget();
+
+ // Initial state: view has mutual edges
+ Assert.assertEquals(view.mutualEdgesCount, 1, "View should have mutual count of 1");
+ Assert.assertEquals(view.getEdgeCount(), 2, "View should have 2 edges");
+
+ // Retain both nodes - mutual edges should remain
+ boolean changed = view.retainNodes(Arrays.asList(n1, n2));
+
+ Assert.assertFalse(changed, "Retaining all nodes should return false");
+ Assert.assertEquals(view.mutualEdgesCount, 1, "Mutual edges should remain");
+ Assert.assertEquals(view.getEdgeCount(), 2, "Should still have 2 edges");
+ }
+
+ @Test
+ public void testRetainEdgesWithMultipleTypes() {
+ GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView(false, true); // Edge view only
+
+ view.fill();
+
+ int type0Count = view.getEdgeCount(0);
+ int type1Count = view.getEdgeCount(1);
+ int type2Count = view.getEdgeCount(2);
+
+ Assert.assertTrue(type0Count > 0, "Should have type 0 edges");
+ Assert.assertTrue(type1Count > 0, "Should have type 1 edges");
+ Assert.assertTrue(type2Count > 0, "Should have type 2 edges");
+
+ // Collect only type 0 edges to retain
+ List type0Edges = new ArrayList<>();
+ for (Edge e : graphStore.getEdges().toArray()) {
+ if (e.getType() == 0) {
+ type0Edges.add(e);
+ }
+ }
+
+ // Retain only type 0 edges
+ boolean changed = view.retainEdges(type0Edges);
+
+ Assert.assertTrue(changed, "Should have removed edges");
+ Assert.assertEquals(view.getEdgeCount(0), type0Count, "Should still have all type 0 edges");
+ Assert.assertEquals(view.getEdgeCount(1), 0, "Should have no type 1 edges");
+ Assert.assertEquals(view.getEdgeCount(2), 0, "Should have no type 2 edges");
+ Assert.assertEquals(view.getEdgeCount(), type0Count, "Total should match type 0 count");
+ }
+
+ @Test
+ public void testRetainNodesWithMultipleEdgeTypes() {
+ GraphStore graphStore = GraphGenerator.generateSmallMultiTypeGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+
+ int initialType0Count = view.getEdgeCount(0);
+ int initialType1Count = view.getEdgeCount(1);
+
+ // Retain subset of nodes
+ List nodesToRetain = new ArrayList<>();
+ int count = 0;
+ for (Node n : graphStore.getNodes()) {
+ nodesToRetain.add(n);
+ count++;
+ if (count >= 5) {
+ break; // Keep first 5 nodes
+ }
+ }
+
+ boolean changed = view.retainNodes(nodesToRetain);
+
+ Assert.assertTrue(changed, "Should have removed nodes");
+ Assert.assertEquals(view.getNodeCount(), 5, "Should have exactly 5 nodes");
+
+ // Edge counts should have decreased but type tracking should still be correct
+ int newType0Count = view.getEdgeCount(0);
+ int newType1Count = view.getEdgeCount(1);
+
+ Assert.assertTrue(newType0Count <= initialType0Count, "Type 0 count should not increase");
+ Assert.assertTrue(newType1Count <= initialType1Count, "Type 1 count should not increase");
+ Assert.assertEquals(view.getEdgeCount(), newType0Count + newType1Count + view
+ .getEdgeCount(2), "Total edge count should match sum of types");
+ }
+
+ @Test
+ public void testRetainNodesLargeScale() {
+ // Test bulk operation performance with larger dataset
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView();
+
+ view.fill();
+ int totalNodes = view.getNodeCount();
+
+ // Retain half the nodes
+ List nodesToRetain = new ArrayList<>();
+ int count = 0;
+ for (Node n : graphStore.getNodes()) {
+ if (count % 2 == 0) {
+ nodesToRetain.add(n);
+ }
+ count++;
+ }
+
+ boolean changed = view.retainNodes(nodesToRetain);
+
+ Assert.assertTrue(changed, "Should have removed nodes");
+ Assert.assertTrue(view.getNodeCount() <= totalNodes / 2 + 1, "Should have roughly half the nodes");
+ Assert.assertTrue(view.getNodeCount() >= totalNodes / 2 - 1, "Should have roughly half the nodes");
+
+ // Verify all retained nodes are in the view
+ for (Node n : nodesToRetain) {
+ Assert.assertTrue(view.containsNode((NodeImpl) n), "Retained node should be in view");
+ }
+ }
+
+ @Test
+ public void testRetainEdgesOnlyView() {
+ // Test retain edges when nodeView=false, edgeView=true
+ GraphStore graphStore = GraphGenerator.generateSmallGraphStore();
+ GraphViewStore store = graphStore.viewStore;
+ GraphViewImpl view = store.createView(false, true);
+
+ view.fill();
+
+ List edgesToRetain = new ArrayList<>();
+ int count = 0;
+ for (Edge e : graphStore.getEdges().toArray()) {
+ edgesToRetain.add(e);
+ count++;
+ if (count >= 10) {
+ break;
+ }
+ }
+
+ boolean changed = view.retainEdges(edgesToRetain);
+
+ Assert.assertTrue(changed, "Should have removed edges");
+ Assert.assertEquals(view.getEdgeCount(), 10, "Should have exactly 10 edges");
+
+ for (Edge e : edgesToRetain) {
+ Assert.assertTrue(view.containsEdge((EdgeImpl) e), "Retained edge should be in view");
+ }
+ }
}
diff --git a/src/test/java/org/gephi/graph/impl/SerializationTest.java b/src/test/java/org/gephi/graph/impl/SerializationTest.java
index c56c346f..561a28c6 100644
--- a/src/test/java/org/gephi/graph/impl/SerializationTest.java
+++ b/src/test/java/org/gephi/graph/impl/SerializationTest.java
@@ -15,7 +15,6 @@
*/
package org.gephi.graph.impl;
-import cern.colt.bitvector.BitVector;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import it.unimi.dsi.fastutil.booleans.BooleanOpenHashSet;
import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap;
@@ -49,12 +48,14 @@
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
import org.gephi.graph.api.Configuration;
import org.gephi.graph.api.GraphModel;
@@ -315,14 +316,14 @@ public void testGraphView() throws IOException, ClassNotFoundException {
}
@Test
- public void testBitVector() throws IOException, ClassNotFoundException {
- BitVector bitVector = new BitVector(10);
+ public void testBitSet() throws IOException, ClassNotFoundException {
+ BitSet bitVector = new BitSet(10);
bitVector.set(1);
bitVector.set(4);
Serialization ser = new Serialization(null);
byte[] buf = ser.serialize(bitVector);
- BitVector l = (BitVector) ser.deserialize(buf);
+ BitSet l = (BitSet) ser.deserialize(buf);
Assert.assertEquals(bitVector, l);
}
@@ -1266,4 +1267,27 @@ public void serializeGraphModel(DataOutput out, GraphModelImpl model) throws IOE
GraphModelImpl read = ser.deserializeGraphModelWithoutVersionPrefix(dio.reset(bytes), Serialization.VERSION);
Assert.assertTrue(read.deepEquals(gm));
}
+
+ @Test
+ public void testBitVectorEqual() throws Exception {
+ Serialization ser = new Serialization();
+
+ Random random = new Random();
+ for (int i = 0; i < 20000; i += 97) {
+ BitSet bs = new BitSet(i);
+
+ int p = random.nextInt(99) + 1;
+ for (int j = 0; j < i; j++) {
+ if (random.nextInt(100) < p) {
+ bs.set(j);
+ }
+ }
+ DataInputOutput dio = new DataInputOutput();
+ ser.serializeBitSet(dio, bs);
+ byte[] bytes = dio.toByteArray();
+
+ BitSet deserializedBs = ser.deserializeBitSet(dio.reset(bytes));
+ Assert.assertEquals(bs, deserializedBs);
+ }
+ }
}