toSet();
+ /**
+ * Returns a Spliterator over the edges.
+ *
+ * Implementations return a splittable, sized, fail-fast spliterator suitable
+ * for parallel streams. When not possible, a non-splittable spliterator is
+ * returned.
+ *
+ * @return edge spliterator
+ */
+ @Override
+ default Spliterator spliterator() {
+ return ElementIterable.super.spliterator();
+ }
+
/**
* Empty edge iterable.
*/
- static final class EdgeIterableEmpty implements Iterator, EdgeIterable {
+ final class EdgeIterableEmpty implements Iterator, EdgeIterable {
@Override
public boolean hasNext() {
@@ -88,6 +104,11 @@ public Iterator iterator() {
return this;
}
+ @Override
+ public Spliterator spliterator() {
+ return Spliterators.emptySpliterator();
+ }
+
@Override
public Edge[] toArray() {
return new Edge[0];
diff --git a/src/main/java/org/gephi/graph/api/ElementIterable.java b/src/main/java/org/gephi/graph/api/ElementIterable.java
index a3ec3e27..2170f492 100644
--- a/src/main/java/org/gephi/graph/api/ElementIterable.java
+++ b/src/main/java/org/gephi/graph/api/ElementIterable.java
@@ -20,6 +20,10 @@
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
* Element iterable.
@@ -41,6 +45,25 @@ public interface ElementIterable extends Iterable {
@Override
public Iterator iterator();
+ /**
+ * Creates a new sequential stream, based on the spliterator returned.
+ *
+ * @return stream
+ */
+ default Stream stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+
+ /**
+ * Creates a new sequential and parallel stream, based on the spliterator
+ * returned.
+ *
+ * @return stream
+ */
+ default Stream parallelStream() {
+ return StreamSupport.stream(spliterator(), true);
+ }
+
/**
* Returns the iterator content as an array.
*
@@ -92,6 +115,11 @@ public Iterator iterator() {
return this;
}
+ @Override
+ public Spliterator spliterator() {
+ return Spliterators.emptySpliterator();
+ }
+
@Override
public Element[] toArray() {
return new Node[0];
diff --git a/src/main/java/org/gephi/graph/api/NodeIterable.java b/src/main/java/org/gephi/graph/api/NodeIterable.java
index 88601682..30d3b2db 100644
--- a/src/main/java/org/gephi/graph/api/NodeIterable.java
+++ b/src/main/java/org/gephi/graph/api/NodeIterable.java
@@ -20,6 +20,8 @@
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
/**
* A node iterable.
@@ -63,6 +65,20 @@ public interface NodeIterable extends ElementIterable {
@Override
public Set toSet();
+ /**
+ * Returns a Spliterator over the nodes.
+ *
+ * Implementations return a splittable, sized, fail-fast spliterator suitable
+ * for parallel streams. When not possible, a non-splittable spliterator is
+ * returned.
+ *
+ * @return node spliterator
+ */
+ @Override
+ default Spliterator spliterator() {
+ return ElementIterable.super.spliterator();
+ }
+
/**
* Empty node iterable.
*/
@@ -88,6 +104,11 @@ public Iterator iterator() {
return this;
}
+ @Override
+ public Spliterator spliterator() {
+ return Spliterators.emptySpliterator();
+ }
+
@Override
public Node[] toArray() {
return new Node[0];
diff --git a/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
index c8761bc3..d90aea76 100644
--- a/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
+++ b/src/main/java/org/gephi/graph/impl/ColumnObserverImpl.java
@@ -19,6 +19,7 @@
import cern.colt.bitvector.QuickBitVector;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
+import it.unimi.dsi.fastutil.objects.ObjectLists;
import org.gephi.graph.api.AttributeUtils;
import org.gephi.graph.api.Column;
import org.gephi.graph.api.ColumnDiff;
@@ -155,7 +156,8 @@ protected final class NodeColumnDiffImpl extends ColumnDiffImpl {
@Override
public NodeIterable getTouchedElements() {
if (!touchedElements.isEmpty()) {
- return graphStore.getNodeIterableWrapper(touchedElements.iterator(), false);
+ return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(touchedElements).iterator(),
+ () -> ObjectLists.unmodifiable(touchedElements).spliterator(), null);
}
return NodeIterable.NodeIterableEmpty.EMPTY;
@@ -167,8 +169,8 @@ protected final class EdgeColumnDiffImpl extends ColumnDiffImpl {
@Override
public EdgeIterable getTouchedElements() {
if (!touchedElements.isEmpty()) {
- return graphStore.getEdgeIterableWrapper(touchedElements.iterator(), false);
-
+ return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(touchedElements).iterator(),
+ () -> ObjectLists.unmodifiable(touchedElements).spliterator(), null);
}
return EdgeIterable.EdgeIterableEmpty.EMPTY;
}
diff --git a/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java b/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java
index 8db89823..d51e7b38 100644
--- a/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java
+++ b/src/main/java/org/gephi/graph/impl/EdgeIterableWrapper.java
@@ -16,21 +16,32 @@
package org.gephi.graph.impl;
import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.function.Supplier;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
public class EdgeIterableWrapper extends ElementIterableWrapper implements EdgeIterable {
- public EdgeIterableWrapper(Iterator iterator) {
- super(iterator);
+ public EdgeIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) {
+ super(iteratorSupplier, lock);
}
- public EdgeIterableWrapper(Iterator iterator, GraphLockImpl lock) {
- super(iterator, lock);
+ public EdgeIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) {
+ super(iteratorSupplier, spliteratorSupplier, lock);
}
@Override
public Edge[] toArray() {
- return toArray(new Edge[0]);
+ if (parallelPossible && lock != null) {
+ lock.readLock();
+ try {
+ return StreamSupport.stream(spliterator(), true).toArray(Edge[]::new);
+ } finally {
+ lock.readUnlock();
+ }
+ }
+ return StreamSupport.stream(spliterator(), parallelPossible).toArray(Edge[]::new);
}
}
diff --git a/src/main/java/org/gephi/graph/impl/EdgeStore.java b/src/main/java/org/gephi/graph/impl/EdgeStore.java
index 7328bb01..497c7a00 100644
--- a/src/main/java/org/gephi/graph/impl/EdgeStore.java
+++ b/src/main/java/org/gephi/graph/impl/EdgeStore.java
@@ -25,10 +25,16 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
import org.gephi.graph.api.Node;
@@ -348,6 +354,49 @@ public EdgeStoreIterator iterator() {
return new EdgeStoreIterator();
}
+ @Override
+ public Spliterator spliterator() {
+ int end = blocksCount;
+ return new EdgeSpliterator(0, end);
+ }
+
+ @Override
+ public Stream stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+
+ @Override
+ public Stream parallelStream() {
+ return StreamSupport.stream(spliterator(), true);
+ }
+
+ public Spliterator spliteratorUndirected() {
+ int end = blocksCount;
+ return new FilteredSizedEdgeSpliterator(0, end, e -> !isUndirectedToIgnore(e), undirectedSize());
+ }
+
+ public Spliterator spliteratorType(int type, boolean undirected) {
+ int end = blocksCount;
+ return new FilteredSizedEdgeSpliterator(0, end,
+ e -> e.getType() == type && (!undirected || !isUndirectedToIgnore(e)),
+ undirected ? undirectedSize(type) : size(type));
+ }
+
+ public Spliterator spliteratorSelfLoop() {
+ int end = blocksCount;
+ return new FilteredEdgeSpliterator(0, end, EdgeImpl::isSelfLoop);
+ }
+
+ protected Spliterator newFilteredSpliterator(java.util.function.Predicate filter) {
+ int end = blocksCount;
+ return new FilteredEdgeSpliterator(0, end, filter);
+ }
+
+ protected Spliterator newFilteredSizedSpliterator(java.util.function.Predicate filter, int size) {
+ int end = blocksCount;
+ return new FilteredSizedEdgeSpliterator(0, end, filter, size);
+ }
+
public EdgeStoreIterator iteratorUndirected() {
return new UndirectedEdgeStoreIterator();
}
@@ -1954,4 +2003,202 @@ public void remove() {
EdgeStore.this.remove(pointer);
}
}
+
+ private class EdgeSpliterator implements Spliterator {
+
+ protected final int endBlockExclusive;
+ protected int blockIndex;
+ protected int indexInBlock;
+ protected EdgeImpl[] currentArray;
+ protected int currentLength;
+ protected final int expectedVersion;
+ protected int totalSize;
+ protected int consumed;
+
+ EdgeSpliterator(int startBlock, int endBlockExclusive) {
+ this(startBlock, endBlockExclusive, EdgeStore.this.size());
+ }
+
+ EdgeSpliterator(int startBlock, int endBlockExclusive, int totalSize) {
+ this.blockIndex = startBlock;
+ this.endBlockExclusive = endBlockExclusive;
+ this.expectedVersion = version != null ? version.getEdgeVersion() : 0;
+ this.consumed = 0;
+
+ // Use the total store size for the root spliterator (covering all blocks)
+ if (startBlock == 0 && endBlockExclusive == blocksCount) {
+ this.totalSize = totalSize;
+ } else {
+ // For split spliterators, compute proportionally
+ this.totalSize = computeExactSize(startBlock, endBlockExclusive);
+ }
+
+ if (startBlock < endBlockExclusive) {
+ EdgeStore.EdgeBlock b = blocks[startBlock];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ private int computeExactSize(int start, int end) {
+ int sum = 0;
+ for (int i = start; i < end; i++) {
+ EdgeStore.EdgeBlock b = blocks[i];
+ if (b != null) {
+ // Exact count: nodeLength minus garbageLength
+ sum += (b.nodeLength - b.garbageLength);
+ }
+ }
+ return sum;
+ }
+
+ protected void advanceBlock() {
+ blockIndex++;
+ if (blockIndex < endBlockExclusive) {
+ EdgeStore.EdgeBlock b = blocks[blockIndex];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ protected void checkForComodification() {
+ if (version != null && expectedVersion != version.getEdgeVersion()) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super Edge> action) {
+ checkForComodification();
+ while (currentArray != null) {
+ while (indexInBlock < currentLength) {
+ EdgeImpl n = currentArray[indexInBlock++];
+ if (n != null) {
+ consumed++;
+ action.accept(n);
+ return true;
+ }
+ }
+ advanceBlock();
+ }
+ return false;
+ }
+
+ protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) {
+ return new EdgeSpliterator(startBlock, endBlockExclusive);
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ // Only split at block boundaries to preserve encounter order
+ if (indexInBlock != 0) {
+ return null;
+ }
+
+ int currentPos = blockIndex;
+ int remainingBlocks = endBlockExclusive - currentPos;
+
+ if (remainingBlocks <= 1) {
+ return null;
+ }
+
+ int mid = currentPos + remainingBlocks / 2;
+
+ // Create left half
+ EdgeSpliterator left = createSplit(currentPos, mid);
+
+ // Update this spliterator to become the right half
+ blockIndex = mid;
+ if (mid < endBlockExclusive) {
+ EdgeStore.EdgeBlock b = blocks[mid];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+
+ // Update this spliterator size
+ this.totalSize = totalSize - left.totalSize;
+
+ return left;
+ }
+
+ @Override
+ public long estimateSize() {
+ // Use the exact totalSize minus what we've consumed
+ long remaining = totalSize - consumed;
+ return remaining < 0 ? 0 : remaining;
+ }
+
+ @Override
+ public int characteristics() {
+ return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED;
+ }
+ }
+
+ private class FilteredSizedEdgeSpliterator extends EdgeSpliterator {
+
+ protected final Predicate filter;
+
+ FilteredSizedEdgeSpliterator(int startBlock, int endBlockExclusive, Predicate filter, int totalSize) {
+ super(startBlock, endBlockExclusive, totalSize);
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super Edge> action) {
+ checkForComodification();
+ while (currentArray != null) {
+ while (indexInBlock < currentLength) {
+ EdgeImpl n = currentArray[indexInBlock++];
+ if (n != null && filter.test(n)) {
+ consumed++;
+ action.accept(n);
+ return true;
+ }
+ }
+ advanceBlock();
+ }
+ return false;
+ }
+
+ protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) {
+ return new FilteredSizedEdgeSpliterator(startBlock, endBlockExclusive, filter, totalSize);
+ }
+
+ @Override
+ public int characteristics() {
+ return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED;
+ }
+ }
+
+ private final class FilteredEdgeSpliterator extends FilteredSizedEdgeSpliterator {
+
+ FilteredEdgeSpliterator(int startBlock, int endBlockExclusive, Predicate filter) {
+ super(startBlock, endBlockExclusive, filter, EdgeStore.this.size());
+ }
+
+ protected EdgeSpliterator createSplit(int startBlock, int endBlockExclusive) {
+ return new FilteredEdgeSpliterator(startBlock, endBlockExclusive, filter);
+ }
+
+ @Override
+ public int characteristics() {
+ return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL;
+ }
+ }
+
}
diff --git a/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java b/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java
index 596fa89b..3f2a7fcd 100644
--- a/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java
+++ b/src/main/java/org/gephi/graph/impl/ElementIterableWrapper.java
@@ -15,55 +15,84 @@
*/
package org.gephi.graph.impl;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Element;
import org.gephi.graph.api.ElementIterable;
public abstract class ElementIterableWrapper implements ElementIterable {
- protected final Iterator iterator;
+ protected final Supplier> iteratorSupplier;
+ protected final Supplier> spliteratorSupplier;
protected final GraphLockImpl lock;
+ protected final boolean parallelPossible;
- public ElementIterableWrapper(Iterator iterator) {
- this(iterator, null);
+ public ElementIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) {
+ this.iteratorSupplier = iteratorSupplier;
+ this.spliteratorSupplier = () -> Spliterators
+ .spliteratorUnknownSize(iteratorSupplier.get(), Spliterator.ORDERED | Spliterator.NONNULL);
+ this.lock = lock;
+ this.parallelPossible = false;
}
- public ElementIterableWrapper(Iterator iterator, GraphLockImpl lock) {
- this.iterator = iterator;
+ public ElementIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) {
+ this.iteratorSupplier = iteratorSupplier;
+ this.spliteratorSupplier = spliteratorSupplier;
this.lock = lock;
+ this.parallelPossible = true;
}
@Override
public Iterator iterator() {
- return iterator;
+ return iteratorSupplier.get();
}
- protected T[] toArray(T[] a) {
- // TODO This can be improved
- return toCollection().toArray(a);
+ @Override
+ public Spliterator spliterator() {
+ return spliteratorSupplier.get();
}
+ @Override
+ public Stream parallelStream() {
+ if (!parallelPossible) {
+ throw new UnsupportedOperationException("Parallel stream not supported for this operation.");
+ }
+ return ElementIterable.super.parallelStream();
+ }
+
+ public abstract T[] toArray();
+
@Override
public Collection toCollection() {
- List list = new ArrayList<>();
- while (iterator.hasNext()) {
- list.add(iterator.next());
+ if (parallelPossible && lock != null) {
+ lock.readLock();
+ try {
+ return StreamSupport.stream(spliterator(), true).collect(Collectors.toList());
+ } finally {
+ lock.readUnlock();
+ }
}
- return list;
+ return StreamSupport.stream(spliterator(), parallelPossible).collect(Collectors.toList());
}
@Override
public Set toSet() {
- Set set = new HashSet<>();
- while (iterator.hasNext()) {
- set.add(iterator.next());
+ if (parallelPossible && lock != null) {
+ lock.readLock();
+ try {
+ return StreamSupport.stream(spliterator(), true).collect(Collectors.toSet());
+ } finally {
+ lock.readUnlock();
+ }
}
- return set;
+ return StreamSupport.stream(spliterator(), parallelPossible).collect(Collectors.toSet());
}
@Override
diff --git a/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java b/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java
index 9737c4cb..6a3bef35 100644
--- a/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java
+++ b/src/main/java/org/gephi/graph/impl/GraphObserverImpl.java
@@ -17,7 +17,7 @@
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
-import java.util.Collections;
+import it.unimi.dsi.fastutil.objects.ObjectLists;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
import org.gephi.graph.api.Graph;
@@ -196,7 +196,8 @@ public GraphDiffImpl() {
@Override
public NodeIterable getAddedNodes() {
if (!addedNodes.isEmpty()) {
- return graphStore.getNodeIterableWrapper(Collections.unmodifiableList(addedNodes).iterator(), false);
+ return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(addedNodes).iterator(),
+ () -> ObjectLists.unmodifiable(addedNodes).spliterator(), null);
}
return NodeIterable.EMPTY;
}
@@ -204,7 +205,8 @@ public NodeIterable getAddedNodes() {
@Override
public NodeIterable getRemovedNodes() {
if (!removedNodes.isEmpty()) {
- return graphStore.getNodeIterableWrapper(Collections.unmodifiableList(removedNodes).iterator(), false);
+ return new NodeIterableWrapper(() -> ObjectLists.unmodifiable(removedNodes).iterator(),
+ () -> ObjectLists.unmodifiable(removedNodes).spliterator(), null);
}
return NodeIterable.EMPTY;
}
@@ -212,7 +214,8 @@ public NodeIterable getRemovedNodes() {
@Override
public EdgeIterable getAddedEdges() {
if (!addedEdges.isEmpty()) {
- return graphStore.getEdgeIterableWrapper(Collections.unmodifiableList(addedEdges).iterator(), false);
+ return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(addedEdges).iterator(),
+ () -> ObjectLists.unmodifiable(addedEdges).spliterator(), null);
}
return EdgeIterable.EMPTY;
}
@@ -220,7 +223,8 @@ public EdgeIterable getAddedEdges() {
@Override
public EdgeIterable getRemovedEdges() {
if (!removedEdges.isEmpty()) {
- return graphStore.getEdgeIterableWrapper(Collections.unmodifiableList(removedEdges).iterator(), false);
+ return new EdgeIterableWrapper(() -> ObjectLists.unmodifiable(removedEdges).iterator(),
+ () -> ObjectLists.unmodifiable(removedEdges).spliterator(), null);
}
return EdgeIterable.EMPTY;
}
diff --git a/src/main/java/org/gephi/graph/impl/GraphStore.java b/src/main/java/org/gephi/graph/impl/GraphStore.java
index 3d8b015c..0bb033a1 100644
--- a/src/main/java/org/gephi/graph/impl/GraphStore.java
+++ b/src/main/java/org/gephi/graph/impl/GraphStore.java
@@ -19,12 +19,27 @@
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-
-import org.gephi.graph.api.*;
+import org.gephi.graph.api.Configuration;
+import org.gephi.graph.api.DirectedGraph;
+import org.gephi.graph.api.DirectedSubgraph;
+import org.gephi.graph.api.Edge;
+import org.gephi.graph.api.EdgeIterable;
+import org.gephi.graph.api.ElementIterable;
+import org.gephi.graph.api.Graph;
+import org.gephi.graph.api.GraphModel;
+import org.gephi.graph.api.GraphView;
+import org.gephi.graph.api.Interval;
+import org.gephi.graph.api.Node;
+import org.gephi.graph.api.NodeIterable;
+import org.gephi.graph.api.Origin;
+import org.gephi.graph.api.SpatialIndex;
+import org.gephi.graph.api.Subgraph;
+import org.gephi.graph.api.Table;
+import org.gephi.graph.api.TimeFormat;
+import org.gephi.graph.api.TimeRepresentation;
import org.gephi.graph.api.types.IntervalSet;
import org.gephi.graph.api.types.TimestampSet;
@@ -248,12 +263,13 @@ protected ElementIterable> getElements(Table table) {
@Override
public EdgeIterable getEdges(int type) {
- return new EdgeIterableWrapper(edgeStore.iteratorType(type, false));
+ return new EdgeIterableWrapper(() -> edgeStore.iteratorType(type, false),
+ () -> edgeStore.spliteratorType(type, false), getAutoLock());
}
@Override
public EdgeIterable getSelfLoops() {
- return new EdgeIterableWrapper(edgeStore.iteratorSelfLoop());
+ return new EdgeIterableWrapper(edgeStore::iteratorSelfLoop, edgeStore::spliteratorSelfLoop, getAutoLock());
}
@Override
@@ -261,8 +277,7 @@ public boolean removeNode(final Node node) {
autoWriteLock();
try {
nodeStore.checkNonNullNodeObject(node);
- for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator((NodeImpl) node); edgeIterator
- .hasNext();) {
+ for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
edgeIterator.next();
edgeIterator.remove();
}
@@ -288,8 +303,7 @@ public boolean removeAllNodes(Collection extends Node> nodes) {
try {
for (Node node : nodes) {
nodeStore.checkNonNullNodeObject(node);
- for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator((NodeImpl) node); edgeIterator
- .hasNext();) {
+ for (EdgeStore.EdgeInOutIterator edgeIterator = edgeStore.edgeIterator(node); edgeIterator.hasNext();) {
edgeIterator.next();
edgeIterator.remove();
}
@@ -370,11 +384,7 @@ public Edge getEdge(final Node node1, final Node node2, final int type) {
@Override
public EdgeIterable getEdges(Node node1, Node node2, int type) {
- Iterator itr = edgeStore.getAll(node1, node2, type, false);
- if (itr != null) {
- return new EdgeIterableWrapper(itr);
- }
- return EdgeIterable.EMPTY;
+ return new EdgeIterableWrapper(() -> edgeStore.getAll(node1, node2, type, false), getAutoLock());
}
@Override
@@ -389,71 +399,67 @@ public Edge getEdge(final Node node1, final Node node2) {
@Override
public EdgeIterable getEdges(Node node1, Node node2) {
- Iterator itr = edgeStore.getAll(node1, node2, false);
- if (itr != null) {
- return new EdgeIterableWrapper(itr);
- }
- return EdgeIterable.EMPTY;
+ return new EdgeIterableWrapper(() -> edgeStore.getAll(node1, node2, false), getAutoLock());
}
@Override
public NodeIterable getNeighbors(final Node node) {
- return new NodeIterableWrapper(edgeStore.neighborIterator(node));
+ return new NodeIterableWrapper(() -> edgeStore.neighborIterator(node), getAutoLock());
}
@Override
public NodeIterable getNeighbors(final Node node, final int type) {
- return new NodeIterableWrapper(edgeStore.neighborIterator(node, type));
+ return new NodeIterableWrapper(() -> edgeStore.neighborIterator(node, type), getAutoLock());
}
@Override
public NodeIterable getPredecessors(final Node node) {
- return new NodeIterableWrapper(edgeStore.neighborInIterator(node));
+ return new NodeIterableWrapper(() -> edgeStore.neighborInIterator(node), getAutoLock());
}
@Override
public NodeIterable getPredecessors(final Node node, final int type) {
- return new NodeIterableWrapper(edgeStore.neighborInIterator(node, type));
+ return new NodeIterableWrapper(() -> edgeStore.neighborInIterator(node, type), getAutoLock());
}
@Override
public NodeIterable getSuccessors(final Node node) {
- return new NodeIterableWrapper(edgeStore.neighborOutIterator(node));
+ return new NodeIterableWrapper(() -> edgeStore.neighborOutIterator(node), getAutoLock());
}
@Override
public NodeIterable getSuccessors(final Node node, final int type) {
- return new NodeIterableWrapper(edgeStore.neighborOutIterator(node, type));
+ return new NodeIterableWrapper(() -> edgeStore.neighborOutIterator(node, type), getAutoLock());
}
@Override
public EdgeIterable getEdges(final Node node) {
- return new EdgeIterableWrapper(edgeStore.edgeIterator(node));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node), getAutoLock());
}
@Override
public EdgeIterable getEdges(final Node node, final int type) {
- return new EdgeIterableWrapper(edgeStore.edgeIterator(node, type));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeIterator(node, type), getAutoLock());
}
@Override
public EdgeIterable getInEdges(final Node node) {
- return new EdgeIterableWrapper(edgeStore.edgeInIterator(node));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeInIterator(node), getAutoLock());
}
@Override
public EdgeIterable getInEdges(final Node node, final int type) {
- return new EdgeIterableWrapper(edgeStore.edgeInIterator(node, type));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeInIterator(node, type), getAutoLock());
}
@Override
public EdgeIterable getOutEdges(final Node node) {
- return new EdgeIterableWrapper(edgeStore.edgeOutIterator(node));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeOutIterator(node), getAutoLock());
}
@Override
public EdgeIterable getOutEdges(final Node node, final int type) {
- return new EdgeIterableWrapper(edgeStore.edgeOutIterator(node, type));
+ return new EdgeIterableWrapper(() -> edgeStore.edgeOutIterator(node, type), getAutoLock());
}
@Override
@@ -833,20 +839,8 @@ protected void destroyGraphObserver(GraphObserverImpl observer) {
}
}
- protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator) {
- return getEdgeIterableWrapper(edgeIterator, true);
- }
-
- protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator) {
- return getNodeIterableWrapper(nodeIterator, true);
- }
-
- protected EdgeIterableWrapper getEdgeIterableWrapper(Iterator edgeIterator, boolean blocking) {
- return new EdgeIterableWrapper(edgeIterator, (blocking && configuration.isEnableAutoLocking()) ? lock : null);
- }
-
- protected NodeIterableWrapper getNodeIterableWrapper(Iterator nodeIterator, boolean blocking) {
- return new NodeIterableWrapper(nodeIterator, (blocking && configuration.isEnableAutoLocking()) ? lock : null);
+ protected GraphLockImpl getAutoLock() {
+ return configuration.isEnableAutoLocking() ? lock : null;
}
public int deepHashCode() {
diff --git a/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java b/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java
index e57632a2..02a4e0f4 100644
--- a/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java
+++ b/src/main/java/org/gephi/graph/impl/GraphStoreConfiguration.java
@@ -35,9 +35,9 @@ public final class GraphStoreConfiguration {
public static final boolean DEFAULT_ENABLE_EDGE_WEIGHT_COLUMN = true;
public static final boolean DEFAULT_ENABLE_PARALLEL_EDGES_SAME_TYPE = true;
// NodeStore
- public final static int NODESTORE_BLOCK_SIZE = 5000;
- public final static int NODESTORE_DEFAULT_BLOCKS = 10;
- public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 1000;
+ public final static int NODESTORE_BLOCK_SIZE = 8192;
+ public final static int NODESTORE_DEFAULT_BLOCKS = 5;
+ public static final int NODESTORE_DEFAULT_DICTIONARY_SIZE = 8192;
public final static float NODESTORE_DICTIONARY_LOAD_FACTOR = .7f;
// EdgeStore
public static final int EDGESTORE_BLOCK_SIZE = 8192;
diff --git a/src/main/java/org/gephi/graph/impl/GraphVersion.java b/src/main/java/org/gephi/graph/impl/GraphVersion.java
index d436325e..fcb717e4 100644
--- a/src/main/java/org/gephi/graph/impl/GraphVersion.java
+++ b/src/main/java/org/gephi/graph/impl/GraphVersion.java
@@ -45,6 +45,14 @@ public int incrementAndGetEdgeVersion() {
return edgeVersion;
}
+ public int getNodeVersion() {
+ return nodeVersion;
+ }
+
+ public int getEdgeVersion() {
+ return edgeVersion;
+ }
+
private void handleNodeReset() {
if (graph != null) {
if (graph.getView().isMainView()) {
diff --git a/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java
index 79971b30..741016c5 100644
--- a/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java
+++ b/src/main/java/org/gephi/graph/impl/GraphViewDecorator.java
@@ -18,6 +18,9 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.ConcurrentModificationException;
+import java.util.function.Consumer;
import org.gephi.graph.api.DirectedSubgraph;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
@@ -60,8 +63,9 @@ public Edge getEdge(Node node1, Node node2) {
@Override
public EdgeIterable getEdges(Node node1, Node node2) {
- return graphStore
- .getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.getAll(node1, node2, undirected)));
+ return new EdgeIterableWrapper(
+ () -> new EdgeViewIterator(graphStore.edgeStore.getAll(node1, node2, undirected)),
+ graphStore.getAutoLock());
}
@Override
@@ -80,8 +84,9 @@ public Edge getEdge(Node node1, Node node2, int type) {
@Override
public EdgeIterable getEdges(Node node1, Node node2, int type) {
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(
- graphStore.edgeStore.getAll(node1, node2, type, undirected)));
+ return new EdgeIterableWrapper(
+ () -> new EdgeViewIterator(graphStore.edgeStore.getAll(node1, node2, type, undirected)),
+ graphStore.getAutoLock());
}
@Override
@@ -101,54 +106,61 @@ public Edge getMutualEdge(Edge e) {
@Override
public NodeIterable getPredecessors(Node node) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node))));
+ return new NodeIterableWrapper(() -> new NeighborsIterator((NodeImpl) node,
+ new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node))), graphStore.getAutoLock());
}
@Override
public NodeIterable getPredecessors(Node node, int type) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type))));
+ return new NodeIterableWrapper(
+ () -> new NeighborsIterator((NodeImpl) node,
+ new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type))),
+ graphStore.getAutoLock());
}
@Override
public NodeIterable getSuccessors(Node node) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node))));
+ return new NodeIterableWrapper(() -> new NeighborsIterator((NodeImpl) node,
+ new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node))), graphStore.getAutoLock());
}
@Override
public NodeIterable getSuccessors(Node node, int type) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type))));
+ return new NodeIterableWrapper(
+ () -> new NeighborsIterator((NodeImpl) node,
+ new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type))),
+ graphStore.getAutoLock());
}
@Override
public EdgeIterable getInEdges(Node node) {
checkValidInViewNodeObject(node);
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node)),
+ graphStore.getAutoLock());
}
@Override
public EdgeIterable getInEdges(Node node, int type) {
checkValidInViewNodeObject(node);
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeInIterator(node, type)),
+ graphStore.getAutoLock());
}
@Override
public EdgeIterable getOutEdges(Node node) {
checkValidInViewNodeObject(node);
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node)),
+ graphStore.getAutoLock());
}
@Override
public EdgeIterable getOutEdges(Node node, int type) {
checkValidInViewNodeObject(node);
- return graphStore
- .getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeOutIterator(node, type)),
+ graphStore.getAutoLock());
}
@Override
@@ -372,51 +384,78 @@ public boolean hasEdge(final Object id) {
@Override
public NodeIterable getNodes() {
- return graphStore.getNodeIterableWrapper(new NodeViewIterator(graphStore.nodeStore.iterator()));
+ return new NodeIterableWrapper(() -> new NodeViewIterator(graphStore.nodeStore.iterator()),
+ NodeViewSpliterator::new, graphStore.getAutoLock());
}
@Override
public EdgeIterable getEdges() {
if (undirected) {
- return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(graphStore.edgeStore.iterator()));
+ return new EdgeIterableWrapper(() -> new UndirectedEdgeViewIterator(graphStore.edgeStore.iterator()),
+ () -> graphStore.edgeStore
+ .newFilteredSizedSpliterator(e -> view.containsEdge(e) && !isUndirectedToIgnore(e), view
+ .getUndirectedEdgeCount()),
+ graphStore.getAutoLock());
} else {
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.iterator()));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.iterator()),
+ () -> graphStore.edgeStore.newFilteredSizedSpliterator(view::containsEdge, view.getEdgeCount()),
+ graphStore.getAutoLock());
}
}
@Override
public EdgeIterable getEdges(int type) {
- return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(
- graphStore.edgeStore.iteratorType(type, undirected)));
+ if (undirected) {
+ return new EdgeIterableWrapper(
+ () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.iteratorType(type, undirected)),
+ () -> graphStore.edgeStore.newFilteredSizedSpliterator(e -> e.getType() == type && view
+ .containsEdge(e) && !isUndirectedToIgnore(e), view.getUndirectedEdgeCount(type)),
+ graphStore.getAutoLock());
+ } else {
+ return new EdgeIterableWrapper(
+ () -> new EdgeViewIterator(graphStore.edgeStore.iteratorType(type, undirected)),
+ () -> graphStore.edgeStore
+ .newFilteredSizedSpliterator(e -> e.getType() == type && view.containsEdge(e), view
+ .getEdgeCount(type)),
+ graphStore.getAutoLock());
+ }
}
@Override
public EdgeIterable getSelfLoops() {
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.iteratorSelfLoop()));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.iteratorSelfLoop()),
+ () -> graphStore.edgeStore.newFilteredSpliterator(e -> e.isSelfLoop() && view.containsEdge(e)),
+ graphStore.getAutoLock());
}
@Override
public NodeIterable getNeighbors(Node node) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node))));
+ return new NodeIterableWrapper(
+ () -> new NeighborsIterator((NodeImpl) node,
+ new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node))),
+ graphStore.getAutoLock());
}
@Override
public NodeIterable getNeighbors(Node node, int type) {
checkValidInViewNodeObject(node);
- return graphStore.getNodeIterableWrapper(new NeighborsIterator((NodeImpl) node,
- new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type))));
+ return new NodeIterableWrapper(
+ () -> new NeighborsIterator((NodeImpl) node,
+ new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type))),
+ graphStore.getAutoLock());
}
@Override
public EdgeIterable getEdges(Node node) {
checkValidInViewNodeObject(node);
if (undirected) {
- return graphStore
- .getEdgeIterableWrapper(new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node)));
+ return new EdgeIterableWrapper(
+ () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node)),
+ graphStore.getAutoLock());
} else {
- return graphStore.getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node)),
+ graphStore.getAutoLock());
}
}
@@ -424,13 +463,13 @@ public EdgeIterable getEdges(Node node) {
public EdgeIterable getEdges(Node node, int type) {
checkValidInViewNodeObject(node);
if (undirected) {
- return graphStore.getEdgeIterableWrapper(new UndirectedEdgeViewIterator(
- graphStore.edgeStore.edgeIterator(node, type)));
+ return new EdgeIterableWrapper(
+ () -> new UndirectedEdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type)),
+ graphStore.getAutoLock());
} else {
- return graphStore
- .getEdgeIterableWrapper(new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type)));
+ return new EdgeIterableWrapper(() -> new EdgeViewIterator(graphStore.edgeStore.edgeIterator(node, type)),
+ graphStore.getAutoLock());
}
-
}
@Override
@@ -560,7 +599,7 @@ public void clearEdges(Node node) {
graphStore.autoWriteLock();
try {
EdgeStore.EdgeInOutIterator itr = graphStore.edgeStore.edgeIterator(node);
- for (; itr.hasNext();) {
+ while (itr.hasNext()) {
EdgeImpl edge = itr.next();
view.removeEdge(edge);
}
@@ -574,7 +613,7 @@ public void clearEdges(Node node, int type) {
graphStore.autoWriteLock();
try {
EdgeStore.EdgeTypeInOutIterator itr = graphStore.edgeStore.edgeIterator(node, type);
- for (; itr.hasNext();) {
+ while (itr.hasNext()) {
EdgeImpl edge = itr.next();
view.removeEdge(edge);
}
@@ -832,8 +871,9 @@ public NodeIterable getNodesInArea(Rect2D rect) {
if (graphStore.spatialIndex == null) {
throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)");
}
- Iterator iterator = graphStore.spatialIndex.getNodesInArea(rect).iterator();
- return new NodeIterableWrapper(new NodeViewIterator(iterator), graphStore.spatialIndex.nodesTree.lock);
+ return new NodeIterableWrapper(
+ () -> new NodeViewIterator(graphStore.spatialIndex.getNodesInArea(rect).iterator()),
+ graphStore.spatialIndex.nodesTree.lock);
}
@Override
@@ -841,8 +881,9 @@ public EdgeIterable getEdgesInArea(Rect2D rect) {
if (graphStore.spatialIndex == null) {
throw new UnsupportedOperationException("Spatial index is disabled (from Configuration)");
}
- Iterator iterator = graphStore.spatialIndex.getEdgesInArea(rect).iterator();
- return new EdgeIterableWrapper(new EdgeViewIterator(iterator), graphStore.spatialIndex.nodesTree.lock);
+ return new EdgeIterableWrapper(
+ () -> new EdgeViewIterator(graphStore.spatialIndex.getEdgesInArea(rect).iterator()),
+ graphStore.spatialIndex.nodesTree.lock);
}
@Override
@@ -885,6 +926,155 @@ public Rect2D getBoundaries() {
}
}
+ private final class NodeViewSpliterator implements Spliterator {
+
+ private final int endBlockExclusive;
+ private int blockIndex;
+ private int indexInBlock;
+ private NodeImpl[] currentArray;
+ private int currentLength;
+ private final int expectedVersion;
+ private int totalSize;
+ private int consumed;
+
+ NodeViewSpliterator() {
+ this(0, graphStore.nodeStore.blocksCount);
+ }
+
+ NodeViewSpliterator(int startBlock, int endBlockExclusive) {
+ this.blockIndex = startBlock;
+ this.endBlockExclusive = endBlockExclusive;
+ this.expectedVersion = graphStore.version != null ? graphStore.version.getNodeVersion() : 0;
+ this.consumed = 0;
+
+ // Use the view's node count for exact sizing
+ // Use the total store size for the root spliterator (covering all blocks)
+ if (startBlock == 0 && endBlockExclusive == graphStore.nodeStore.blocksCount) {
+ this.totalSize = view.getNodeCount();
+ } else {
+ // For split spliterators, compute proportionally
+ this.totalSize = computeSizeEstimate(startBlock, endBlockExclusive);
+ }
+
+ if (startBlock < endBlockExclusive) {
+ NodeStore.NodeBlock b = graphStore.nodeStore.blocks[startBlock];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ private void advanceBlock() {
+ blockIndex++;
+ if (blockIndex < endBlockExclusive) {
+ NodeStore.NodeBlock b = graphStore.nodeStore.blocks[blockIndex];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ private void checkForComodification() {
+ if (graphStore.version != null && expectedVersion != graphStore.version.getNodeVersion()) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ private int computeSizeEstimate(int start, int end) {
+ int sum = 0;
+ for (int i = start; i < end; i++) {
+ NodeStore.NodeBlock b = graphStore.nodeStore.blocks[i];
+ if (b != null) {
+ // Exact count: nodeLength minus garbageLength
+ sum += (b.nodeLength - b.garbageLength);
+ }
+ }
+ if (sum > 0) {
+ // Scale by view ratio to estimate number of nodes in view
+ double viewRatio = (double) view.getNodeCount() / graphStore.nodeStore.size;
+ sum = (int) Math.round(sum * viewRatio);
+ }
+ return sum;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super Node> action) {
+ checkForComodification();
+ while (currentArray != null) {
+ while (indexInBlock < currentLength) {
+ NodeImpl n = currentArray[indexInBlock++];
+ if (n != null && view.containsNode(n)) {
+ consumed++;
+ action.accept(n);
+ return true;
+ }
+ }
+ advanceBlock();
+ }
+ return false;
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ // Only split at block boundaries to preserve encounter order
+ if (indexInBlock != 0) {
+ return null;
+ }
+
+ int currentPos = blockIndex;
+ int remainingBlocks = endBlockExclusive - currentPos;
+
+ if (remainingBlocks <= 1) {
+ return null;
+ }
+
+ int mid = currentPos + remainingBlocks / 2;
+
+ // Create left half
+ NodeViewSpliterator left = new NodeViewSpliterator(currentPos, mid);
+
+ // Update this spliterator to become the right half
+ blockIndex = mid;
+ if (mid < endBlockExclusive) {
+ NodeStore.NodeBlock b = graphStore.nodeStore.blocks[mid];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+
+ // Update this spliterator size
+ this.totalSize = Math.max(0, totalSize - left.totalSize);
+
+ return left;
+ }
+
+ @Override
+ public long estimateSize() {
+ // Use the exact view size minus what we've consumed
+ long remaining = totalSize - consumed;
+ return remaining < 0 ? 0 : remaining;
+ }
+
+ @Override
+ public int characteristics() {
+ // SIZED because we know the exact count from view.getNodeCount()
+ // But not SUBSIZED because splits can't guarantee exact size distribution
+ return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED;
+ }
+ }
+
protected final class NodeViewIterator implements Iterator {
private final Iterator nodeIterator;
@@ -989,7 +1179,7 @@ public void remove() {
}
}
- protected class NeighborsIterator implements Iterator {
+ protected static class NeighborsIterator implements Iterator {
protected final NodeImpl node;
protected final Iterator itr;
diff --git a/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java b/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java
index d8cb4c79..dab1e7b9 100644
--- a/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java
+++ b/src/main/java/org/gephi/graph/impl/NodeIterableWrapper.java
@@ -16,21 +16,32 @@
package org.gephi.graph.impl;
import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.function.Supplier;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.NodeIterable;
public class NodeIterableWrapper extends ElementIterableWrapper implements NodeIterable {
- public NodeIterableWrapper(Iterator iterator) {
- super(iterator);
+ public NodeIterableWrapper(Supplier> iteratorSupplier, GraphLockImpl lock) {
+ super(iteratorSupplier, lock);
}
- public NodeIterableWrapper(Iterator iterator, GraphLockImpl lock) {
- super(iterator, lock);
+ public NodeIterableWrapper(Supplier> iteratorSupplier, Supplier> spliteratorSupplier, GraphLockImpl lock) {
+ super(iteratorSupplier, spliteratorSupplier, lock);
}
@Override
public Node[] toArray() {
- return toArray(new Node[0]);
+ if (parallelPossible && lock != null) {
+ lock.readLock();
+ try {
+ return StreamSupport.stream(spliterator(), true).toArray(Node[]::new);
+ } finally {
+ lock.readUnlock();
+ }
+ }
+ return StreamSupport.stream(spliterator(), parallelPossible).toArray(Node[]::new);
}
}
diff --git a/src/main/java/org/gephi/graph/impl/NodeStore.java b/src/main/java/org/gephi/graph/impl/NodeStore.java
index 709deb31..0ec778c9 100644
--- a/src/main/java/org/gephi/graph/impl/NodeStore.java
+++ b/src/main/java/org/gephi/graph/impl/NodeStore.java
@@ -21,10 +21,15 @@
import it.unimi.dsi.fastutil.objects.ObjectSet;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.NodeIterable;
@@ -46,7 +51,7 @@ public class NodeStore implements Collection, NodeIterable {
protected int garbageSize;
protected int blocksCount;
protected int currentBlockIndex;
- protected NodeBlock blocks[];
+ protected NodeBlock[] blocks;
protected NodeBlock currentBlock;
protected Object2IntOpenHashMap dictionary;
@@ -174,6 +179,22 @@ public NodeStoreIterator iterator() {
return new NodeStoreIterator();
}
+ @Override
+ public Spliterator spliterator() {
+ int end = blocksCount;
+ return new NodeSpliterator(0, end);
+ }
+
+ @Override
+ public Stream stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+
+ @Override
+ public Stream parallelStream() {
+ return StreamSupport.stream(spliterator(), true);
+ }
+
@Override
public NodeImpl[] toArray() {
readLock();
@@ -675,4 +696,141 @@ public void remove() {
NodeStore.this.remove(pointer);
}
}
+
+ private final class NodeSpliterator implements Spliterator {
+
+ private final int endBlockExclusive;
+ private int blockIndex;
+ private int indexInBlock;
+ private NodeImpl[] currentArray;
+ private int currentLength;
+ private final int expectedVersion;
+ private int totalSize;
+ private int consumed;
+
+ NodeSpliterator(int startBlock, int endBlockExclusive) {
+ this.blockIndex = startBlock;
+ this.endBlockExclusive = endBlockExclusive;
+ this.expectedVersion = version != null ? version.getNodeVersion() : 0;
+ this.consumed = 0;
+
+ // Use the total store size for the root spliterator (covering all blocks)
+ if (startBlock == 0 && endBlockExclusive == blocksCount) {
+ this.totalSize = NodeStore.this.size();
+ } else {
+ // For split spliterators, compute proportionally
+ this.totalSize = computeExactSize(startBlock, endBlockExclusive);
+ }
+
+ if (startBlock < endBlockExclusive) {
+ NodeBlock b = blocks[startBlock];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ private int computeExactSize(int start, int end) {
+ int sum = 0;
+ for (int i = start; i < end; i++) {
+ NodeBlock b = blocks[i];
+ if (b != null) {
+ // Exact count: nodeLength minus garbageLength
+ sum += (b.nodeLength - b.garbageLength);
+ }
+ }
+ return sum;
+ }
+
+ private void advanceBlock() {
+ blockIndex++;
+ if (blockIndex < endBlockExclusive) {
+ NodeBlock b = blocks[blockIndex];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+ }
+
+ private void checkForComodification() {
+ if (version != null && expectedVersion != version.getNodeVersion()) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super Node> action) {
+ checkForComodification();
+ while (currentArray != null) {
+ while (indexInBlock < currentLength) {
+ NodeImpl n = currentArray[indexInBlock++];
+ if (n != null) {
+ consumed++;
+ action.accept(n);
+ return true;
+ }
+ }
+ advanceBlock();
+ }
+ return false;
+ }
+
+ @Override
+ public Spliterator trySplit() {
+ // Only split at block boundaries to preserve encounter order
+ if (indexInBlock != 0) {
+ return null;
+ }
+
+ int currentPos = blockIndex;
+ int remainingBlocks = endBlockExclusive - currentPos;
+
+ if (remainingBlocks <= 1) {
+ return null;
+ }
+
+ int mid = currentPos + remainingBlocks / 2;
+
+ // Create left half
+ NodeSpliterator left = new NodeSpliterator(currentPos, mid);
+
+ // Update this spliterator to become the right half
+ blockIndex = mid;
+ if (mid < endBlockExclusive) {
+ NodeBlock b = blocks[mid];
+ currentArray = b.backingArray;
+ currentLength = b.nodeLength;
+ indexInBlock = 0;
+ } else {
+ currentArray = null;
+ currentLength = 0;
+ indexInBlock = 0;
+ }
+
+ // Update this spliterator size
+ this.totalSize = totalSize - left.totalSize;
+
+ return left;
+ }
+
+ @Override
+ public long estimateSize() {
+ // Use the exact totalSize minus what we've consumed
+ long remaining = totalSize - consumed;
+ return remaining < 0 ? 0 : remaining;
+ }
+
+ @Override
+ public int characteristics() {
+ return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED;
+ }
+ }
}
diff --git a/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java b/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java
index 75389855..1a4152b4 100644
--- a/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java
+++ b/src/main/java/org/gephi/graph/impl/SpatialIndexImpl.java
@@ -31,7 +31,8 @@ public NodeIterable getNodesInArea(Rect2D rect) {
@Override
public EdgeIterable getEdgesInArea(Rect2D rect) {
- return new EdgeIterableWrapper(new EdgeIterator(rect, nodesTree.getNodes(rect).iterator()), nodesTree.lock);
+ return new EdgeIterableWrapper(() -> new EdgeIterator(rect, nodesTree.getNodes(rect).iterator()),
+ nodesTree.lock);
}
protected void clearNodes() {
diff --git a/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java
index 91bc601e..055a4d5e 100644
--- a/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java
+++ b/src/main/java/org/gephi/graph/impl/UndirectedDecorator.java
@@ -17,8 +17,18 @@
import java.util.Collection;
import java.util.Set;
-
-import org.gephi.graph.api.*;
+import org.gephi.graph.api.Edge;
+import org.gephi.graph.api.EdgeIterable;
+import org.gephi.graph.api.Graph;
+import org.gephi.graph.api.GraphModel;
+import org.gephi.graph.api.GraphView;
+import org.gephi.graph.api.Interval;
+import org.gephi.graph.api.Node;
+import org.gephi.graph.api.NodeIterable;
+import org.gephi.graph.api.SpatialIndex;
+import org.gephi.graph.api.Subgraph;
+import org.gephi.graph.api.UndirectedGraph;
+import org.gephi.graph.api.UndirectedSubgraph;
public class UndirectedDecorator implements UndirectedGraph, UndirectedSubgraph {
@@ -138,7 +148,8 @@ public Edge getEdge(Node node1, Node node2) {
@Override
public EdgeIterable getEdges(Node node1, Node node2) {
- return store.getEdgeIterableWrapper(store.edgeStore.edgesUndirectedIterator(node1, node2));
+ return new EdgeIterableWrapper(() -> store.edgeStore.edgesUndirectedIterator(node1, node2),
+ store.getAutoLock());
}
@Override
@@ -153,7 +164,8 @@ public Edge getEdge(Node node1, Node node2, int type) {
@Override
public EdgeIterable getEdges(Node node1, Node node2, int type) {
- return store.getEdgeIterableWrapper(store.edgeStore.edgesUndirectedIterator(node1, node2, type));
+ return new EdgeIterableWrapper(() -> store.edgeStore.edgesUndirectedIterator(node1, node2, type),
+ store.getAutoLock());
}
@Override
@@ -163,37 +175,40 @@ public NodeIterable getNodes() {
@Override
public EdgeIterable getEdges() {
- return store.getEdgeIterableWrapper(store.edgeStore.iteratorUndirected());
+ return new EdgeIterableWrapper(store.edgeStore::iteratorUndirected, store.edgeStore::spliteratorUndirected,
+ store.getAutoLock());
}
@Override
public EdgeIterable getEdges(int type) {
- return store.getEdgeIterableWrapper(store.edgeStore.iteratorType(type, true));
+ return new EdgeIterableWrapper(() -> store.edgeStore.iteratorType(type, true),
+ () -> store.edgeStore.spliteratorType(type, true), store.getAutoLock());
}
@Override
public EdgeIterable getSelfLoops() {
- return store.getEdgeIterableWrapper(store.edgeStore.iteratorSelfLoop());
+ return new EdgeIterableWrapper(store.edgeStore::iteratorSelfLoop, store.edgeStore::spliteratorSelfLoop,
+ store.getAutoLock());
}
@Override
public NodeIterable getNeighbors(Node node) {
- return store.getNodeIterableWrapper(store.edgeStore.neighborIterator(node));
+ return new NodeIterableWrapper(() -> store.edgeStore.neighborIterator(node), store.getAutoLock());
}
@Override
public NodeIterable getNeighbors(Node node, int type) {
- return store.getNodeIterableWrapper(store.edgeStore.neighborIterator(node, type));
+ return new NodeIterableWrapper(() -> store.edgeStore.neighborIterator(node, type), store.getAutoLock());
}
@Override
public EdgeIterable getEdges(Node node) {
- return store.getEdgeIterableWrapper(store.edgeStore.edgeUndirectedIterator(node));
+ return new EdgeIterableWrapper(() -> store.edgeStore.edgeUndirectedIterator(node), store.getAutoLock());
}
@Override
public EdgeIterable getEdges(Node node, int type) {
- return store.getEdgeIterableWrapper(store.edgeStore.edgeUndirectedIterator(node, type));
+ return new EdgeIterableWrapper(() -> store.edgeStore.edgeUndirectedIterator(node, type), store.getAutoLock());
}
@Override
diff --git a/src/test/java/org/gephi/graph/impl/BasicGraphStore.java b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java
index 17b45f9c..6e17f62b 100644
--- a/src/test/java/org/gephi/graph/impl/BasicGraphStore.java
+++ b/src/test/java/org/gephi/graph/impl/BasicGraphStore.java
@@ -35,7 +35,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Stream;
import org.gephi.graph.api.Column;
import org.gephi.graph.api.ColumnIterable;
import org.gephi.graph.api.DirectedGraph;
@@ -1149,6 +1151,21 @@ public Iterator iterator() {
return new BasicNodeIterator(idToNodeMap.values().iterator());
}
+ @Override
+ public Spliterator spliterator() {
+ return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED | Spliterator.NONNULL);
+ }
+
+ @Override
+ public Stream stream() {
+ return Collection.super.stream();
+ }
+
+ @Override
+ public Stream parallelStream() {
+ return Collection.super.parallelStream();
+ }
+
@Override
public Node[] toArray() {
return idToNodeMap.values().toArray(new Node[0]);
diff --git a/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java b/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java
index f6fc2b86..0498dfd3 100644
--- a/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java
+++ b/src/test/java/org/gephi/graph/impl/EdgeStoreTest.java
@@ -30,13 +30,18 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
+import java.util.Spliterator;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import org.gephi.graph.api.Configuration;
import org.gephi.graph.api.Edge;
+import org.gephi.graph.api.Node;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -1783,7 +1788,7 @@ public void testUndirectedIterator() {
}
EdgeStore.EdgeStoreIterator undirectedIterator = edgeStore.iteratorUndirected();
- for (; undirectedIterator.hasNext();) {
+ while (undirectedIterator.hasNext()) {
EdgeImpl e = undirectedIterator.next();
Assert.assertTrue(edgeSet.remove(e));
}
@@ -1791,6 +1796,141 @@ public void testUndirectedIterator() {
Assert.assertEquals(0, edgeSet.size());
}
+ @Test
+ public void testUndirectedSpliterator() {
+ EdgeImpl[] edges = GraphGenerator.generateMutualEdges(0);
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.addAll(Arrays.asList(edges));
+
+ // Collect ids from spliteratorUndirected
+ Spliterator spliterator = edgeStore.spliteratorUndirected();
+ Assert.assertEquals(spliterator.estimateSize(), 1);
+ List edgeList = StreamSupport.stream(spliterator, true).collect(Collectors.toList());
+ Assert.assertEquals(edgeList.size(), 1);
+ Assert.assertEquals(edgeList.get(0), edges[0]);
+ }
+
+ @Test
+ public void testUndirectedSpliteratorMatchesIterator() {
+ EdgeImpl[] edges = GraphGenerator.generateEdgeList(300, 0, true, true, false);
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.addAll(Arrays.asList(edges));
+
+ // Collect ids from iteratorUndirected
+ List idsIter = iteratorToArray(edgeStore.iteratorUndirected());
+
+ // Collect ids from spliteratorUndirected
+ Spliterator spliterator = edgeStore.spliteratorUndirected();
+ Assert.assertEquals(spliterator.estimateSize(), idsIter.size());
+ List idsSplit = StreamSupport.stream(spliterator, true).collect(Collectors.toList());
+
+ Assert.assertEquals(idsSplit, idsIter);
+ }
+
+ @Test
+ public void testTypeSpliteratorMatchesIterator() {
+ EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList();
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.addAll(Arrays.asList(edges));
+
+ for (boolean directed : new boolean[] { true, false }) {
+ for (int type = 0; type < 3; type++) {
+ // Collect ids from iteratorUndirected
+ List idsIter = iteratorToArray(edgeStore.iteratorType(type, directed));
+
+ // Collect ids from spliteratorUndirected
+ Spliterator spliterator = edgeStore.spliteratorType(type, directed);
+ Assert.assertEquals(spliterator.estimateSize(), idsIter.size());
+ List idsSplit = StreamSupport.stream(spliterator, true).collect(Collectors.toList());
+
+ Assert.assertEquals(idsSplit, idsIter);
+ }
+ }
+ }
+
+ @Test
+ public void testSelfLoopSpliterator() {
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.add(GraphGenerator.generateSelfLoop(0, true));
+
+ List idsSplit = StreamSupport.stream(edgeStore.spliteratorSelfLoop(), true).collect(Collectors.toList());
+ List idsIter = iteratorToArray(edgeStore.iteratorSelfLoop());
+ Assert.assertEquals(idsSplit, idsIter);
+ }
+
+ @Test
+ public void testFilteredSpliteratorCharacteristicsAndEstimate() {
+ EdgeImpl[] edges = GraphGenerator.generateEdgeList(200, 0, true, true, false);
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.addAll(Arrays.asList(edges));
+
+ Spliterator sp = edgeStore.spliteratorUndirected();
+ int ch = sp.characteristics();
+ Assert.assertTrue((ch & Spliterator.SIZED) != 0);
+ Assert.assertTrue((ch & Spliterator.SUBSIZED) == 0);
+
+ // Count expected
+ List expected = iteratorToArray(edgeStore.iteratorUndirected());
+ Assert.assertEquals(sp.estimateSize(), expected.size());
+
+ // Consume 5 and check estimate decreases
+ for (int i = 0; i < 5; i++) {
+ Assert.assertTrue(sp.tryAdvance(e -> {
+ }));
+ }
+ Assert.assertEquals(sp.estimateSize(), expected.size() - 5);
+ }
+
+ @Test
+ public void testEdgeSpliteratorCoversAll() {
+ NodeStore nodeStore = GraphGenerator.generateNodeStore(2);
+ NodeImpl n1 = nodeStore.get(0);
+ NodeImpl n2 = nodeStore.get(1);
+ EdgeStore store = new EdgeStore();
+ int n = GraphStoreConfiguration.EDGESTORE_BLOCK_SIZE * 2 + 321;
+ for (int i = 0; i < n; i++) {
+ store.add(new EdgeImpl("e" + i, n1, n2, 0, 1.0, true));
+ }
+ Set seen = new HashSet<>();
+ Spliterator sp = store.spliterator();
+ sp.forEachRemaining(seen::add);
+ Assert.assertEquals(seen.size(), n);
+ for (Edge e : store) {
+ Assert.assertTrue(seen.contains(e));
+ }
+ }
+
+ @Test(expectedExceptions = ConcurrentModificationException.class)
+ public void testEdgeSpliteratorFailFastOnAdd() {
+ EdgeImpl[] edges = GraphGenerator.generateSmallMultiTypeEdgeList();
+ EdgeStore edgeStore = new EdgeStore(null, null, null, null, null, new GraphVersion(null));
+ edgeStore.addAll(Arrays.asList(edges));
+ Spliterator sp = edgeStore.spliterator();
+ Assert.assertTrue(edgeStore.remove(edges[0]));
+ sp.tryAdvance(x -> {
+ });
+ }
+
+ @Test
+ public void testEdgeParallelStreamCount() {
+ EdgeImpl[] edges = GraphGenerator.generateEdgeList(100000, 2, true, true, true);
+ EdgeStore edgeStore = new EdgeStore();
+ edgeStore.addAll(Arrays.asList(edges));
+ long count = edgeStore.parallelStream().count();
+ Assert.assertEquals(count, edges.length);
+ }
+
+ @Test
+ public void testEdgeStream() {
+ EdgeStore store = new EdgeStore();
+ EdgeImpl[] edges = GraphGenerator.generateLargeEdgeList();
+ store.addAll(Arrays.asList(edges));
+
+ Set set = Collections.synchronizedSet(new HashSet<>());
+ store.parallelStream().forEachOrdered(set::add);
+ Assert.assertEquals(set, store.toSet());
+ }
+
@Test
public void testUndirectedIteratorRemove() {
EdgeStore edgeStore = new EdgeStore();
@@ -2105,10 +2245,10 @@ private NodeImpl[] getNodes(EdgeImpl[] edges) {
return nodes.toArray(new NodeImpl[0]);
}
- public List iteratorToArray(Iterator edgeIterator) {
- List list = new ArrayList<>();
+ public List iteratorToArray(Iterator edgeIterator) {
+ List list = new ArrayList<>();
for (; edgeIterator.hasNext();) {
- EdgeImpl e = edgeIterator.next();
+ Edge e = edgeIterator.next();
list.add(e);
}
return list;
diff --git a/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java b/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java
index 5650fa94..e33283f4 100644
--- a/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java
+++ b/src/test/java/org/gephi/graph/impl/EmptyIterableTest.java
@@ -16,8 +16,10 @@
package org.gephi.graph.impl;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
import org.gephi.graph.api.Element;
@@ -62,6 +64,15 @@ public void testEdgeIterableDoBreak() {
EdgeIterable.EMPTY.doBreak();
}
+ @Test
+ public void testEdgeIterableSpliterator() {
+ EdgeIterable itr = EdgeIterable.EMPTY;
+ Assert.assertFalse(itr.spliterator().tryAdvance((e) -> {
+ }));
+ Assert.assertEquals(itr.spliterator().estimateSize(), 0);
+ Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST);
+ }
+
@Test
public void testNodeIterableHasNext() {
Iterator itr = NodeIterable.EMPTY.iterator();
@@ -95,6 +106,15 @@ public void testNodeIterableDoBreak() {
NodeIterable.EMPTY.doBreak();
}
+ @Test
+ public void testNodeIterableSpliterator() {
+ NodeIterable itr = NodeIterable.EMPTY;
+ Assert.assertFalse(itr.spliterator().tryAdvance((e) -> {
+ }));
+ Assert.assertEquals(itr.spliterator().estimateSize(), 0);
+ Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST);
+ }
+
@Test
public void testElementIterableHasNext() {
Iterator itr = ElementIterable.EMPTY.iterator();
@@ -127,4 +147,13 @@ public void testElementIterableToCollection() {
public void testElementIterableDoBreak() {
ElementIterable.EMPTY.doBreak();
}
+
+ @Test
+ public void testElementIterableSpliterator() {
+ ElementIterable itr = ElementIterable.EMPTY;
+ Assert.assertFalse(itr.spliterator().tryAdvance((e) -> {
+ }));
+ Assert.assertEquals(itr.spliterator().estimateSize(), 0);
+ Assert.assertEquals(itr.stream().collect(Collectors.toList()), Collections.EMPTY_LIST);
+ }
}
diff --git a/src/test/java/org/gephi/graph/impl/GraphStoreTest.java b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java
index 9d4c70ab..d3ee961a 100644
--- a/src/test/java/org/gephi/graph/impl/GraphStoreTest.java
+++ b/src/test/java/org/gephi/graph/impl/GraphStoreTest.java
@@ -22,11 +22,10 @@
import java.awt.Color;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Spliterator;
import org.gephi.graph.api.Column;
import org.gephi.graph.api.ColumnIterable;
import org.gephi.graph.api.Configuration;
@@ -733,8 +732,8 @@ public void testGetNodeEdges() {
graphStore.addAllEdges(Arrays.asList(edges));
for (EdgeImpl e : edges) {
- testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e });
- testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
}
}
@@ -746,8 +745,8 @@ public void testGetNodeEdgesUnusedEdgeType() {
graphStore.addAllEdges(Arrays.asList(edges));
for (EdgeImpl e : edges) {
- testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {});
- testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {});
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
}
}
@@ -756,8 +755,8 @@ public void testGetNodeEdgesMixed() {
GraphStore graphStore = GraphGenerator.generateSmallMixedGraphStore();
for (EdgeImpl e : graphStore.edgeStore.toArray()) {
- testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e });
- testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
}
}
@@ -766,8 +765,8 @@ public void testGetNodeEdgesMixedUnusedEdgeType() {
GraphStore graphStore = GraphGenerator.generateSmallMixedGraphStore(2);
for (EdgeImpl e : graphStore.edgeStore.toArray()) {
- testEdgeIterable(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {});
- testEdgeIterable(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target), new EdgeImpl[] {});
+ testEdgeIterableWithoutParallel(graphStore.getEdges(e.source, e.target, e.type), new EdgeImpl[] { e });
}
}
@@ -1057,19 +1056,19 @@ public void testVersion() {
// UTILITY
private void testNodeIterable(NodeIterable iterable, NodeImpl[] nodes) {
- Set nodeSet = new HashSet<>(iterable.toCollection());
- for (NodeImpl n : nodes) {
- Assert.assertTrue(nodeSet.remove(n));
- }
- Assert.assertEquals(nodeSet.size(), 0);
+ Assert.assertEquals(iterable.toArray(), nodes);
+ Assert.assertEquals(iterable.stream().toArray(Node[]::new), nodes);
+ Assert.assertEquals(iterable.parallelStream().toArray(Node[]::new), nodes);
}
private void testEdgeIterable(EdgeIterable iterable, EdgeImpl[] edges) {
- Set edgeSet = new HashSet<>(iterable.toCollection());
- for (EdgeImpl n : edges) {
- Assert.assertTrue(edgeSet.remove(n));
- }
- Assert.assertEquals(edgeSet.size(), 0);
+ testEdgeIterableWithoutParallel(iterable, edges);
+ Assert.assertEquals(iterable.parallelStream().toArray(Edge[]::new), edges);
+ }
+
+ private void testEdgeIterableWithoutParallel(EdgeIterable iterable, EdgeImpl[] edges) {
+ Assert.assertEquals(iterable.toArray(), edges);
+ Assert.assertEquals(iterable.stream().toArray(Edge[]::new), edges);
}
private void testBasicStoreEquals(GraphStore graphStore, BasicGraphStore basicGraphStore) {
@@ -1193,4 +1192,36 @@ private void testNodeSets(NodeIterable n1, NodeIterable n2) {
}
Assert.assertEquals(s2.size(), 0);
}
+
+ @Test
+ public void testGetEdgesTypeSpliteratorMatches() {
+ GraphStore gs = GraphGenerator.generateSmallMultiTypeGraphStore();
+ Edge[] edges = gs.getEdges().toArray();
+ for (int i = 0; i < 3; i++) {
+ Spliterator sp = gs.getEdges(i).spliterator();
+ int finalI = i;
+ sp.forEachRemaining(e -> Assert.assertEquals(finalI, e.getType()));
+ }
+ }
+
+ // @Test
+ // public void testGetSelfLoopsSpliteratorMatches() {
+ // GraphStore gs = new GraphStore();
+ // NodeImpl[] nodes = GraphGenerator.generateSmallNodeList();
+ // gs.addAllNodes(Arrays.asList(nodes));
+ // EdgeImpl[] edges = new EdgeImpl[] {
+ // GraphGenerator.generateSelfLoop(0, true),
+ // GraphGenerator.generateSelfLoop(1, false)
+ // };
+ // for (EdgeImpl e : edges) {
+ // e.source = nodes[0];
+ // e.target = nodes[0];
+ // gs.addEdge(e);
+ // }
+ // Set