• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gephi / graphstore / #504

03 Oct 2025 01:24PM UTC coverage: 91.042% (+0.2%) from 90.873%
#504

push

web-flow
Add Spliterator on NodesQuadTree (#243)

* Change default config for block size and default dict size

* Replace LinkedHashSet with arrays and implement spliterator

* Add multi-node edge iterator

* Refactoring to remove duplicate getNodes methods in quadtree

* Add edge iteration support in quadtree

* Add global quad tree edge iterator option

* Refactor to configure edge inout iterator locking and test all edges in quadtree spliterator

* Formatting

* Git ignore

* Non approximate support for global iterator and bugfix

* Add additional tests

* Quadtree versioning

* Tweak boundaries

* Documentation

* Update src/main/java/org/gephi/graph/impl/NodesQuadTree.java

Remove distinct from spliterator as edges can be returned twice

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix locking function name clash in view decorator

* Fix toArray

* Reduce memory overhead of quad node array init

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

535 of 615 new or added lines in 10 files covered. (86.99%)

1 existing line in 1 file now uncovered.

11576 of 12715 relevant lines covered (91.04%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.92
/src/main/java/org/gephi/graph/impl/NodesQuadTree.java
1
/*
2
 * Copyright 2012-2013 Gephi Consortium
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
 * use this file except in compliance with the License. You may obtain a copy of
6
 * the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 * License for the specific language governing permissions and limitations under
14
 * the License.
15
 */
16
package org.gephi.graph.impl;
17

18
import java.util.ArrayDeque;
19
import java.util.ArrayList;
20
import java.util.Collection;
21
import java.util.Collections;
22
import java.util.Deque;
23
import java.util.HashSet;
24
import java.util.Iterator;
25
import java.util.List;
26
import java.util.Set;
27
import java.util.Spliterator;
28
import java.util.function.Consumer;
29
import java.util.function.Predicate;
30
import java.util.ConcurrentModificationException;
31
import org.gephi.graph.api.Edge;
32
import org.gephi.graph.api.EdgeIterable;
33
import org.gephi.graph.api.Node;
34
import org.gephi.graph.api.NodeIterable;
35
import org.gephi.graph.api.Rect2D;
36

37
/**
38
 * Adapted from https://bitbucket.org/C3/quadtree/wiki/Home
39
 *
40
 * @author Eduardo Ramos
41
 */
42
public class NodesQuadTree {
43

44
    protected final GraphLockImpl lock = new GraphLockImpl();
1✔
45

46
    private final QuadTreeNode quadTreeRoot;
47
    private final int maxLevels;
48
    private final int maxObjectsPerNode;
49
    private final GraphStore graphStore;
50
    private int version = 0;
1✔
51

52
    public NodesQuadTree(Rect2D rect) {
53
        this(null, rect);
1✔
54
    }
1✔
55

56
    public NodesQuadTree(GraphStore store, Rect2D rect) {
57
        this(store, rect, GraphStoreConfiguration.SPATIAL_INDEX_MAX_LEVELS,
1✔
58
                GraphStoreConfiguration.SPATIAL_INDEX_MAX_OBJECTS_PER_NODE);
59
    }
1✔
60

61
    public NodesQuadTree(GraphStore store, Rect2D rect, int maxLevels, int maxObjectsPerNode) {
1✔
62
        this.quadTreeRoot = new QuadTreeNode(rect);
1✔
63
        this.maxLevels = maxLevels;
1✔
64
        this.maxObjectsPerNode = maxObjectsPerNode;
1✔
65
        this.graphStore = store;
1✔
66
    }
1✔
67

68
    public Rect2D quadRect() {
69
        return quadTreeRoot.quadRect();
1✔
70
    }
71

72
    public NodeIterable getNodes(Rect2D searchRect) {
73
        return quadTreeRoot.getNodes(searchRect);
1✔
74
    }
75

76
    public NodeIterable getNodes(Rect2D searchRect, boolean approximate) {
77
        return quadTreeRoot.getNodes(searchRect, approximate);
1✔
78
    }
79

80
    public NodeIterable getNodes(Rect2D searchRect, boolean approximate, Predicate<? super Node> predicate) {
81
        return quadTreeRoot.getNodes(searchRect, approximate, predicate);
1✔
82
    }
83

84
    public NodeIterable getAllNodes() {
85
        return quadTreeRoot.getAllNodes();
1✔
86
    }
87

88
    public NodeIterable getAllNodes(Predicate<? super Node> predicate) {
89
        return quadTreeRoot.getAllNodes(predicate);
1✔
90
    }
91

92
    public EdgeIterable getEdges() {
93
        return quadTreeRoot.getAllEdges();
1✔
94
    }
95

96
    public EdgeIterable getEdges(Rect2D searchRect) {
NEW
97
        return quadTreeRoot.getEdges(searchRect);
×
98
    }
99

100
    public EdgeIterable getEdges(Rect2D searchRect, boolean approximate) {
101
        return quadTreeRoot.getEdges(searchRect, approximate);
1✔
102
    }
103

104
    public EdgeIterable getEdges(Rect2D searchRect, boolean approximate, Predicate<? super Edge> predicate) {
105
        return quadTreeRoot.getEdges(searchRect, approximate, predicate);
1✔
106
    }
107

108
    public void incrementVersion() {
109
        version++;
1✔
110
    }
1✔
111

112
    public boolean updateNode(NodeImpl item, float minX, float minY, float maxX, float maxY) {
113
        writeLock();
1✔
114
        try {
115
            final SpatialNodeDataImpl obj = item.getSpatialData();
1✔
116
            if (obj != null) {
1✔
117
                obj.updateBoundaries(minX, minY, maxX, maxY);
1✔
118
                quadTreeRoot.update(item);
1✔
119
                version++;
1✔
120
                return true;
1✔
121
            } else {
122
                return false;
×
123
            }
124
        } finally {
125
            writeUnlock();
1✔
126
        }
127
    }
128

129
    public boolean addNode(NodeImpl item) {
130
        writeLock();
1✔
131
        try {
132
            final float x = item.x();
1✔
133
            final float y = item.y();
1✔
134
            final float size = item.size();
1✔
135

136
            final float minX = x - size;
1✔
137
            final float minY = y - size;
1✔
138
            final float maxX = x + size;
1✔
139
            final float maxY = y + size;
1✔
140

141
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
142
            if (spatialData == null) {
1✔
143
                spatialData = new SpatialNodeDataImpl(minX, minY, maxX, maxY);
1✔
144
                item.setSpatialData(spatialData);
1✔
145
                quadTreeRoot.insert(item);
1✔
146
                version++;
1✔
147
                return true;
1✔
148
            } else {
149
                return false;
1✔
150
            }
151
        } finally {
152
            writeUnlock();
1✔
153
        }
154
    }
155

156
    public void clear() {
157
        writeLock();
1✔
158
        try {
159
            for (Node node : getAllNodes()) {
1✔
160
                SpatialNodeDataImpl spatialData = ((NodeImpl) node).getSpatialData();
1✔
161
                spatialData.clear();
1✔
162
            }
1✔
163
            quadTreeRoot.clear();
1✔
164
            version++;
1✔
165
        } finally {
166
            writeUnlock();
1✔
167
        }
168
    }
1✔
169

170
    public boolean removeNode(NodeImpl item) {
171
        writeLock();
1✔
172
        try {
173
            final SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
174
            if (spatialData != null && spatialData.quadTreeNode != null) {
1✔
175
                quadTreeRoot.delete(item, true);
1✔
176
                version++;
1✔
177
                return true;
1✔
178
            }
179
            return false;
1✔
180
        } finally {
181
            writeUnlock();
1✔
182
        }
183
    }
184

185
    public int getObjectCount() {
186
        readLock();
1✔
187
        int count = quadTreeRoot.objectCount();
1✔
188
        readUnlock();
1✔
189
        return count;
1✔
190
    }
191

192
    public void readLock() {
193
        if (lock != null) {
1✔
194
            lock.readLock();
1✔
195
        }
196
    }
1✔
197

198
    public void readUnlock() {
199
        if (lock != null) {
1✔
200
            lock.readUnlock();
1✔
201
        }
202
    }
1✔
203

204
    public void writeLock() {
205
        if (lock != null) {
1✔
206
            lock.writeLock();
1✔
207
        }
208
    }
1✔
209

210
    public void writeUnlock() {
211
        if (lock != null) {
1✔
212
            lock.writeUnlock();
1✔
213
        }
214
    }
1✔
215

216
    @Override
217
    public String toString() {
218
        StringBuilder sb = new StringBuilder();
×
219

220
        quadTreeRoot.toString(sb);
×
221

222
        return sb.toString();
×
223
    }
224

225
    public int getDepth() {
226
        readLock();
1✔
227
        int depth = quadTreeRoot.getDepth();
1✔
228
        readUnlock();
1✔
229
        return depth;
1✔
230
    }
231

232
    public int getNodeCount(boolean keepOnlyWithObjects) {
233
        readLock();
1✔
234
        int count = quadTreeRoot.getNodeCount(keepOnlyWithObjects);
1✔
235
        readUnlock();
1✔
236
        return count;
1✔
237
    }
238

239
    public Rect2D getBoundaries() {
240
        return getBoundaries(null);
1✔
241
    }
242

243
    public Rect2D getBoundaries(Predicate<? super Node> predicate) {
244
        readLock();
1✔
245
        try {
246
            NodeIterable allNodes = predicate == null ? getAllNodes() : getAllNodes(predicate);
1✔
247

248
            float minX = Float.POSITIVE_INFINITY;
1✔
249
            float minY = Float.POSITIVE_INFINITY;
1✔
250
            float maxX = Float.NEGATIVE_INFINITY;
1✔
251
            float maxY = Float.NEGATIVE_INFINITY;
1✔
252

253
            boolean hasNodes = false;
1✔
254

255
            for (Node node : allNodes) {
1✔
256
                if (node == null) {
1✔
NEW
257
                    continue;
×
258
                }
259
                SpatialNodeDataImpl spatialData = ((NodeImpl) node).getSpatialData();
1✔
260
                if (spatialData != null) {
1✔
261
                    hasNodes = true;
1✔
262
                    if (spatialData.minX < minX) {
1✔
263
                        minX = spatialData.minX;
1✔
264
                    }
265
                    if (spatialData.minY < minY) {
1✔
266
                        minY = spatialData.minY;
1✔
267
                    }
268
                    if (spatialData.maxX > maxX) {
1✔
269
                        maxX = spatialData.maxX;
1✔
270
                    }
271
                    if (spatialData.maxY > maxY) {
1✔
272
                        maxY = spatialData.maxY;
1✔
273
                    }
274
                }
275
            }
1✔
276

277
            return hasNodes ? new Rect2D(minX, minY, maxX, maxY) : new Rect2D(Float.NEGATIVE_INFINITY,
1✔
278
                    Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
279
        } finally {
280
            readUnlock();
1✔
281
        }
282
    }
283

284
    private int collectOverlapping(QuadTreeNode node, Rect2D searchRect, Set<QuadTreeNode> resultSet) {
285
        if (searchRect != null && !node.rect.intersects(searchRect)) {
1✔
286
            return 0;
1✔
287
        }
288

289
        // If this node has objects and intersects with search rect, add it
290
        int nodeCount = 0;
1✔
291
        if (node.objectCount > 0) {
1✔
292
            resultSet.add(node);
1✔
293
            nodeCount += node.objectCount;
1✔
294
        }
295

296
        // Recursively check children
297
        if (node.childTL != null) {
1✔
298
            nodeCount += collectOverlapping(node.childTL, searchRect, resultSet);
1✔
299
            nodeCount += collectOverlapping(node.childTR, searchRect, resultSet);
1✔
300
            nodeCount += collectOverlapping(node.childBL, searchRect, resultSet);
1✔
301
            nodeCount += collectOverlapping(node.childBR, searchRect, resultSet);
1✔
302
        }
303
        return nodeCount;
1✔
304
    }
305

306
    protected class QuadTreeNode {
307

308
        private NodeImpl[] objects = null; // Fixed-size array for objects
1✔
309
        private int objectCount = 0; // Number of objects currently in this node
1✔
310
        private final Rect2D rect; // The area this QuadTree represents
311

312
        private final QuadTreeNode parent; // The parent of this quad
313
        private final int level;
314
        private int size = 0; // Total number of objects in this node and its children
1✔
315

316
        private QuadTreeNode childTL = null; // Top Left Child
1✔
317
        private QuadTreeNode childTR = null; // Top Right Child
1✔
318
        private QuadTreeNode childBL = null; // Bottom Left Child
1✔
319
        private QuadTreeNode childBR = null; // Bottom Right Child
1✔
320

321
        public Rect2D quadRect() {
322
            return rect;
1✔
323
        }
324

325
        public QuadTreeNode topLeftChild() {
326
            return childTL;
×
327
        }
328

329
        public QuadTreeNode topRightChild() {
330
            return childTR;
×
331
        }
332

333
        public QuadTreeNode bottomLeftChild() {
334
            return childBL;
×
335
        }
336

337
        public QuadTreeNode bottomRightChild() {
338
            return childBR;
×
339
        }
340

341
        public QuadTreeNode parent() {
342
            return parent;
×
343
        }
344

345
        public int count() {
346
            return size;
1✔
347
        }
348

349
        public boolean isEmptyLeaf() {
NEW
350
            return size == 0 && childTL == null;
×
351
        }
352

353
        public QuadTreeNode(Rect2D rect) {
354
            this(null, 0, rect);
1✔
355
        }
1✔
356

357
        private QuadTreeNode(QuadTreeNode parent, int level, Rect2D rect) {
1✔
358
            this.level = level;
1✔
359
            this.rect = rect;
1✔
360
            this.parent = parent;
1✔
361
        }
1✔
362

363
        private void add(NodeImpl item) {
364
            if (objects == null) {
1✔
365
                // Allocate initial array
366
                objects = new NodeImpl[maxObjectsPerNode / 16];
1✔
367
            }
368

369
            // Check if item is already in this node (avoid duplicates)
370
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
371
            if (spatialData.quadTreeNode == this && spatialData.arrayIndex >= 0) {
1✔
372
                return; // Already in this node
1✔
373
            }
374

375
            // Resize array if needed (can happen when at max depth or when objects don't
376
            // fit in children)
377
            if (objectCount >= objects.length) {
1✔
378
                NodeImpl[] newArray = new NodeImpl[objects.length * 2];
1✔
379
                System.arraycopy(objects, 0, newArray, 0, objects.length);
1✔
380
                objects = newArray;
1✔
381
            }
382

383
            // Add to array
384
            objects[objectCount] = item;
1✔
385
            spatialData.setQuadTreeNode(this);
1✔
386
            spatialData.setArrayIndex(objectCount);
1✔
387
            objectCount++;
1✔
388

389
            // Update size and edge size for this node and all parents
390
            QuadTreeNode node = this;
1✔
391
            while (node != null) {
1✔
392
                node.size++;
1✔
393
                node = node.parent;
1✔
394
            }
395
        }
1✔
396

397
        private void remove(NodeImpl item) {
398
            if (objects != null && objectCount > 0) {
1✔
399
                SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
400
                int index = spatialData.arrayIndex;
1✔
401

402
                if (index >= 0 && index < objectCount && objects[index] == item) {
1✔
403
                    // Swap with last element for O(1) removal
404
                    objectCount--;
1✔
405
                    NodeImpl lastItem = objects[objectCount];
1✔
406
                    objects[index] = lastItem;
1✔
407
                    objects[objectCount] = null;
1✔
408

409
                    // Update the moved item's index
410
                    if (lastItem != null && index < objectCount) {
1✔
411
                        lastItem.getSpatialData().setArrayIndex(index);
1✔
412
                    }
413

414
                    // Clear removed item's data
415
                    spatialData.clear();
1✔
416

417
                    // Update size
418
                    QuadTreeNode node = this;
1✔
419
                    while (node != null) {
1✔
420
                        node.size--;
1✔
421
                        node = node.parent;
1✔
422
                    }
423
                }
424
            }
425
        }
1✔
426

427
        private int objectCount() {
428
            return size;
1✔
429
        }
430

431
        private void subdivide() {
432
            // We've reached capacity, subdivide...
433
            final float minX = rect.minX;
1✔
434
            final float halfX = (rect.minX + rect.maxX) / 2;
1✔
435
            final float maxX = rect.maxX;
1✔
436

437
            final float minY = rect.minY;
1✔
438
            final float halfY = (rect.minY + rect.maxY) / 2;
1✔
439
            final float maxY = rect.maxY;
1✔
440

441
            childTL = new QuadTreeNode(this, level + 1, new Rect2D(minX, minY, halfX, halfY));
1✔
442
            childTR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, minY, maxX, halfY));
1✔
443
            childBL = new QuadTreeNode(this, level + 1, new Rect2D(minX, halfY, halfX, maxY));
1✔
444
            childBR = new QuadTreeNode(this, level + 1, new Rect2D(halfX, halfY, maxX, maxY));
1✔
445

446
            // Keep track of objects that couldn't be moved
447
            NodeImpl[] remainingObjects = new NodeImpl[objectCount];
1✔
448
            int remainingCount = 0;
1✔
449

450
            // If they're completely contained by the quad, bump objects down
451
            for (int i = 0; i < objectCount; i++) {
1✔
452
                NodeImpl obj = objects[i];
1✔
453
                QuadTreeNode destTree = getDestinationTree(obj);
1✔
454
                if (destTree != this) {
1✔
455
                    // Insert to the appropriate tree
456
                    destTree.insert(obj);
1✔
457

458
                    // Update size
459
                    QuadTreeNode node = this;
1✔
460
                    while (node != null) {
1✔
461
                        node.size--;
1✔
462
                        node = node.parent;
1✔
463
                    }
464
                } else {
1✔
465
                    // Keep this object in the current node
466
                    remainingObjects[remainingCount] = obj;
1✔
467
                    obj.getSpatialData().setArrayIndex(remainingCount);
1✔
468
                    remainingCount++;
1✔
469
                }
470
            }
471

472
            // Update this node's object array
473
            for (int i = 0; i < remainingCount; i++) {
1✔
474
                objects[i] = remainingObjects[i];
1✔
475
            }
476
            for (int i = remainingCount; i < objectCount; i++) {
1✔
477
                objects[i] = null;
1✔
478
            }
479
            objectCount = remainingCount;
1✔
480
        }
1✔
481

482
        private QuadTreeNode getDestinationTree(NodeImpl item) {
483
            // If a child can't contain an object, it will live in this Quad
484
            final QuadTreeNode destTree;
485

486
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
487
            final float minX = spatialData.minX;
1✔
488
            final float minY = spatialData.minY;
1✔
489
            final float maxX = spatialData.maxX;
1✔
490
            final float maxY = spatialData.maxY;
1✔
491

492
            if (childTL.quadRect().contains(minX, minY, maxX, maxY)) {
1✔
493
                destTree = childTL;
1✔
494
            } else if (childTR.quadRect().contains(minX, minY, maxX, maxY)) {
1✔
495
                destTree = childTR;
1✔
496
            } else if (childBL.quadRect().contains(minX, minY, maxX, maxY)) {
1✔
497
                destTree = childBL;
1✔
498
            } else if (childBR.quadRect().contains(minX, minY, maxX, maxY)) {
1✔
499
                destTree = childBR;
1✔
500
            } else {
501
                destTree = this;
1✔
502
            }
503

504
            return destTree;
1✔
505
        }
506

507
        private void relocate(NodeImpl item) {
508
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
509

510
            // Are we still inside our parent?
511
            if (quadRect().contains(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) {
1✔
512
                // Good, have we moved inside any of our children?
513
                if (childTL != null) {
1✔
514
                    QuadTreeNode dest = getDestinationTree(item);
1✔
515
                    if (spatialData.quadTreeNode != dest) {
1✔
516
                        // Delete the item from this quad and add it to our
517
                        // child
518
                        // Note: Do NOT clean during this call, it can
519
                        // potentially delete our destination quad
520
                        QuadTreeNode formerOwner = spatialData.quadTreeNode;
1✔
521
                        delete(item, false);
1✔
522
                        dest.insert(item);
1✔
523

524
                        // Clean up ourselves
525
                        formerOwner.cleanUpwards();
1✔
526
                    }
527
                }
1✔
528
            } else {
529
                // We don't fit here anymore, move up, if we can
530
                if (parent != null) {
1✔
531
                    parent.relocate(item);
1✔
532
                }
533
            }
534
        }
1✔
535

536
        private void cleanUpwards() {
537
            if (childTL != null) {
1✔
538
                // If all the children are empty leaves, delete all the children
539
                if (childTL.isEmptyLeaf() && childTR.isEmptyLeaf() && childBL.isEmptyLeaf() && childBR.isEmptyLeaf()) {
×
540
                    childTL = null;
×
541
                    childTR = null;
×
542
                    childBL = null;
×
543
                    childBR = null;
×
544

545
                    if (parent != null && count() == 0) {
×
546
                        parent.cleanUpwards();
×
547
                    }
548
                }
549
            } else {
550
                // I could be one of 4 empty leaves, tell my parent to clean up
551
                if (parent != null && count() == 0) {
1✔
552
                    parent.cleanUpwards();
×
553
                }
554
            }
555
        }
1✔
556

557
        private void clear() {
558
            // clear out the children, if we have any
559
            if (childTL != null) {
1✔
560
                childTL.clear();
×
561
                childTR.clear();
×
562
                childBL.clear();
×
563
                childBR.clear();
×
564
            }
565

566
            // clear any objects at this level
567
            if (objects != null) {
1✔
568
                // Clear spatial data references for all objects
569
                for (int i = 0; i < objectCount; i++) {
1✔
570
                    if (objects[i] != null) {
1✔
571
                        SpatialNodeDataImpl spatialData = objects[i].getSpatialData();
1✔
572
                        spatialData.clear();
1✔
573
                        objects[i] = null;
1✔
574
                    }
575
                }
576
                objects = null;
1✔
577
                objectCount = 0;
1✔
578
            }
579

580
            // Reset size and edge size
581
            size = 0;
1✔
582

583
            // Set the children to null
584
            childTL = null;
1✔
585
            childTR = null;
1✔
586
            childBL = null;
1✔
587
            childBR = null;
1✔
588
        }
1✔
589

590
        private void delete(NodeImpl node, boolean clean) {
591
            SpatialNodeDataImpl spatialData = node.getSpatialData();
1✔
592
            if (spatialData.quadTreeNode != null) {
1✔
593
                if (spatialData.quadTreeNode == this) {
1✔
594
                    remove(node);
1✔
595
                    if (clean) {
1✔
596
                        cleanUpwards();
1✔
597
                    }
598
                } else {
599
                    spatialData.quadTreeNode.delete(node, clean);
1✔
600
                }
601
            }
602
        }
1✔
603

604
        private void insert(NodeImpl item) {
605
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
606
            // If this quad doesn't contain the items rectangle, do nothing,
607
            // unless we are the root
608
            if (!rect.contains(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) {
1✔
609
                if (parent == null) {
1✔
610
                    // This object is outside of the QuadTreeXNA bounds, we
611
                    // should add it at the root level
612
                    add(item);
1✔
613
                } else {
614
                    throw new IllegalStateException(
×
615
                            "We are not the root, and this object doesn't fit here. How did we get here?");
616
                }
617
            }
618

619
            if (objects == null || (childTL == null && (level >= maxLevels || objectCount + 1 <= maxObjectsPerNode))) {
1✔
620
                // If there's room to add the object, just add it
621
                add(item);
1✔
622
            } else {
623
                // No quads, create them and bump objects down where appropriate
624
                if (childTL == null) {
1✔
625
                    subdivide();
1✔
626
                }
627

628
                // Find out which tree this object should go in and add it there
629
                final QuadTreeNode destTree = getDestinationTree(item);
1✔
630
                if (destTree == this) {
1✔
631
                    add(item);
1✔
632
                } else {
633
                    destTree.insert(item);
1✔
634
                }
635
            }
636
        }
1✔
637

638
        private NodeIterable getNodes(Rect2D searchRect) {
639
            return new QuadTreeNodesIterable(searchRect);
1✔
640
        }
641

642
        private NodeIterable getNodes(Rect2D searchRect, boolean approximate) {
643
            return new QuadTreeNodesIterable(searchRect, approximate);
1✔
644
        }
645

646
        private NodeIterable getNodes(Rect2D searchRect, boolean approximate, Predicate<? super Node> predicate) {
647
            return new FilteredQuadTreeNodeIterable(searchRect, approximate, predicate);
1✔
648
        }
649

650
        private NodeIterable getAllNodes() {
651
            return new QuadTreeNodesIterable(null);
1✔
652
        }
653

654
        private NodeIterable getAllNodes(Predicate<? super Node> predicate) {
655
            return new FilteredQuadTreeNodeIterable(null, false, predicate);
1✔
656
        }
657

658
        private EdgeIterable getEdges(Rect2D searchRect) {
NEW
659
            return new QuadTreeEdgesIterable(searchRect);
×
660
        }
661

662
        private EdgeIterable getEdges(Rect2D searchRect, boolean approximate) {
663
            return new QuadTreeEdgesIterable(searchRect, approximate);
1✔
664
        }
665

666
        private EdgeIterable getEdges(Rect2D searchRect, boolean approximate, Predicate<? super Edge> predicate) {
667
            return new FilteredQuadTreeEdgeIterable(searchRect, approximate, predicate);
1✔
668
        }
669

670
        private EdgeIterable getAllEdges() {
671
            return new QuadTreeEdgesIterable(null);
1✔
672
        }
673

674
        private void update(NodeImpl item) {
675
            SpatialNodeDataImpl spatialData = item.getSpatialData();
1✔
676
            if (spatialData.quadTreeNode != null) {
1✔
677
                spatialData.quadTreeNode.relocate(item);
1✔
678
            } else {
679
                relocate(item);
×
680
            }
681
        }
1✔
682

683
        private int getDepth() {
684
            int maxLevel = level;
1✔
685
            if (childTL != null) {
1✔
686
                maxLevel = Math.max(maxLevel, childTL.getDepth());
1✔
687
                maxLevel = Math.max(maxLevel, childBR.getDepth());
1✔
688
                maxLevel = Math.max(maxLevel, childTR.getDepth());
1✔
689
                maxLevel = Math.max(maxLevel, childBL.getDepth());
1✔
690
            }
691
            return maxLevel;
1✔
692
        }
693

694
        private int getNodeCount(boolean withObjects) {
695
            int count = 1; // Count this node
1✔
696

697
            // If withObjects is true, only count nodes that have objects
698
            if (withObjects && (objects == null || objectCount == 0)) {
1✔
699
                count = 0;
1✔
700
            }
701

702
            // Recursively count children
703
            if (childTL != null) {
1✔
704
                count += childTL.getNodeCount(withObjects);
1✔
705
                count += childTR.getNodeCount(withObjects);
1✔
706
                count += childBL.getNodeCount(withObjects);
1✔
707
                count += childBR.getNodeCount(withObjects);
1✔
708
            }
709

710
            return count;
1✔
711
        }
712

713
        public void toString(StringBuilder sb) {
714
            for (int i = 0; i < level; i++) {
×
715
                sb.append("  ");
×
716
            }
717
            sb.append(rect.toString()).append('\n');
×
718

719
            if (objects != null) {
×
NEW
720
                for (int j = 0; j <= level; j++) {
×
NEW
721
                    sb.append("  ");
×
722
                }
723

NEW
724
                sb.append(objectCount).append(" objects \n");
×
725
            }
726

727
            if (childTL != null) {
×
728
                childTL.toString(sb);
×
729
                childTR.toString(sb);
×
730
                childBL.toString(sb);
×
731
                childBR.toString(sb);
×
732
            }
733
        }
×
734
    }
735

736
    private class FilteredQuadTreeNodeIterable extends QuadTreeNodesIterable {
737

738
        private final Predicate<? super Node> predicate;
739

740
        public FilteredQuadTreeNodeIterable(Rect2D searchRect, boolean approximate, Predicate<? super Node> predicate) {
1✔
741
            super(searchRect, approximate);
1✔
742
            this.predicate = predicate;
1✔
743
        }
1✔
744

745
        @Override
746
        public Iterator<Node> iterator() {
747
            return new QuadTreeNodesIterator(quadTreeRoot, searchRect, approximate, predicate);
1✔
748
        }
749

750
        @Override
751
        public Spliterator<Node> spliterator() {
752
            return new FilteredQuadTreeNodesSpliterator(quadTreeRoot, searchRect, approximate, predicate);
1✔
753
        }
754
    }
755

756
    private class QuadTreeNodesIterable implements NodeIterable {
757

758
        protected final Rect2D searchRect;
759
        protected final boolean approximate;
760

761
        public QuadTreeNodesIterable(Rect2D searchRect) {
762
            this(searchRect, GraphStoreConfiguration.SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH);
1✔
763
        }
1✔
764

765
        public QuadTreeNodesIterable(Rect2D searchRect, boolean approximate) {
1✔
766
            this.searchRect = searchRect;
1✔
767
            this.approximate = approximate;
1✔
768
        }
1✔
769

770
        @Override
771
        public Iterator<Node> iterator() {
772
            return new QuadTreeNodesIterator(quadTreeRoot, searchRect, approximate);
1✔
773
        }
774

775
        @Override
776
        public Spliterator<Node> spliterator() {
777
            return new QuadTreeNodesSpliterator(quadTreeRoot, searchRect, approximate);
1✔
778
        }
779

780
        @Override
781
        public Node[] toArray() {
782
            return toCollection().toArray(new Node[0]);
1✔
783
        }
784

785
        @Override
786
        public Collection<Node> toCollection() {
787
            final List<Node> list = new ArrayList<>();
1✔
788

789
            for (Node node : this) {
1✔
790
                list.add(node);
1✔
791
            }
1✔
792

793
            return list;
1✔
794
        }
795

796
        @Override
797
        public Set<Node> toSet() {
798
            final Set<Node> set = new HashSet<>();
1✔
799

800
            for (Node node : this) {
1✔
801
                set.add(node);
1✔
802
            }
1✔
803

804
            return set;
1✔
805
        }
806

807
        @Override
808
        public void doBreak() {
809
            readUnlock();
×
810
        }
×
811
    }
812

813
    private class FilteredQuadTreeEdgeIterable extends QuadTreeEdgesIterable {
814

815
        private final Predicate<? super Edge> predicate;
816

817
        public FilteredQuadTreeEdgeIterable(Rect2D searchRect, boolean approximate, Predicate<? super Edge> predicate) {
1✔
818
            super(searchRect, approximate);
1✔
819
            this.predicate = predicate;
1✔
820
        }
1✔
821

822
        @Override
823
        public Iterator<Edge> iterator() {
824
            return new QuadTreeEdgesIterator(quadTreeRoot, searchRect, approximate, predicate);
1✔
825
        }
826

827
        @Override
828
        public Spliterator<Edge> spliterator() {
829
            HashSet<QuadTreeNode> overlappingNodes = new HashSet<>();
1✔
830
            int nodeCount = collectOverlapping(quadTreeRoot, searchRect, overlappingNodes);
1✔
831
            if (useDirectIterator(nodeCount)) {
1✔
832
                return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingNodes, predicate);
1✔
833
            }
834
            // Use local iterator
NEW
835
            return new FilteredQuadTreeEdgesSpliterator(quadTreeRoot, searchRect, approximate, predicate);
×
836
        }
837
    }
838

839
    private class QuadTreeEdgesIterable implements EdgeIterable {
840

841
        protected final Rect2D searchRect;
842
        protected final boolean approximate;
843

844
        public QuadTreeEdgesIterable(Rect2D searchRect) {
845
            this(searchRect, GraphStoreConfiguration.SPATIAL_INDEX_APPROXIMATE_AREA_SEARCH);
1✔
846
        }
1✔
847

848
        public QuadTreeEdgesIterable(Rect2D searchRect, boolean approximate) {
1✔
849
            this.searchRect = searchRect;
1✔
850
            this.approximate = approximate;
1✔
851
        }
1✔
852

853
        @Override
854
        public Iterator<Edge> iterator() {
855
            return new QuadTreeEdgesIterator(quadTreeRoot, searchRect, approximate);
1✔
856
        }
857

858
        protected boolean useDirectIterator(int nodeCount) {
859
            return (float) nodeCount / quadTreeRoot.size > GraphStoreConfiguration.SPATIAL_INDEX_LOCAL_ITERATOR_THRESHOLD;
1✔
860
        }
861

862
        @Override
863
        public Spliterator<Edge> spliterator() {
864
            if (searchRect == null) {
1✔
865
                // Special case: all edges
866
                return new QuadTreeGlobalEdgesSpliterator(null, approximate, null, null);
1✔
867
            }
868
            HashSet<QuadTreeNode> overlappingNodes = new HashSet<>();
1✔
869
            int nodeCount = collectOverlapping(quadTreeRoot, searchRect, overlappingNodes);
1✔
870
            if (approximate && nodeCount == quadTreeRoot.size) {
1✔
871
                // Optimisation: approximate search and all nodes overlapping, so just return
872
                // all edges
873
                return new QuadTreeGlobalEdgesSpliterator(null, true, null, null);
1✔
874
            } else if (useDirectIterator(nodeCount)) {
1✔
875
                return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingNodes, null);
1✔
876
            }
877
            // Use local iterator
878
            return new QuadTreeEdgesSpliterator(quadTreeRoot, searchRect, approximate);
1✔
879
        }
880

881
        @Override
882
        public Edge[] toArray() {
NEW
883
            return toCollection().toArray(new Edge[0]);
×
884
        }
885

886
        @Override
887
        public Collection<Edge> toCollection() {
888
            final List<Edge> list = new ArrayList<>();
1✔
889

890
            for (Edge edge : this) {
1✔
891
                list.add(edge);
1✔
892
            }
1✔
893

894
            return list;
1✔
895
        }
896

897
        @Override
898
        public Set<Edge> toSet() {
899
            final Set<Edge> set = new HashSet<>();
1✔
900

901
            for (Edge edge : this) {
1✔
902
                set.add(edge);
1✔
903
            }
1✔
904

905
            return set;
1✔
906
        }
907

908
        @Override
909
        public void doBreak() {
NEW
910
            readUnlock();
×
NEW
911
        }
×
912

913
    }
914

915
    private class QuadTreeEdgesIterator implements Iterator<Edge> {
916

917
        private final EdgeStore.EdgeInOutMultiIterator edgeIterator;
918
        private final Predicate<? super Edge> predicate;
919
        private boolean finished = false;
1✔
920
        private Edge next;
921

922
        public QuadTreeEdgesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) {
923
            this(root, searchRect, approximate, null);
1✔
924
        }
1✔
925

926
        public QuadTreeEdgesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate<? super Edge> predicate) {
1✔
927
            this.predicate = predicate;
1✔
928
            readLock();
1✔
929

930
            // Create a node iterator for the quad tree
931
            final QuadTreeNodesIterator nodeIterator = new QuadTreeNodesIterator(root, searchRect, approximate);
1✔
932

933
            // Create the edge iterator using the EdgeStore method
934
            this.edgeIterator = graphStore.edgeStore.edgeIterator(new Iterator<>() {
1✔
935
                @Override
936
                public boolean hasNext() {
937
                    return nodeIterator.hasNext();
1✔
938
                }
939

940
                @Override
941
                public NodeImpl next() {
942
                    return nodeIterator.next();
1✔
943
                }
944
            }, true);
945
        }
1✔
946

947
        @Override
948
        public boolean hasNext() {
949
            if (finished) {
1✔
NEW
950
                return false;
×
951
            }
952

953
            if (next != null) {
1✔
NEW
954
                return true;
×
955
            }
956

957
            // Look for next edge that passes predicate
958
            while (edgeIterator != null && edgeIterator.hasNext()) {
1✔
959
                Edge edge = edgeIterator.next();
1✔
960
                if (predicate == null || predicate.test(edge)) {
1✔
961
                    next = edge;
1✔
962
                    return true;
1✔
963
                }
964
            }
1✔
965

966
            readUnlock();
1✔
967
            finished = true;
1✔
968
            return false;
1✔
969
        }
970

971
        @Override
972
        public Edge next() {
973
            if (next == null && !hasNext()) {
1✔
NEW
974
                throw new IllegalStateException("No next available!");
×
975
            }
976

977
            Edge result = next;
1✔
978
            next = null;
1✔
979
            return result;
1✔
980
        }
981
    }
982

983
    private class QuadTreeNodesIterator implements Iterator<Node> {
984

985
        private final Rect2D searchRect;
986
        private final boolean approximate;
987
        private final Predicate<? super Node> predicate;
988
        private final Deque<QuadTreeNode> nodesStack = new ArrayDeque<>();
1✔
989
        private final Deque<Boolean> fullyContainedStack = new ArrayDeque<>();
1✔
990

991
        // Current:
992
        private Iterator<NodeImpl> currentIterator;
993
        private boolean currentFullyContained;
994
        private boolean finished = false;
1✔
995

996
        private NodeImpl next;
997

998
        public QuadTreeNodesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) {
999
            this(root, searchRect, approximate, null);
1✔
1000
        }
1✔
1001

1002
        public QuadTreeNodesIterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate<? super Node> predicate) {
1✔
1003
            this.searchRect = searchRect;
1✔
1004
            this.approximate = approximate;
1✔
1005
            this.predicate = predicate;
1✔
1006

1007
            readLock();
1✔
1008

1009
            // Null rect means get all
1010
            currentFullyContained = searchRect == null;
1✔
1011

1012
            // We always add the root and don't test for the root being fully
1013
            // contained, to correctly handle the case of nodes out of the quad
1014
            // tree bounds
1015
            addChildrenToVisit(root, currentFullyContained);
1✔
1016
            currentIterator = root.objects != null ? new ArrayIterator(root.objects, root.objectCount) : null;
1✔
1017
        }
1✔
1018

1019
        private void addChildrenToVisit(QuadTreeNode quadTreeNode, boolean fullyContained) {
1020
            if (quadTreeNode.childTL != null) {
1✔
1021
                nodesStack.push(quadTreeNode.childBR);
1✔
1022
                nodesStack.push(quadTreeNode.childBL);
1✔
1023
                nodesStack.push(quadTreeNode.childTR);
1✔
1024
                nodesStack.push(quadTreeNode.childTL);
1✔
1025

1026
                fullyContainedStack.push(fullyContained);
1✔
1027
                fullyContainedStack.push(fullyContained);
1✔
1028
                fullyContainedStack.push(fullyContained);
1✔
1029
                fullyContainedStack.push(fullyContained);
1✔
1030
            }
1031
        }
1✔
1032

1033
        @Override
1034
        public boolean hasNext() {
1035
            if (finished) {
1✔
1036
                return false;
1✔
1037
            }
1038

1039
            if (next != null) {
1✔
1040
                return true;
×
1041
            }
1042

1043
            while (currentIterator != null || !nodesStack.isEmpty()) {
1✔
1044
                if (currentIterator != null) {
1✔
1045
                    while (currentIterator.hasNext()) {
1✔
1046
                        final NodeImpl elem = currentIterator.next();
1✔
1047

1048
                        // First check spatial conditions
1049
                        boolean spatialMatch;
1050
                        if (approximate || currentFullyContained) {
1✔
1051
                            // In approximate mode or when fully contained, include all objects
1052
                            spatialMatch = true;
1✔
1053
                        } else {
1054
                            // In exact mode, check intersection
1055
                            final SpatialNodeDataImpl spatialData = elem.getSpatialData();
1✔
1056
                            spatialMatch = searchRect
1✔
1057
                                    .intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY);
1✔
1058
                        }
1059

1060
                        // If spatial conditions are met, check predicate
1061
                        if (spatialMatch && (predicate == null || predicate.test(elem))) {
1✔
1062
                            next = elem;
1✔
1063
                            return true;
1✔
1064
                        }
1065
                    }
1✔
1066

1067
                    currentIterator = null;
1✔
1068
                } else {
1069
                    final QuadTreeNode pointer = nodesStack.pop();
1✔
1070

1071
                    currentFullyContained = fullyContainedStack.pop() || searchRect.contains(pointer.rect);
1✔
1072

1073
                    if (currentFullyContained || pointer.rect.intersects(searchRect)) {
1✔
1074
                        addChildrenToVisit(pointer, currentFullyContained);
1✔
1075
                        currentIterator = pointer.objects != null
1✔
1076
                                ? new ArrayIterator(pointer.objects, pointer.objectCount) : null;
1✔
1077
                    } else {
1078
                        currentIterator = null;
1✔
1079
                    }
1080
                }
1✔
1081
            }
1082

1083
            readUnlock();
1✔
1084
            finished = true;
1✔
1085
            return false;
1✔
1086
        }
1087

1088
        @Override
1089
        public NodeImpl next() {
1090
            if (next == null) {
1✔
1091
                throw new IllegalStateException("No next available!");
×
1092
            }
1093

1094
            final NodeImpl node = next;
1✔
1095

1096
            next = null;
1✔
1097
            return node;
1✔
1098
        }
1099

1100
    }
1101

1102
    // Helper class to iterate over array elements
1103
    private static class ArrayIterator implements Iterator<NodeImpl> {
1104
        private final NodeImpl[] array;
1105
        private final int size;
1106
        private final Predicate<? super NodeImpl> predicate;
1107
        private int index = 0;
1✔
1108
        private NodeImpl next;
1109

1110
        public ArrayIterator(NodeImpl[] array, int size) {
1111
            this(array, size, null);
1✔
1112
        }
1✔
1113

1114
        public ArrayIterator(NodeImpl[] array, int size, Predicate<? super NodeImpl> predicate) {
1✔
1115
            this.array = array;
1✔
1116
            this.size = size;
1✔
1117
            this.predicate = predicate;
1✔
1118
        }
1✔
1119

1120
        @Override
1121
        public boolean hasNext() {
1122
            if (next != null) {
1✔
NEW
1123
                return true;
×
1124
            }
1125

1126
            // Find next element that passes predicate
1127
            while (index < size) {
1✔
1128
                NodeImpl candidate = array[index++];
1✔
1129
                if (predicate == null || predicate.test(candidate)) {
1✔
1130
                    next = candidate;
1✔
1131
                    return true;
1✔
1132
                }
NEW
1133
            }
×
1134
            return false;
1✔
1135
        }
1136

1137
        @Override
1138
        public NodeImpl next() {
1139
            if (next == null && !hasNext()) {
1✔
NEW
1140
                throw new IllegalStateException("No more elements");
×
1141
            }
1142
            NodeImpl result = next;
1✔
1143
            next = null;
1✔
1144
            return result;
1✔
1145
        }
1146
    }
