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

gephi / graphstore / #611

18 Oct 2026 12:55PM UTC coverage: 91.546% (-0.001%) from 91.547%
#611

push

web-flow
Check view existence under the lock and reject destroyed views in index getters (#320)

createView, destroyView and setTimeInterval checked the view before taking the write lock, so a racing destroyView could leave them copying stale state into a new view. The index getters created a view index for a destroyed view that was never deleted. setTimeInterval on the main view threw ClassCastException instead of IllegalArgumentException.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

13 of 14 new or added lines in 3 files covered. (92.86%)

12095 of 13212 relevant lines covered (91.55%)

0.92 hits per line

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

96.33
/src/main/java/org/gephi/graph/impl/TimeIndexStore.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 it.unimi.dsi.fastutil.ints.IntRBTreeSet;
19
import it.unimi.dsi.fastutil.ints.IntSortedSet;
20
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
21
import java.util.Iterator;
22
import java.util.Map;
23
import java.util.Map.Entry;
24
import org.gephi.graph.api.Edge;
25
import org.gephi.graph.api.Element;
26
import org.gephi.graph.api.Graph;
27
import org.gephi.graph.api.GraphView;
28
import org.gephi.graph.api.Node;
29
import org.gephi.graph.api.TimeIndex;
30
import org.gephi.graph.api.types.TimeMap;
31
import org.gephi.graph.api.types.TimeSet;
32
import org.gephi.graph.impl.utils.MapDeepEquals;
33

34
public abstract class TimeIndexStore<T extends Element, K, S extends TimeSet<K>, M extends TimeMap<K, ?>> {
35

36
    // Lock
37
    protected final TableLockImpl lock;
38
    // Element
39
    protected final Class<T> elementType;
40
    // Timestamp index management
41
    protected final Map<K, Integer> timeSortedMap;
42
    protected final IntSortedSet garbageQueue;
43
    protected int[] countMap;
44
    protected int length;
45
    // Index
46
    protected TimeIndexImpl mainIndex;
47
    protected final Map<GraphView, TimeIndexImpl> viewIndexes;
48

49
    protected TimeIndexStore(Class<T> type, TableLockImpl lock, boolean indexed, Map<K, Integer> sortedMap) {
1 ✔
50
        this.elementType = type;
1 ✔
51
        this.lock = lock;
1 ✔
52

53
        garbageQueue = new IntRBTreeSet();
1 ✔
54
        // Subclass
55
        timeSortedMap = sortedMap;
1 ✔
56
        countMap = new int[0];
1 ✔
57

58
        viewIndexes = indexed ? new Object2ObjectOpenHashMap<>() : null;
1 ✔
59
    }
1 ✔
60

61
    protected abstract void checkK(K k);
62

63
    protected abstract double getLow(K k);
64

65
    protected abstract TimeIndexImpl createIndex(boolean main);
66

67
    protected Integer add(K k) {
68
        checkK(k);
1 ✔
69

70
        lock();
1 ✔
71
        try {
72
            Integer id = timeSortedMap.get(k);
1 ✔
73
            if (id == null) {
1 ✔
74
                if (!garbageQueue.isEmpty()) {
1 ✔
75
                    id = garbageQueue.firstInt();
1 ✔
76
                    garbageQueue.remove(id);
1 ✔
77
                } else {
78
                    id = length++;
1 ✔
79
                }
80
                timeSortedMap.put(k, id);
1 ✔
81
                ensureArraySize(id);
1 ✔
82
                countMap[id] = 1;
1 ✔
83
            } else {
84
                countMap[id]++;
1 ✔
85
            }
86

87
            return id;
1 ✔
88
        } finally {
89
            unlock();
1 ✔
90
        }
91
    }
92

93
    public void add(K k, Element element) {
94
        lock();
1 ✔
95
        try {
96
            int timeIndex = add(k);
1 ✔
97

98
            if (mainIndex != null) {
1 ✔
99
                mainIndex.add(timeIndex, element);
1 ✔
100

101
                if (!viewIndexes.isEmpty()) {
1 ✔
102
                    for (Entry<GraphView, TimeIndexImpl> entry : viewIndexes.entrySet()) {
1 ✔
103
                        GraphViewImpl graphView = (GraphViewImpl) entry.getKey();
1 ✔
104
                        boolean node = element instanceof Node;
1 ✔
105
                        if (node ? graphView.containsNode((Node) element) : graphView.containsEdge((Edge) element)) {
1 ✔
106
                            entry.getValue().add(timeIndex, element);
×
107
                        }
108
                    }
1 ✔
109

110
                }
111
            }
112
        } finally {
113
            unlock();
1 ✔
114
        }
115
    }
1 ✔
116

117
    public void add(TimeMap<K, ?> timeMap) {
118
        for (K timeKey : timeMap.toKeysArray()) {
1 ✔
119
            add(timeKey);
1 ✔
120
        }
121
    }
1 ✔
122

123
    public void add(TimeSet<K> timeSet, Element element) {
124
        for (K timeKey : timeSet.toArray()) {
1 ✔
125
            add(timeKey, element);
1 ✔
126
        }
127
    }
1 ✔
128

129
    protected Integer remove(K k) {
130
        lock();
1 ✔
131
        try {
132
            checkK(k);
1 ✔
133

134
            Integer id = timeSortedMap.get(k);
1 ✔
135
            if (id != null) {
1 ✔
136
                if (--countMap[id] == 0) {
1 ✔
137
                    garbageQueue.add(id);
1 ✔
138
                    timeSortedMap.remove(k);
1 ✔
139
                }
140
            }
141
            return id;
1 ✔
142
        } finally {
143
            unlock();
1 ✔
144
        }
145
    }
146

147
    public void remove(K k, Element element) {
148
        lock();
1 ✔
149
        try {
150
            Integer timeIndex = remove(k);
1 ✔
151
            if (timeIndex == null) {
1 ✔
152
                return;
×
153
            }
154

155
            if (mainIndex != null) {
1 ✔
156
                mainIndex.remove(timeIndex, element);
1 ✔
157

158
                if (!viewIndexes.isEmpty()) {
1 ✔
159
                    for (Entry<GraphView, TimeIndexImpl> entry : viewIndexes.entrySet()) {
1 ✔
160
                        // Reads the view directly, the graph lock can't be taken while holding the table lock
161
                        GraphViewImpl graphView = (GraphViewImpl) entry.getKey();
1 ✔
162
                        boolean node = element instanceof Node;
1 ✔
163
                        if (node ? graphView.containsNode((Node) element) : graphView.containsEdge((Edge) element)) {
1 ✔
164
                            entry.getValue().remove(timeIndex, element);
1 ✔
165
                        }
166
                    }
1 ✔
167
                }
168
            }
169

170
        } finally {
171
            unlock();
1 ✔
172
        }
173
    }
1 ✔
174

175
    public void remove(M timeMap) {
176
        for (K timeKey : timeMap.toKeysArray()) {
1 ✔
177
            remove(timeKey);
1 ✔
178
        }
179
    }
1 ✔
180

181
    public void remove(S timeSet, Element element) {
182
        for (K timeKey : timeSet.toArray()) {
1 ✔
183
            remove(timeKey, element);
1 ✔
184
        }
185
    }
1 ✔
186

187
    public boolean contains(K k) {
188
        checkK(k);
1 ✔
189

190
        lock();
1 ✔
191
        try {
192
            return timeSortedMap.containsKey(k);
1 ✔
193
        } finally {
194
            unlock();
1 ✔
195
        }
196
    }
197

198
    public void index(Element element) {
199
        synchronized (element) {
1 ✔
200
            S timeSet = getTimeSet(element);
1 ✔
201
            lock();
1 ✔
202
            try {
203
                if (timeSet != null) {
1 ✔
204
                    add(timeSet, element);
1 ✔
205
                }
206
                for (Object val : element.getAttributes()) {
1 ✔
207
                    if (val instanceof TimeMap) {
1 ✔
208
                        TimeMap dynamicValue = (TimeMap) val;
1 ✔
209
                        add(dynamicValue);
1 ✔
210
                    }
211
                }
212
            } finally {
213
                unlock();
1 ✔
214
            }
215
        }
1 ✔
216
    }
1 ✔
217

218
    public void clear(Element element) {
219
        synchronized (element) {
1 ✔
220
            S timeSet = getTimeSet(element);
1 ✔
221
            lock();
1 ✔
222
            try {
223
                if (timeSet != null) {
1 ✔
224
                    remove(timeSet, element);
1 ✔
225
                }
226
                for (Object val : element.getAttributes()) {
1 ✔
227
                    if (val instanceof TimeMap) {
1 ✔
228
                        TimeMap dynamicValue = (TimeMap) val;
1 ✔
229
                        remove((M) dynamicValue);
1 ✔
230
                    }
231
                }
232
            } finally {
233
                unlock();
1 ✔
234
            }
235
        }
1 ✔
236
    }
1 ✔
237

238
    public void clear() {
239
        lock();
1 ✔
240
        try {
241
            timeSortedMap.clear();
1 ✔
242
            garbageQueue.clear();
1 ✔
243
            countMap = new int[0];
1 ✔
244
            length = 0;
1 ✔
245

246
            if (mainIndex != null) {
1 ✔
247
                mainIndex.clear();
1 ✔
248

249
                if (!viewIndexes.isEmpty()) {
1 ✔
250
                    for (TimeIndexImpl index : viewIndexes.values()) {
1 ✔
251
                        index.clear();
1 ✔
252
                    }
1 ✔
253
                }
254
            }
255
        } finally {
256
            unlock();
1 ✔
257
        }
258
    }
1 ✔
259

260
    public int size() {
261
        return timeSortedMap.size();
1 ✔
262
    }
263

264
    public TimeIndex getIndex(Graph graph) {
265
        GraphView view = graph.getView();
1 ✔
266
        if (view.isMainView()) {
1 ✔
267
            return mainIndex;
1 ✔
268
        }
269
        if (viewIndexes == null) {
1 ✔
270
            return null;
1 ✔
271
        }
272
        lock();
1 ✔
273
        try {
274
            TimeIndexImpl viewIndex = viewIndexes.get(view);
1 ✔
275
            if (viewIndex != null) {
1 ✔
276
                return viewIndex;
1 ✔
277
            }
278
        } finally {
279
            unlock();
1 ✔
280
        }
281
        // The graph lock is taken first, as indexing the view requires it
282
        graph.readLock();
1 ✔
283
        lock();
1 ✔
284
        try {
285
            if (((GraphViewImpl) view).isDestroyed()) {
1 ✔
NEW
286
                throw new IllegalArgumentException("The view doesn't exist");
×
287
            }
288
            TimeIndexImpl viewIndex = viewIndexes.get(view);
1 ✔
289
            if (viewIndex == null) {
1 ✔
290
                // TODO Make the auto-creation optional?
291
                viewIndex = createViewIndex(graph);
1 ✔
292
            }
293
            return viewIndex;
1 ✔
294
        } finally {
295
            unlock();
1 ✔
296
            graph.readUnlock();
1 ✔
297
        }
298
    }
299

300
    protected TimeIndexImpl createViewIndex(Graph graph) {
301
        if (graph.getView().isMainView()) {
1 ✔
302
            throw new IllegalArgumentException("Can't create a view index for the main view");
1 ✔
303
        }
304

305
        TimeIndexImpl viewIndex = createIndex(false);
1 ✔
306
        // TODO: Check view doesn't exist already
307
        viewIndexes.put(graph.getView(), viewIndex);
1 ✔
308

309
        indexView(graph);
1 ✔
310

311
        return viewIndex;
1 ✔
312
    }
313

314
    public void deleteViewIndex(Graph graph) {
315
        if (graph.getView().isMainView()) {
1 ✔
316
            throw new IllegalArgumentException("Can't delete a view index for the main view");
1 ✔
317
        }
318
        lock();
1 ✔
319
        try {
320
            TimeIndexImpl index = viewIndexes.remove(graph.getView());
1 ✔
321
            if (index != null) {
1 ✔
322
                index.clear();
1 ✔
323
            }
324
        } finally {
325
            unlock();
1 ✔
326
        }
327
    }
1 ✔
328

329
    public void indexView(Graph graph) {
330
        TimeIndexImpl viewIndex = viewIndexes.get(graph.getView());
1 ✔
331
        if (viewIndex != null) {
1 ✔
332
            graph.readLock();
1 ✔
333
            lock();
1 ✔
334
            try {
335
                Iterator<T> iterator = null;
1 ✔
336

337
                if (elementType.equals(Node.class)) {
1 ✔
338
                    iterator = (Iterator<T>) graph.getNodes().iterator();
1 ✔
339
                } else if (elementType.equals(Edge.class)) {
1 ✔
340
                    iterator = (Iterator<T>) graph.getEdges().iterator();
1 ✔
341
                }
342

343
                if (iterator != null) {
1 ✔
344
                    while (iterator.hasNext()) {
1 ✔
345
                        Element element = iterator.next();
1 ✔
346
                        S set = getTimeSet(element);
1 ✔
347
                        if (set != null) {
1 ✔
348
                            K[] ts = set.toArray();
1 ✔
349
                            int tsLength = ts.length;
1 ✔
350
                            for (int i = 0; i < tsLength; i++) {
1 ✔
351
                                Integer timestamp = timeSortedMap.get(ts[i]);
1 ✔
352
                                if (timestamp != null) {
1 ✔
353
                                    viewIndex.add(timestamp, element);
1 ✔
354
                                }
355
                            }
356
                        }
357
                    }
1 ✔
358
                }
359
            } finally {
360
                graph.readUnlock();
1 ✔
361
                unlock();
1 ✔
362
            }
363
        }
364
    }
1 ✔
365

366
    public void indexInView(T element, GraphView view) {
367
        TimeIndexImpl viewIndex = viewIndexes.get(view);
1 ✔
368
        if (viewIndex != null) {
1 ✔
369
            lock();
1 ✔
370
            try {
371
                S set = getTimeSet(element);
1 ✔
372
                if (set != null) {
1 ✔
373
                    K[] ts = set.toArray();
1 ✔
374
                    int tsLength = ts.length;
1 ✔
375
                    for (int i = 0; i < tsLength; i++) {
1 ✔
376
                        Integer timestampIndex = timeSortedMap.get(ts[i]);
1 ✔
377
                        if (timestampIndex != null) {
1 ✔
378
                            viewIndex.add(timestampIndex, element);
1 ✔
379
                        }
380
                    }
381
                }
382
            } finally {
383
                unlock();
1 ✔
384
            }
385
        }
386
    }
1 ✔
387

388
    public void clearInView(T element, GraphView view) {
389
        TimeIndexImpl viewIndex = viewIndexes.get(view);
1 ✔
390
        if (viewIndex != null) {
1 ✔
391
            lock();
1 ✔
392
            try {
393
                S set = getTimeSet(element);
1 ✔
394
                if (set != null) {
1 ✔
395
                    K[] ts = set.toArray();
1 ✔
396
                    int tsLength = ts.length;
1 ✔
397
                    for (int i = 0; i < tsLength; i++) {
1 ✔
398
                        Integer timestampIndex = timeSortedMap.get(ts[i]);
1 ✔
399
                        if (timestampIndex != null) {
1 ✔
400
                            viewIndex.remove(timestampIndex, element);
1 ✔
401
                        }
402
                    }
403
                }
404
            } finally {
405
                unlock();
1 ✔
406
            }
407
        }
408
    }
1 ✔
409

410
    public void clear(GraphView view) {
411
        TimeIndexImpl viewIndex = viewIndexes.get(view);
1 ✔
412
        if (viewIndex != null) {
1 ✔
413
            viewIndex.clear();
1 ✔
414
        }
415
    }
1 ✔
416

417
    public boolean hasIndex() {
418
        return mainIndex != null;
1 ✔
419
    }
420

421
    private S getTimeSet(Element element) {
422
        Object[] attributes = element.getAttributes();
1 ✔
423
        if (GraphStoreConfiguration.ENABLE_ELEMENT_TIME_SET && GraphStoreConfiguration.ELEMENT_TIMESET_INDEX < attributes.length) {
1 ✔
424
            return (S) attributes[GraphStoreConfiguration.ELEMENT_TIMESET_INDEX];
1 ✔
425
        }
426
        return null;
×
427
    }
428

429
    private void checkTimeIndex(Integer timeIndex) {
430
        if (timeIndex == null) {
×
431
            throw new IllegalArgumentException("Unknown time index");
×
432
        }
433
    }
×
434

435
    protected void ensureArraySize(int index) {
436
        if (index >= countMap.length) {
1 ✔
437
            int newSize = Math.min(Math
1 ✔
438
                    .max(index + 1, (int) (index * GraphStoreConfiguration.TIMESTAMP_STORE_GROWING_FACTOR)), Integer.MAX_VALUE);
1 ✔
439
            int[] newArray = new int[newSize];
1 ✔
440
            System.arraycopy(countMap, 0, newArray, 0, countMap.length);
1 ✔
441
            countMap = newArray;
1 ✔
442
        }
443
    }
1 ✔
444

445
    public int deepHashCode() {
446
        int hash = 3;
1 ✔
447
        hash = 29 * hash + elementType.hashCode();
1 ✔
448
        for (Map.Entry<K, Integer> entry : timeSortedMap.entrySet()) {
1 ✔
449
            hash = 29 * hash + entry.getKey().hashCode();
1 ✔
450
            hash = 29 * hash + entry.getValue().hashCode();
1 ✔
451
            hash = 29 * hash + countMap[entry.getValue()];
1 ✔
452
        }
1 ✔
453
        return hash;
1 ✔
454
    }
455

456
    public boolean deepEquals(TimeIndexStore obj) {
457
        if (obj == null) {
1 ✔
458
            return false;
1 ✔
459
        }
460
        if (!obj.getClass().equals(getClass())) {
1 ✔
461
            return false;
×
462
        }
463
        TimeIndexStore other = obj;
1 ✔
464
        if (!other.elementType.equals(elementType)) {
1 ✔
465
            return false;
×
466
        }
467
        if (!MapDeepEquals.mapDeepEquals(timeSortedMap, other.timeSortedMap)) {
1 ✔
468
            return false;
1 ✔
469
        }
470
        int[] otherCountMap = other.countMap;
1 ✔
471
        for (Integer k : timeSortedMap.values()) {
1 ✔
472
            if (otherCountMap[k] != countMap[k]) {
1 ✔
473
                return false;
1 ✔
474
            }
475
        }
1 ✔
476
        return true;
1 ✔
477
    }
478

479
    private void lock() {
480
        if (lock != null) {
1 ✔
481
            lock.lock();
1 ✔
482
        }
483
    }
1 ✔
484

485
    private void unlock() {
486
        if (lock != null) {
1 ✔
487
            lock.unlock();
1 ✔
488
        }
489
    }
1 ✔
490
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc