diff --git a/pom.xml b/pom.xml index ec7cb929..350d84df 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.gephi graphstore - 0.7.4-SNAPSHOT + 0.8.0-SNAPSHOT jar GraphStore diff --git a/src/main/java/org/gephi/graph/api/EdgeIterable.java b/src/main/java/org/gephi/graph/api/EdgeIterable.java index 399c848e..cc9b44dd 100644 --- a/src/main/java/org/gephi/graph/api/EdgeIterable.java +++ b/src/main/java/org/gephi/graph/api/EdgeIterable.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; /** * An edge iterable. @@ -63,10 +65,24 @@ public interface EdgeIterable extends ElementIterable { @Override public Set 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 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 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 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 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 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 ids = new HashSet<>(); + // gs.getSelfLoops().spliterator().forEachRemaining(e -> ids.add(e.getId())); + // for (EdgeImpl e : edges) { + // Assert.assertTrue(ids.contains(e.getId())); + // } + // } } diff --git a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java index ff60e577..ad340afb 100644 --- a/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphViewDecoratorTest.java @@ -15,20 +15,18 @@ */ package org.gephi.graph.impl; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; import java.util.Arrays; import java.util.Collections; import java.util.Random; +import java.util.stream.Collectors; import org.gephi.graph.api.Configuration; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; -import org.gephi.graph.api.Element; -import org.gephi.graph.api.ElementIterable; +import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Interval; import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; import org.gephi.graph.api.Rect2D; -import org.gephi.graph.api.SpatialIndex; import org.gephi.graph.api.UndirectedSubgraph; import org.testng.Assert; import org.testng.annotations.Test; @@ -500,26 +498,26 @@ public void testDirectedIterators() { DirectedSubgraph graph = store.getDirectedGraph(view); GraphStore copyGraphStore = convertToStore(view); - Assert.assertTrue(isIterablesEqual(graph.getNodes(), copyGraphStore.getNodes())); - Assert.assertTrue(isIterablesEqual(graph.getEdges(), copyGraphStore.getEdges())); - Assert.assertTrue(isIterablesEqual(graph.getSelfLoops(), copyGraphStore.getSelfLoops())); + assertIterablesEqual(graph.getNodes(), copyGraphStore.getNodes()); + assertIterablesEqual(graph.getEdges(), copyGraphStore.getEdges()); + assertIterablesEqual(graph.getSelfLoops(), copyGraphStore.getSelfLoops()); for (Node n : graph.getNodes()) { Node m = copyGraphStore.getNode(n.getId()); - Assert.assertTrue(isIterablesEqual(graph.getEdges(n), copyGraphStore.getEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getInEdges(n), copyGraphStore.getInEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getOutEdges(n), copyGraphStore.getOutEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n), copyGraphStore.getNeighbors(m))); - Assert.assertTrue(isIterablesEqual(graph.getSuccessors(n), copyGraphStore.getSuccessors(m))); - Assert.assertTrue(isIterablesEqual(graph.getPredecessors(n), copyGraphStore.getPredecessors(m))); + assertIterablesEqual(graph.getEdges(n), copyGraphStore.getEdges(m)); + assertIterablesEqual(graph.getInEdges(n), copyGraphStore.getInEdges(m)); + assertIterablesEqual(graph.getOutEdges(n), copyGraphStore.getOutEdges(m)); + assertIterablesEqual(graph.getNeighbors(n), copyGraphStore.getNeighbors(m)); + assertIterablesEqual(graph.getSuccessors(n), copyGraphStore.getSuccessors(m)); + assertIterablesEqual(graph.getPredecessors(n), copyGraphStore.getPredecessors(m)); for (int i = 0; i < typeCount; i++) { - Assert.assertTrue(isIterablesEqual(graph.getEdges(n, i), copyGraphStore.getEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getInEdges(n, i), copyGraphStore.getInEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getOutEdges(n, i), copyGraphStore.getOutEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.getNeighbors(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getSuccessors(n, i), copyGraphStore.getSuccessors(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getPredecessors(n, i), copyGraphStore.getPredecessors(m, i))); + assertIterablesEqual(graph.getEdges(n, i), copyGraphStore.getEdges(m, i)); + assertIterablesEqual(graph.getInEdges(n, i), copyGraphStore.getInEdges(m, i)); + assertIterablesEqual(graph.getOutEdges(n, i), copyGraphStore.getOutEdges(m, i)); + assertIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.getNeighbors(m, i)); + assertIterablesEqual(graph.getSuccessors(n, i), copyGraphStore.getSuccessors(m, i)); + assertIterablesEqual(graph.getPredecessors(n, i), copyGraphStore.getPredecessors(m, i)); } } } @@ -535,21 +533,18 @@ public void testUndirectedIterators() { UndirectedSubgraph graph = store.getUndirectedGraph(view); GraphStore copyGraphStore = convertToStore(view); - Assert.assertTrue(isIterablesEqual(graph.getNodes(), copyGraphStore.undirectedDecorator.getNodes())); - Assert.assertTrue(isIterablesEqual(graph.getEdges(), copyGraphStore.undirectedDecorator.getEdges())); - Assert.assertTrue(isIterablesEqual(graph.getSelfLoops(), copyGraphStore.undirectedDecorator.getSelfLoops())); + assertIterablesEqual(graph.getNodes(), copyGraphStore.undirectedDecorator.getNodes()); + assertIterablesEqual(graph.getEdges(), copyGraphStore.undirectedDecorator.getEdges()); + assertIterablesEqual(graph.getSelfLoops(), copyGraphStore.undirectedDecorator.getSelfLoops()); for (Node n : graph.getNodes()) { Node m = copyGraphStore.getNode(n.getId()); - Assert.assertTrue(isIterablesEqual(graph.getEdges(n), copyGraphStore.undirectedDecorator.getEdges(m))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n), copyGraphStore.undirectedDecorator - .getNeighbors(m))); + assertIterablesEqual(graph.getEdges(n), copyGraphStore.undirectedDecorator.getEdges(m)); + assertIterablesEqual(graph.getNeighbors(n), copyGraphStore.undirectedDecorator.getNeighbors(m)); for (int i = 0; i < typeCount; i++) { - Assert.assertTrue(isIterablesEqual(graph.getEdges(n, i), copyGraphStore.undirectedDecorator - .getEdges(m, i))); - Assert.assertTrue(isIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.undirectedDecorator - .getNeighbors(m, i))); + assertIterablesEqual(graph.getEdges(n, i), copyGraphStore.undirectedDecorator.getEdges(m, i)); + assertIterablesEqual(graph.getNeighbors(n, i), copyGraphStore.undirectedDecorator.getNeighbors(m, i)); } } } @@ -892,35 +887,6 @@ public void testIntersection() { Assert.assertTrue(graph1.contains(n2)); } - // UTILITY - private boolean isIterablesEqual(ElementIterable n1, ElementIterable n2) { - ObjectSet s1 = new ObjectOpenHashSet(); - for (Object n : n1) { - s1.add(((Element) n).getId()); - } - ObjectSet s2 = new ObjectOpenHashSet(); - for (Object n : n2) { - s2.add(((Element) n).getId()); - } - return s1.equals(s2); - } - - private GraphStore convertToStore(GraphViewImpl view) { - GraphStore store = new GraphStore(); - DirectedSubgraph graph = view.getDirectedGraph(); - for (Node n : graph.getNodes()) { - NodeImpl m = new NodeImpl(n.getId()); - store.addNode(m); - } - for (Edge e : graph.getEdges()) { - NodeImpl source = store.getNode(e.getSource().getId()); - NodeImpl target = store.getNode(e.getTarget().getId()); - EdgeImpl f = new EdgeImpl(e.getId(), source, target, e.getType(), e.getWeight(), e.isDirected()); - store.addEdge(f); - } - return store; - } - @Test public void testGetBoundariesEmptyView() { GraphStore graphStore = GraphGenerator.generateEmptyGraphStore(getSpatialConfig()); @@ -1104,6 +1070,35 @@ public void testGetBoundariesAfterViewChanges() { Assert.assertEquals(expected, graph.getSpatialIndex().getBoundaries()); } + // UTILITY + private void assertIterablesEqual(NodeIterable n1, NodeIterable n2) { + Assert.assertEquals(n1.toCollection(), n2.toCollection()); + Assert.assertEquals(n1.stream().collect(Collectors.toList()), n2.stream().collect(Collectors.toList())); + Assert.assertEquals(n1.toArray(), n2.toArray()); + } + + private void assertIterablesEqual(EdgeIterable e1, EdgeIterable e2) { + Assert.assertEquals(e1.toCollection(), e2.toCollection()); + Assert.assertEquals(e1.stream().collect(Collectors.toList()), e2.stream().collect(Collectors.toList())); + Assert.assertEquals(e1.toArray(), e2.toArray()); + } + + private GraphStore convertToStore(GraphViewImpl view) { + GraphStore store = new GraphStore(); + DirectedSubgraph graph = view.getDirectedGraph(); + for (Node n : graph.getNodes()) { + NodeImpl m = new NodeImpl(n.getId()); + store.addNode(m); + } + for (Edge e : graph.getEdges()) { + NodeImpl source = store.getNode(e.getSource().getId()); + NodeImpl target = store.getNode(e.getTarget().getId()); + EdgeImpl f = new EdgeImpl(e.getId(), source, target, e.getType(), e.getWeight(), e.isDirected()); + store.addEdge(f); + } + return store; + } + private void addSomeElements(GraphStore store, GraphViewImpl view) { double perc = 0.8; Random rand = new Random(98324); diff --git a/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java b/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java index eb6561a1..2efe9dba 100644 --- a/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/GraphViewStoreTest.java @@ -15,11 +15,11 @@ */ package org.gephi.graph.impl; -import org.gephi.graph.api.Interval; import org.gephi.graph.api.DirectedSubgraph; import org.gephi.graph.api.Edge; 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.Subgraph; import org.gephi.graph.api.UndirectedSubgraph; diff --git a/src/test/java/org/gephi/graph/impl/NodeStoreTest.java b/src/test/java/org/gephi/graph/impl/NodeStoreTest.java index 4b1c6a1a..2c320bfd 100644 --- a/src/test/java/org/gephi/graph/impl/NodeStoreTest.java +++ b/src/test/java/org/gephi/graph/impl/NodeStoreTest.java @@ -20,10 +20,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.Spliterator; +import java.util.stream.Collectors; import org.gephi.graph.api.Node; import org.testng.Assert; import org.testng.annotations.Test; @@ -502,6 +505,167 @@ public void testDictionaryDuplicate() { nodeStore.add(node2); } + @Test + public void testSpliteratorCoversAll() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + + List seen = new ArrayList<>(); + Spliterator sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size()); + sp.forEachRemaining(e -> seen.add((NodeImpl) e)); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorSizeReduce() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + + Spliterator sp = nodeStore.spliterator(); + long size = sp.estimateSize(); + sp.tryAdvance(n -> { + }); + Assert.assertEquals(sp.estimateSize(), size - 1); + } + + @Test + public void testSpliteratorSizeReduceWithGarbage() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + nodeStore.remove(nodes.get(0)); + + Spliterator sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size() - 1); + + // Last node + nodeStore.remove(nodes.get(nodes.size() - 1)); + sp = nodeStore.spliterator(); + Assert.assertEquals(sp.estimateSize(), nodes.size() - 2); + } + + @Test + public void testSpliteratorParallel() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorEmpty() { + NodeStore nodeStore = new NodeStore(); + + Assert.assertEquals(nodeStore.parallelStream().count(), 0); + Assert.assertEquals(nodeStore.spliterator().estimateSize(), 0); + } + + @Test + public void testSpliteratorEmptyAfterRemove() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + nodeStore.addAll(nodes); + nodeStore.removeAll(nodes); + + Assert.assertEquals(nodeStore.parallelStream().count(), 0); + Assert.assertEquals(nodeStore.spliterator().estimateSize(), 0); + } + + @Test + public void testSpliteratorParallelLarge() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays + .asList(GraphGenerator.generateNodeList(GraphStoreConfiguration.NODESTORE_BLOCK_SIZE * 4 + 10)); + nodeStore.addAll(nodes); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes); + } + + @Test + public void testSpliteratorParallelAfterRemove() { + NodeStore nodeStore = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateSmallNodeList()); + nodeStore.addAll(nodes); + nodeStore.remove(nodes.get(0)); + + List seen = nodeStore.parallelStream().collect(Collectors.toList()); + + Assert.assertEquals(seen, nodes.subList(1, nodes.size())); + } + + @Test(expectedExceptions = ConcurrentModificationException.class) + public void testSpliteratorFailFastOnAdd() { + NodeStore store = new NodeStore(null, null, null, null, new GraphVersion(null)); + store.add(new NodeImpl("a")); + Spliterator sp = store.spliterator(); + store.add(new NodeImpl("b")); + sp.tryAdvance(x -> { + }); + } + + @Test + public void testParallelStreamCount() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + long count = store.parallelStream().count(); + Assert.assertEquals(count, store.size()); + } + + @Test + public void testParallelStreamForEach() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEach(set::add); + Assert.assertEquals(set, store.toSet()); + } + + @Test + public void testParallelStreamForEachOrdered() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEachOrdered(set::add); + Assert.assertEquals(set, store.toSet()); + } + + @Test + public void testParallelStreamForEachOrderedAfterRemove() { + NodeStore store = new NodeStore(); + List nodes = Arrays.asList(GraphGenerator.generateLargeNodeList()); + store.addAll(nodes); + + Random random = new Random(); + for (int i = 0; i < nodes.size() / 5; i++) { + int r = random.nextInt(store.maxStoreId()); + Node n = store.getForGetByStoreId(r); + if (n != null) { + store.remove(n); + } + } + + Set set = Collections.synchronizedSet(new HashSet<>()); + store.parallelStream().forEachOrdered(set::add); + Assert.assertEquals(set, store.toSet()); + } + + // Utils + private void testContainsOnly(NodeStore store, List list) { for (NodeImpl n : list) { Assert.assertTrue(store.contains(n)); diff --git a/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java b/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java index 0ca95d96..41d47d29 100644 --- a/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java +++ b/src/test/java/org/gephi/graph/impl/UndirectedDecoratorTest.java @@ -15,12 +15,10 @@ */ package org.gephi.graph.impl; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.gephi.graph.api.Edge; import org.gephi.graph.api.EdgeIterable; import org.gephi.graph.api.Node; @@ -205,7 +203,11 @@ public void testGetEdgesMixed() { // UTILITY private void testEdgeIterable(EdgeIterable iterable, Edge[] edges) { - Set edgeSet = new HashSet<>(iterable.toCollection()); + testEdgeIterable(new HashSet<>(iterable.toCollection()), edges); + testEdgeIterable(iterable.stream().collect(Collectors.toSet()), edges); + } + + private void testEdgeIterable(Set edgeSet, Edge[] edges) { for (Edge n : edges) { Assert.assertTrue(edgeSet.remove(n)); }