1147

1148
    private abstract class AbstractQuadTreeSpliterator<T> implements Spliterator<T> {
1149
        protected final Rect2D searchRect;
1150
        protected final boolean approximate;
1151
        protected final Deque<QuadTreeNode> nodesStack = new ArrayDeque<>();
1✔
1152
        protected final Deque<Boolean> fullyContainedStack = new ArrayDeque<>();
1✔
1153

1154
        protected final int expectedVersion;
1155
        protected Iterator<?> currentIterator;
1156
        protected boolean currentFullyContained;
1157
        protected T next;
1158
        protected int remainingSize;
1159

1160
        protected AbstractQuadTreeSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) {
1✔
1161
            this.searchRect = searchRect;
1✔
1162
            this.approximate = approximate;
1✔
1163
            this.expectedVersion = version;
1✔
1164

1165
            // Null rect means get all
1166
            currentFullyContained = searchRect == null;
1✔
1167

1168
            // Initialize with root
1169
            addNode(root, currentFullyContained);
1✔
1170
            currentIterator = createIteratorForNode(root);
1✔
1171

1172
            // For SIZED characteristic, we need exact count
1173
            if (searchRect == null) {
1✔
1174
                // Getting all elements, so we can use the maintained size
1175
                remainingSize = root.size;
1✔
1176
            } else if (approximate) {
1✔
1177
                // In approximate mode, count all elements in intersecting quadrants
1178
                remainingSize = countNodesInRectApproximate(root, searchRect);
1✔
1179
            } else {
1180
                // Need to count elements in the search rect
1181
                remainingSize = countNodesInRect(root, searchRect);
1✔
1182
            }
1183
        }
1✔
1184

1185
        protected AbstractQuadTreeSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) {
1✔
1186
            this.searchRect = searchRect;
1✔
1187
            this.approximate = approximate;
1✔
1188
            this.expectedVersion = expectedVersion;
1✔
1189
            this.remainingSize = size;
1✔
1190
            this.currentFullyContained = fullyContained;
1✔
1191

1192
            if (node != null) {
1✔
NEW
1193
                addNode(node, fullyContained);
×
NEW
1194
                currentIterator = createIteratorForNode(node);
×
1195
            } else {
1196
                currentIterator = null;
1✔
1197
            }
1198
        }
1✔
1199

1200
        protected void checkForComodification() {
1201
            if (version != expectedVersion) {
1✔
NEW
1202
                throw new ConcurrentModificationException();
×
1203
            }
1204
        }
1✔
1205

1206
        protected void addNode(QuadTreeNode node, boolean fullyContained) {
1207
            if (node.childTL != null) {
1✔
1208
                nodesStack.push(node.childBR);
1✔
1209
                nodesStack.push(node.childBL);
1✔
1210
                nodesStack.push(node.childTR);
1✔
1211
                nodesStack.push(node.childTL);
1✔
1212

1213
                fullyContainedStack.push(fullyContained);
1✔
1214
                fullyContainedStack.push(fullyContained);
1✔
1215
                fullyContainedStack.push(fullyContained);
1✔
1216
                fullyContainedStack.push(fullyContained);
1✔
1217
            }
1218
        }
1✔
1219

1220
        protected int countNodesInRect(QuadTreeNode node, Rect2D rect) {
1221
            int count = 0;
1✔
1222

1223
            // Count objects at this level
1224
            if (node.objects != null) {
1✔
1225
                for (int i = 0; i < node.objectCount; i++) {
1✔
1226
                    NodeImpl obj = node.objects[i];
1✔
1227
                    SpatialNodeDataImpl spatialData = obj.getSpatialData();
1✔
1228
                    if (rect.intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY)) {
1✔
1229
                        count++;
1✔
1230
                    }
1231
                }
1232
            }
1233

1234
            // Count in children if they intersect
1235
            if (node.childTL != null) {
1✔
1236
                if (rect.contains(node.childTL.rect)) {
1✔
NEW
1237
                    count += node.childTL.size;
×
1238
                } else if (node.childTL.rect.intersects(rect)) {
1✔
1239
                    count += countNodesInRect(node.childTL, rect);
1✔
1240
                }
1241

1242
                if (rect.contains(node.childTR.rect)) {
1✔
NEW
1243
                    count += node.childTR.size;
×
1244
                } else if (node.childTR.rect.intersects(rect)) {
1✔
1245
                    count += countNodesInRect(node.childTR, rect);
1✔
1246
                }
1247

1248
                if (rect.contains(node.childBL.rect)) {
1✔
NEW
1249
                    count += node.childBL.size;
×
1250
                } else if (node.childBL.rect.intersects(rect)) {
1✔
1251
                    count += countNodesInRect(node.childBL, rect);
1✔
1252
                }
1253

1254
                if (rect.contains(node.childBR.rect)) {
1✔
NEW
1255
                    count += node.childBR.size;
×
1256
                } else if (node.childBR.rect.intersects(rect)) {
1✔
1257
                    count += countNodesInRect(node.childBR, rect);
1✔
1258
                }
1259
            }
1260

1261
            return count;
1✔
1262
        }
1263

1264
        protected int countNodesInRectApproximate(QuadTreeNode node, Rect2D rect) {
1265
            int count = 0;
1✔
1266

1267
            // Count objects at this level if the node intersects
1268
            if (node.objects != null) {
1✔
1269
                count += node.objectCount;
1✔
1270
            }
1271

1272
            // Count in children if they intersect
1273
            if (node.childTL != null) {
1✔
1274
                if (rect.containsOrIntersects(node.childTL.rect)) {
1✔
1275
                    count += node.childTL.size;
1✔
1276
                }
1277
                if (rect.containsOrIntersects(node.childTR.rect)) {
1✔
NEW
1278
                    count += node.childTR.size;
×
1279
                }
1280
                if (rect.containsOrIntersects(node.childBL.rect)) {
1✔
NEW
1281
                    count += node.childBL.size;
×
1282
                }
1283
                if (rect.containsOrIntersects(node.childBR.rect)) {
1✔
NEW
1284
                    count += node.childBR.size;
×
1285
                }
1286
            }
1287

1288
            return count;
1✔
1289
        }
1290

1291
        @Override
1292
        public boolean tryAdvance(Consumer<? super T> action) {
1293
            checkForComodification();
1✔
1294

1295
            if (next != null || findNext()) {
1✔
1296
                action.accept(next);
1✔
1297
                next = null;
1✔
1298
                remainingSize--;
1✔
1299
                return true;
1✔
1300
            }
1301
            return false;
1✔
1302
        }
1303

1304
        protected abstract boolean findNext();
1305

1306
        protected abstract Iterator<?> createIteratorForNode(QuadTreeNode node);
1307

1308
        protected abstract boolean checkElementSpatialMatch(Object element);
1309

1310
        protected abstract AbstractQuadTreeSpliterator<T> createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size);
1311

1312
        @Override
1313
        public Spliterator<T> trySplit() {
1314
            checkForComodification();
1✔
1315

1316
            // Can only split if we have nodes on the stack
1317
            if (!nodesStack.isEmpty() && remainingSize > 1) {
1✔
1318
                // Take half of the remaining nodes from the stack
1319
                int nodesToSplit = Math.min(nodesStack.size() / 2, remainingSize / 2);
1✔
1320
                if (nodesToSplit > 0) {
1✔
1321
                    Deque<QuadTreeNode> splitNodes = new ArrayDeque<>();
1✔
1322
                    Deque<Boolean> splitContained = new ArrayDeque<>();
1✔
1323

1324
                    // Calculate size for the split
1325
                    int splitSize = 0;
1✔
1326

1327
                    // Move nodes to split queues and calculate their size
1328
                    for (int i = 0; i < nodesToSplit; i++) {
1✔
1329
                        QuadTreeNode node = nodesStack.removeLast();
1✔
1330
                        boolean contained = fullyContainedStack.removeLast();
1✔
1331
                        splitNodes.addFirst(node);
1✔
1332
                        splitContained.addFirst(contained);
1✔
1333

1334
                        if (searchRect == null || contained) {
1✔
1335
                            splitSize += node.size;
1✔
1336
                        } else if (approximate) {
1✔
1337
                            splitSize += countNodesInRectApproximate(node, searchRect);
1✔
1338
                        } else {
1339
                            splitSize += countNodesInRect(node, searchRect);
1✔
1340
                        }
1341
                    }
1342

1343
                    // Update our remaining size
1344
                    remainingSize -= splitSize;
1✔
1345

1346
                    // Create new spliterator for the split portion with empty initial state
1347
                    AbstractQuadTreeSpliterator<T> split = createSplitInstance(null, searchRect, approximate, expectedVersion, false, splitSize);
1✔
1348

1349
                    // Add all split nodes to the new spliterator's stack
1350
                    while (!splitNodes.isEmpty()) {
1✔
1351
                        split.nodesStack.push(splitNodes.removeLast());
1✔
1352
                        split.fullyContainedStack.push(splitContained.removeLast());
1✔
1353
                    }
1354

1355
                    return split;
1✔
1356
                }
1357
            }
1358
            return null;
1✔
1359
        }
1360

1361
        @Override
1362
        public long estimateSize() {
1363
            return remainingSize;
1✔
1364
        }
1365
    }
1366

1367
    private class QuadTreeNodesSpliterator extends AbstractQuadTreeSpliterator<Node> {
1368

1369
        public QuadTreeNodesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) {
1✔
1370
            super(root, searchRect, approximate);
1✔
1371
        }
1✔
1372

1373
        private QuadTreeNodesSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) {
1✔
1374
            super(node, searchRect, approximate, expectedVersion, fullyContained, size);
1✔
1375
        }
1✔
1376

1377
        @Override
1378
        protected Iterator<?> createIteratorForNode(QuadTreeNode node) {
1379
            return node.objects != null ? new ArrayIterator(node.objects, node.objectCount) : null;
1✔
1380
        }
1381

1382
        @Override
1383
        protected boolean checkElementSpatialMatch(Object element) {
1384
            NodeImpl elem = (NodeImpl) element;
1✔
1385
            if (approximate || currentFullyContained || searchRect == null) {
1✔
1386
                return true;
1✔
1387
            } else {
1388
                final SpatialNodeDataImpl spatialData = elem.getSpatialData();
1✔
1389
                return searchRect.intersects(spatialData.minX, spatialData.minY, spatialData.maxX, spatialData.maxY);
1✔
1390
            }
1391
        }
1392

1393
        @Override
1394
        protected AbstractQuadTreeSpliterator<Node> createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) {
1395
            return new QuadTreeNodesSpliterator(node, searchRect, approximate, expectedVersion, fullyContained, size);
1✔
1396
        }
1397

1398
        @Override
1399
        protected boolean findNext() {
1400
            while (currentIterator != null || !nodesStack.isEmpty()) {
1✔
1401
                if (currentIterator != null) {
1✔
1402
                    while (currentIterator.hasNext()) {
1✔
1403
                        final NodeImpl elem = (NodeImpl) currentIterator.next();
1✔
1404

1405
                        if (checkElementSpatialMatch(elem)) {
1✔
1406
                            next = elem;
1✔
1407
                            return true;
1✔
1408
                        }
1409
                    }
1✔
1410
                    currentIterator = null;
1✔
1411
                } else {
1412
                    final QuadTreeNode pointer = nodesStack.pop();
1✔
1413
                    currentFullyContained = fullyContainedStack
1✔
1414
                            .pop() || (searchRect != null && searchRect.contains(pointer.rect));
1✔
1415

1416
                    if (currentFullyContained || searchRect == null || pointer.rect.intersects(searchRect)) {
1✔
1417
                        addNode(pointer, currentFullyContained);
1✔
1418
                        currentIterator = createIteratorForNode(pointer);
1✔
1419
                    }
1420
                }
1✔
1421
            }
1422
            return false;
1✔
1423
        }
1424

1425
        @Override
1426
        public int characteristics() {
1427
            return DISTINCT | SIZED | SUBSIZED | NONNULL;
1✔
1428
        }
1429
    }
1430

1431
    private abstract static class FilteredSpliterator<T, S extends Spliterator<T>, P extends Spliterator<T>> implements Spliterator<T> {
1432
        protected final S parentSpliterator;
1433
        protected final Predicate<? super T> predicate;
1434
        protected final Object[] holder = new Object[1];
1✔
1435

1436
        protected FilteredSpliterator(S parentSpliterator, Predicate<? super T> predicate) {
1✔
1437
            this.parentSpliterator = parentSpliterator;
1✔
1438
            this.predicate = predicate;
1✔
1439
        }
1✔
1440

1441
        protected abstract P createSplitInstance(S splitParent, Predicate<? super T> predicate);
1442

1443
        protected abstract boolean testPredicate(T element);
1444

1445
        @Override
1446
        public boolean tryAdvance(Consumer<? super T> action) {
1447
            while (true) {
1448
                boolean advanced = parentSpliterator.tryAdvance(e -> holder[0] = e);
1✔
1449
                if (!advanced)
1✔
1450
                    return false;
1✔
1451
                @SuppressWarnings("unchecked")
1452
                T t = (T) holder[0];
1✔
1453
                if (testPredicate(t)) {
1✔
1454
                    action.accept(t);
1✔
1455
                    return true;
1✔
1456
                }
1457
            }
1✔
1458
        }
1459

1460
        @Override
1461
        @SuppressWarnings("unchecked")
1462
        public Spliterator<T> trySplit() {
1463
            S splitParent = (S) parentSpliterator.trySplit();
1✔
1464
            if (splitParent != null) {
1✔
NEW
1465
                return createSplitInstance(splitParent, predicate);
×
1466
            }
1467
            return null;
1✔
1468
        }
1469

1470
        @Override
1471
        public long estimateSize() {
1472
            return parentSpliterator.estimateSize();
1✔
1473
        }
1474

1475
        @Override
1476
        public int characteristics() {
1477
            return parentSpliterator.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED);
1✔
1478
        }
1479
    }
1480

1481
    private class FilteredQuadTreeNodesSpliterator extends FilteredSpliterator<Node, QuadTreeNodesSpliterator, FilteredQuadTreeNodesSpliterator> {
1482

1483
        public FilteredQuadTreeNodesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate<? super Node> predicate) {
1✔
1484
            super(new QuadTreeNodesSpliterator(root, searchRect, approximate), predicate);
1✔
1485
        }
1✔
1486

NEW
1487
        private FilteredQuadTreeNodesSpliterator(QuadTreeNodesSpliterator parentSpliterator, Predicate<? super Node> predicate) {
×
NEW
1488
            super(parentSpliterator, predicate);
×
NEW
1489
        }
×
1490

1491
        @Override
1492
        protected FilteredQuadTreeNodesSpliterator createSplitInstance(QuadTreeNodesSpliterator splitParent, Predicate<? super Node> predicate) {
NEW
1493
            return new FilteredQuadTreeNodesSpliterator(splitParent, predicate);
×
1494
        }
1495

1496
        @Override
1497
        protected boolean testPredicate(Node element) {
1498
            return predicate == null || predicate.test(element);
1✔
1499
        }
1500
    }
1501

1502
    private class QuadTreeEdgesSpliterator extends AbstractQuadTreeSpliterator<Edge> {
1503

1504
        public QuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect) {
NEW
1505
            this(root, searchRect, false);
×
NEW
1506
        }
×
1507

1508
        public QuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate) {
1✔
1509
            super(root, searchRect, approximate);
1✔
1510
        }
1✔
1511

1512
        private QuadTreeEdgesSpliterator(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) {
1✔
1513
            super(node, searchRect, approximate, expectedVersion, fullyContained, size);
1✔
1514
        }
1✔
1515

1516
        @Override
1517
        protected Iterator<?> createIteratorForNode(QuadTreeNode node) {
1518
            if (node.objects == null) {
1✔
NEW
1519
                return Collections.emptyIterator();
×
1520
            }
1521
            return graphStore.edgeStore.edgeIterator(new ArrayIterator(node.objects, node.objectCount), false);
1✔
1522
        }
1523

1524
        @Override
1525
        protected boolean checkElementSpatialMatch(Object element) {
1526
            Edge edge = (Edge) element;
1✔
1527
            if (approximate || currentFullyContained || searchRect == null) {
1✔
1528
                return true;
1✔
1529
            } else {
1530
                // In exact mode, check if edge endpoints intersect with search rect
1531
                Node source = edge.getSource();
1✔
1532
                Node target = edge.getTarget();
1✔
1533
                SpatialNodeDataImpl sourceSpatialData = ((NodeImpl) source).getSpatialData();
1✔
1534
                SpatialNodeDataImpl targetSpatialData = ((NodeImpl) target).getSpatialData();
1✔
1535

1536
                return (sourceSpatialData != null && searchRect
1✔
1537
                        .intersects(sourceSpatialData.minX, sourceSpatialData.minY, sourceSpatialData.maxX, sourceSpatialData.maxY)) || (targetSpatialData != null && searchRect
1✔
1538
                                .intersects(targetSpatialData.minX, targetSpatialData.minY, targetSpatialData.maxX, targetSpatialData.maxY));
1✔
1539
            }
1540
        }
1541

1542
        @Override
1543
        protected AbstractQuadTreeSpliterator<Edge> createSplitInstance(QuadTreeNode node, Rect2D searchRect, boolean approximate, int expectedVersion, boolean fullyContained, int size) {
1544
            return new QuadTreeEdgesSpliterator(node, searchRect, approximate, expectedVersion, fullyContained, size);
1✔
1545
        }
1546

1547
        @Override
1548
        protected boolean findNext() {
1549
            while (currentIterator != null || !nodesStack.isEmpty()) {
1✔
1550
                if (currentIterator != null) {
1✔
1551
                    if (currentIterator.hasNext()) {
1✔
1552
                        Edge edge = (Edge) currentIterator.next();
1✔
1553

1554
                        if (checkElementSpatialMatch(edge)) {
1✔
1555
                            next = edge;
1✔
1556
                            return true;
1✔
1557
                        }
NEW
1558
                    } else {
×
1559
                        currentIterator = null;
1✔
1560
                    }
1561
                } else {
1562
                    final QuadTreeNode pointer = nodesStack.pop();
1✔
1563
                    currentFullyContained = fullyContainedStack
1✔
1564
                            .pop() || (searchRect != null && searchRect.contains(pointer.rect));
1✔
1565

1566
                    if (currentFullyContained || searchRect == null || pointer.rect.intersects(searchRect)) {
1✔
1567
                        addNode(pointer, currentFullyContained);
1✔
1568
                        currentIterator = createIteratorForNode(pointer);
1✔
1569
                    }
1570
                }
1✔
1571
            }
1572
            return false;
1✔
1573
        }
1574

1575
        @Override
1576
        public int characteristics() {
1577
            return NONNULL;
1✔
1578
        }
1579
    }
1580

1581
    private class FilteredQuadTreeEdgesSpliterator extends FilteredSpliterator<Edge, QuadTreeEdgesSpliterator, FilteredQuadTreeEdgesSpliterator> {
1582

NEW
1583
        public FilteredQuadTreeEdgesSpliterator(QuadTreeNode root, Rect2D searchRect, boolean approximate, Predicate<? super Edge> predicate) {
×
NEW
1584
            super(new QuadTreeEdgesSpliterator(root, searchRect, approximate), predicate);
×
NEW
1585
        }
×
1586

NEW
1587
        private FilteredQuadTreeEdgesSpliterator(QuadTreeEdgesSpliterator parentSpliterator, Predicate<? super Edge> predicate) {
×
NEW
1588
            super(parentSpliterator, predicate);
×
NEW
1589
        }
×
1590

1591
        @Override
1592
        protected FilteredQuadTreeEdgesSpliterator createSplitInstance(QuadTreeEdgesSpliterator splitParent, Predicate<? super Edge> predicate) {
NEW
1593
            return new FilteredQuadTreeEdgesSpliterator(splitParent, predicate);
×
1594
        }
1595

1596
        @Override
1597
        protected boolean testPredicate(Edge element) {
NEW
1598
            return predicate == null || predicate.test(element);
×
1599
        }
1600
    }
1601

1602
    /**
1603
     * A spliterator that iterates through all edges in the EdgeStore and filters
1604
     * them based on whether their nodes belong to quad tree nodes that overlap with
1605
     * a search rectangle. This approach iterates edges directly rather than
1606
     * iterating nodes first.
1607
     */
1608
    protected class QuadTreeGlobalEdgesSpliterator implements Spliterator<Edge> {
1609

1610
        private final Rect2D searchRect;
1611
        private final boolean approximate;
1612
        private final Set<QuadTreeNode> overlappingQuadNodes;
1613
        private final Spliterator<Edge> baseSpliterator;
1614
        private final int expectedVersion;
1615
        private final Predicate<? super Edge> additionalPredicate;
1616

1617
        public QuadTreeGlobalEdgesSpliterator(Rect2D searchRect, boolean approximate, Set<QuadTreeNode> overlappingQuadNodes, Predicate<? super Edge> additionalPredicate) {
1✔
1618
            this.searchRect = searchRect;
1✔
1619
            this.approximate = approximate;
1✔
1620
            this.additionalPredicate = additionalPredicate;
1✔
1621
            this.expectedVersion = version;
1✔
1622
            this.overlappingQuadNodes = overlappingQuadNodes;
1✔
1623

1624
            // Create the base spliterator from EdgeStore with our predicate
1625
            if (additionalPredicate == null) {
1✔
1626
                if (searchRect == null) {
1✔
1627
                    // No filtering needed, use the full spliterator
1628
                    this.baseSpliterator = graphStore.edgeStore.spliterator();
1✔
1629
                } else {
1630
                    // Only spatial filtering
1631
                    this.baseSpliterator = graphStore.edgeStore.newFilteredSpliterator(this::shouldIncludeEdge);
1✔
1632
                }
1633
            } else {
1634
                if (searchRect == null) {
1✔
NEW
1635
                    this.baseSpliterator = graphStore.edgeStore
×
NEW
1636
                            .newFilteredSpliterator(this::shouldIncludeEdgeAllWithPredicate);
×
1637
                } else {
1638
                    this.baseSpliterator = graphStore.edgeStore.newFilteredSpliterator(this::shouldIncludeEdge);
1✔
1639
                }
1640
            }
1641
        }
1✔
1642

1643
        private QuadTreeGlobalEdgesSpliterator(Rect2D searchRect, boolean approximate, Set<QuadTreeNode> overlappingQuadNodes, Spliterator<Edge> baseSpliterator, int expectedVersion, Predicate<? super Edge> additionalPredicate) {
1✔
1644
            this.searchRect = searchRect;
1✔
1645
            this.approximate = approximate;
1✔
1646
            this.overlappingQuadNodes = overlappingQuadNodes;
1✔
1647
            this.baseSpliterator = baseSpliterator;
1✔
1648
            this.expectedVersion = expectedVersion;
1✔
1649
            this.additionalPredicate = additionalPredicate;
1✔
1650
        }
1✔
1651

1652
        private boolean shouldIncludeEdgeAllWithPredicate(EdgeImpl edge) {
NEW
1653
            checkForComodification();
×
1654

NEW
1655
            return additionalPredicate.test(edge);
×
1656
        }
1657

1658
        /**
1659
         * Determines if an edge should be included based on spatial filtering criteria
1660
         */
1661
        private boolean shouldIncludeEdge(EdgeImpl edge) {
1662
            checkForComodification();
1✔
1663

1664
            boolean spatialMatch = false;
1✔
1665

1666
            SpatialNodeDataImpl sourceSpatialData = edge.source.getSpatialData();
1✔
1667
            SpatialNodeDataImpl targetSpatialData = edge.target.getSpatialData();
1✔
1668

1669
            if (sourceSpatialData != null && sourceSpatialData.quadTreeNode != null) {
1✔
1670
                spatialMatch = overlappingQuadNodes.contains(sourceSpatialData.quadTreeNode);
1✔
1671
            }
1672

1673
            if (!spatialMatch && targetSpatialData != null && targetSpatialData.quadTreeNode != null) {
1✔
1674
                // Only check target if source wasn't already overlapping
NEW
1675
                spatialMatch = overlappingQuadNodes.contains(targetSpatialData.quadTreeNode);
×
1676
            }
1677

1678
            // Apply additional predicate if provided
1679
            if (spatialMatch && (additionalPredicate == null || additionalPredicate.test(edge))) {
1✔
1680
                if (approximate) {
1✔
1681
                    return true;
1✔
1682
                } else {
1683
                    // In exact mode, check if edge endpoints intersect with search rect
1684
                    boolean sourceIntersects = sourceSpatialData != null && searchRect
1✔
1685
                            .intersects(sourceSpatialData.minX, sourceSpatialData.minY, sourceSpatialData.maxX, sourceSpatialData.maxY);
1✔
1686
                    boolean targetIntersects = targetSpatialData != null && searchRect
1✔
1687
                            .intersects(targetSpatialData.minX, targetSpatialData.minY, targetSpatialData.maxX, targetSpatialData.maxY);
1✔
1688
                    return sourceIntersects || targetIntersects;
1✔
1689
                }
1690
            }
1691
            return false;
1✔
1692
        }
1693

1694
        private void checkForComodification() {
1695
            if (expectedVersion != version) {
1✔
NEW
1696
                throw new ConcurrentModificationException();
×
1697
            }
1698
        }
1✔
1699

1700
        @Override
1701
        public boolean tryAdvance(Consumer<? super Edge> action) {
1702
            return baseSpliterator.tryAdvance(action);
1✔
1703
        }
1704

1705
        @Override
1706
        public Spliterator<Edge> trySplit() {
1707
            Spliterator<Edge> splitBase = baseSpliterator.trySplit();
1✔
1708
            if (splitBase == null) {
1✔
1709
                return null;
1✔
1710
            }
1711

1712
            return new QuadTreeGlobalEdgesSpliterator(searchRect, approximate, overlappingQuadNodes, splitBase,
1✔
1713
                    expectedVersion, additionalPredicate);
1714
        }
1715

1716
        @Override
1717
        public long estimateSize() {
1718
            return baseSpliterator.estimateSize();
1✔
1719
        }
1720

1721
        @Override
1722
        public int characteristics() {
1723
            return baseSpliterator.characteristics();
1✔
1724
        }
1725
    }
1726
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc