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

gephi / graphstore / #553

24 Apr 2026 05:33PM UTC coverage: 91.042% (+0.003%) from 91.039%
#553

push

mbastian
Fix column store locking issues

31 of 35 new or added lines in 2 files covered. (88.57%)

1 existing line in 1 file now uncovered.

11759 of 12916 relevant lines covered (91.04%)

0.91 hits per line

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

93.5
/src/main/java/org/gephi/graph/impl/ColumnStore.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.objects.Object2ShortMap;
19
import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;
20
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
21
import it.unimi.dsi.fastutil.shorts.ShortRBTreeSet;
22
import it.unimi.dsi.fastutil.shorts.ShortSortedSet;
23
import java.util.ArrayList;
24
import java.util.Iterator;
25
import java.util.List;
26
import java.util.Set;
27
import org.gephi.graph.api.Column;
28
import org.gephi.graph.api.ColumnIterable;
29
import org.gephi.graph.api.Element;
30
import org.gephi.graph.api.Origin;
31

32
public class ColumnStore<T extends Element> implements ColumnIterable {
33

34
    // Config
35
    protected final static int MAX_SIZE = 65534;
36
    // Const
37
    protected final static int NULL_ID = -1;
38
    protected final static short NULL_SHORT = Short.MIN_VALUE;
39
    // Configuration
40
    protected final ConfigurationImpl configuration;
41
    // GraphStore
42
    protected final GraphStore graphStore;
43
    // Element
44
    protected final Class<T> elementType;
45
    // Columns
46
    protected final Object2ShortMap<String> idMap;
47
    protected final ColumnImpl[] columns;
48
    protected final ShortSortedSet garbageQueue;
49
    // Index
50
    protected final IndexStore<T> indexStore;
51
    // Version
52
    protected final List<TableObserverImpl> observers;
53
    // Locking (optional)
54
    protected final TableLockImpl lock;
55
    // Variables
56
    protected int length;
57

58
    public ColumnStore(Class<T> elementType, boolean indexed) {
59
        this(null, elementType);
1✔
60
    }
1✔
61

62
    public ColumnStore(GraphStore graphStore, Class<T> elementType) {
1✔
63
        if (MAX_SIZE >= Short.MAX_VALUE - Short.MIN_VALUE + 1) {
64
            throw new RuntimeException("Column Store size can't exceed 65534");
65
        }
66
        this.graphStore = graphStore;
1✔
67
        if (graphStore == null) {
1✔
68
            // Used for testing only
69
            configuration = new ConfigurationImpl();
1✔
70
        } else {
71
            configuration = graphStore.configuration;
1✔
72
        }
73
        this.lock = configuration.isEnableAutoLocking() ? new TableLockImpl() : null;
1✔
74
        this.garbageQueue = new ShortRBTreeSet();
1✔
75
        this.idMap = new Object2ShortOpenHashMap<>(MAX_SIZE);
1✔
76
        this.columns = new ColumnImpl[MAX_SIZE];
1✔
77
        this.elementType = elementType;
1✔
78
        this.indexStore = new IndexStore<>(this);
1✔
79
        idMap.defaultReturnValue(NULL_SHORT);
1✔
80
        this.observers = new ArrayList<>();
1✔
81
    }
1✔
82

83
    public void addColumn(final Column column) {
84
        checkNonNullColumnObject(column);
1✔
85
        checkIndexStatus(column);
1✔
86

87
        lock();
1✔
88
        try {
89
            final ColumnImpl columnImpl = (ColumnImpl) column;
1✔
90
            short id = idMap.getShort(columnImpl.getId());
1✔
91
            if (id == NULL_SHORT) {
1✔
92
                if (!garbageQueue.isEmpty()) {
1✔
93
                    id = garbageQueue.firstShort();
1✔
94
                    garbageQueue.remove(id);
1✔
95
                } else {
96
                    id = intToShort(length);
1✔
97
                    if (length >= MAX_SIZE) {
1✔
98
                        throw new RuntimeException("Maximum number of columns reached at " + MAX_SIZE);
×
99
                    }
100
                    length++;
1✔
101
                }
102
                idMap.put(column.getId(), id);
1✔
103
                int intIndex = shortToInt(id);
1✔
104
                columnImpl.setStoreId(intIndex);
1✔
105
                columns[intIndex] = columnImpl;
1✔
106
                if (indexStore != null) {
1✔
107
                    indexStore.addColumn(columnImpl);
1✔
108
                }
109

110
                // Index attributes
111
                if (graphStore != null && columnImpl.table != null) {
1✔
112
                    for (Element e : graphStore.getElements(columnImpl.table)) {
1✔
113
                        e.setAttribute(column, column.getDefaultValue());
1✔
114
                    }
1✔
115
                }
116
            } else {
1✔
117
                throw new IllegalArgumentException("The column " + column.getId() + " already exist");
1✔
118
            }
119
        } finally {
120
            unlock();
1✔
121
        }
122
    }
1✔
123

124
    public void removeColumn(final Column column) {
125
        checkNonNullColumnObject(column);
1✔
126

127
        lock();
1✔
128
        try {
129
            final ColumnImpl columnImpl = (ColumnImpl) column;
1✔
130

131
            // Clean attributes
132
            if (graphStore != null && columnImpl.table != null) {
1✔
133
                for (Element e : graphStore.getElements(columnImpl.table)) {
1✔
134
                    ((ElementImpl) e).attributes.setAttribute(column, null);
1✔
135
                }
1✔
136
            }
137

138
            short id = idMap.removeShort(column.getId());
1✔
139
            if (id == NULL_SHORT) {
1✔
140
                throw new IllegalArgumentException("The column doesnt exist");
1✔
141
            }
142
            garbageQueue.add(id);
1✔
143

144
            int intId = shortToInt(id);
1✔
145
            columns[intId] = null;
1✔
146
            if (indexStore != null) {
1✔
147
                indexStore.removeColumn((ColumnImpl) column);
1✔
148
            }
149
            columnImpl.setStoreId(NULL_ID);
1✔
150
        } finally {
151
            unlock();
1✔
152
        }
153
    }
1✔
154

155
    public void removeColumn(final String key) {
156
        checkNonNullObject(key);
1✔
157
        ColumnImpl col = getColumn(key);
1✔
158
        if (col == null) {
1✔
NEW
159
            throw new IllegalArgumentException("The column doesnt exist");
×
160
        }
161
        removeColumn(col);
1✔
162
    }
1✔
163

164
    public int getColumnIndex(final String key) {
165
        checkNonNullObject(key);
1✔
166
        short id = idMap.getShort(key.toLowerCase());
1✔
167
        if (id == NULL_SHORT) {
1✔
168
            throw new IllegalArgumentException("The column doesnt exist");
1✔
169
        }
170
        return shortToInt(id);
1✔
171
    }
172

173
    public ColumnImpl getColumnByIndex(final int index) {
174
        if (index < 0 || index >= columns.length) {
1✔
175
            throw new IllegalArgumentException("The column doesnt exist");
×
176
        }
177
        ColumnImpl a = columns[index];
1✔
178
        if (a == null) {
1✔
179
            throw new IllegalArgumentException("The column doesnt exist");
1✔
180
        }
181
        return a;
1✔
182
    }
183

184
    public ColumnImpl getColumn(final String key) {
185
        checkNonNullObject(key);
1✔
186
        short id = idMap.getShort(key.toLowerCase());
1✔
187
        if (id == NULL_SHORT) {
1✔
188
            return null;
1✔
189
        }
190
        return columns[shortToInt(id)];
1✔
191
    }
192

193
    public boolean hasColumn(String key) {
194
        checkNonNullObject(key);
1✔
195
        return idMap.containsKey(key.toLowerCase());
1✔
196
    }
197

198
    @Override
199
    public Iterator<Column> iterator() {
200
        return new ColumnStoreIterator();
1✔
201
    }
202

203
    @Override
204
    public ColumnImpl[] toArray() {
205
        lock();
1✔
206
        try {
207
            ColumnImpl[] cols = new ColumnImpl[size()];
1✔
208
            int j = 0;
1✔
209
            for (int i = 0; i < length; i++) {
1✔
210
                ColumnImpl c = columns[i];
1✔
211
                if (c != null) {
1✔
212
                    cols[j++] = c;
1✔
213
                }
214
            }
215
            return cols;
1✔
216
        } finally {
217
            unlock();
1✔
218
        }
219
    }
220

221
    @Override
222
    public List<Column> toList() {
223
        lock();
1✔
224
        try {
225
            List<Column> cols = new ArrayList<>(size());
1✔
226
            for (int i = 0; i < length; i++) {
1✔
227
                ColumnImpl c = columns[i];
1✔
228
                if (c != null) {
1✔
229
                    cols.add(c);
1✔
230
                }
231
            }
232
            return cols;
1✔
233
        } finally {
234
            unlock();
1✔
235
        }
236
    }
237

238
    @Override
239
    public void doBreak() {
240
        unlock();
1✔
241
    }
1✔
242

243
    public Set<String> getColumnKeys() {
244
        lock();
1✔
245
        try {
246
            return new ObjectOpenHashSet<>(idMap.keySet());
1✔
247
        } finally {
248
            unlock();
1✔
249
        }
250
    }
251

252
    public int size() {
253
        return length - garbageQueue.size();
1✔
254
    }
255

256
    public int size(Origin origin) {
257
        checkNonNullObject(origin);
1✔
258
        lock();
1✔
259
        try {
260
            int res = 0;
1✔
261
            for (int i = 0; i < length; i++) {
1✔
262
                ColumnImpl c = columns[i];
1✔
263
                if (c != null && c.origin.equals(origin)) {
1✔
264
                    res++;
1✔
265
                }
266
            }
267
            return res;
1✔
268
        } finally {
269
            unlock();
1✔
270
        }
271
    }
272

273
    protected TableObserverImpl createTableObserver(TableImpl table, boolean withDiff) {
274
        if (observers != null) {
1✔
275
            lock();
1✔
276
            try {
277
                TableObserverImpl observer = new TableObserverImpl(table, withDiff);
1✔
278
                observers.add(observer);
1✔
279

280
                return observer;
1✔
281
            } finally {
282
                unlock();
1✔
283
            }
284
        }
285
        return null;
×
286
    }
287

288
    protected void destroyTablesObserver(TableObserverImpl observer) {
289
        if (observers != null) {
1✔
290
            lock();
1✔
291
            try {
292
                observers.remove(observer);
1✔
293
                observer.destroyObserver();
1✔
294
            } finally {
295
                unlock();
1✔
296
            }
297
        }
298
    }
1✔
299

300
    short intToShort(final int id) {
301
        return (short) (id + Short.MIN_VALUE + 1);
1✔
302
    }
303

304
    int shortToInt(final short id) {
305
        return id - Short.MIN_VALUE - 1;
1✔
306
    }
307

308
    void lock() {
309
        if (lock != null) {
1✔
310
            lock.lock();
1✔
311
        }
312
    }
1✔
313

314
    void unlock() {
315
        if (lock != null) {
1✔
316
            lock.unlock();
1✔
317
        }
318
    }
1✔
319

320
    void checkNonNullObject(final Object o) {
321
        if (o == null) {
1✔
322
            throw new NullPointerException();
×
323
        }
324
    }
1✔
325

326
    void checkNonNullColumnObject(final Object o) {
327
        if (o == null) {
1✔
328
            throw new NullPointerException();
1✔
329
        }
330
        if (!(o instanceof ColumnImpl)) {
1✔
331
            throw new ClassCastException("Must be ColumnImpl object");
1✔
332
        }
333
    }
1✔
334

335
    void checkIndexStatus(final Column column) {
336
        if (indexStore == null && column.isIndexed()) {
1✔
337
            throw new IllegalArgumentException("Can't add an indexed column to a non indexed store");
×
338
        }
339
    }
1✔
340

341
    private final class ColumnStoreIterator implements Iterator<Column> {
342

343
        private int index;
344
        private ColumnImpl pointer;
345

346
        public ColumnStoreIterator() {
1✔
347
            lock();
1✔
348
        }
1✔
349

350
        @Override
351
        public boolean hasNext() {
352
            if (pointer != null) {
1✔
NEW
353
                return true;
×
354
            }
355
            while (index < length && (pointer = columns[index++]) == null) {
1✔
356
            }
357
            if (pointer == null) {
1✔
358
                unlock();
1✔
359
                return false;
1✔
360
            }
361
            return true;
1✔
362
        }
363

364
        @Override
365
        public ColumnImpl next() {
366
            ColumnImpl c = pointer;
1✔
367
            pointer = null;
1✔
368
            return c;
1✔
369
        }
370

371
        @Override
372
        public void remove() {
373
            throw new UnsupportedOperationException("Not supported");
1✔
374
        }
375
    }
376

377
    public boolean deepEquals(ColumnStore<T> obj) {
378
        if (obj == null) {
1✔
379
            return false;
×
380
        }
381
        if (this.elementType != obj.elementType && (this.elementType == null || !this.elementType
1✔
382
                .equals(obj.elementType))) {
×
383
            return false;
×
384
        }
385
        Iterator<Column> itr1 = this.iterator();
1✔
386
        Iterator<Column> itr2 = obj.iterator();
1✔
387
        boolean itr1Closed = false;
1✔
388
        boolean itr2Closed = false;
1✔
389
        try {
390
            while (itr1.hasNext()) {
1✔
391
                if (!itr2.hasNext()) {
1✔
NEW
392
                    itr2Closed = true;
×
NEW
393
                    return false;
×
394
                }
395
                Column c1 = itr1.next();
1✔
396
                Column c2 = itr2.next();
1✔
397
                if (!c1.equals(c2)) {
1✔
398
                    return false;
1✔
399
                }
400
            }
1✔
401
            itr1Closed = true;
1✔
402
            if (itr2.hasNext()) {
1✔
UNCOV
403
                return false;
×
404
            }
405
            itr2Closed = true;
1✔
406
            return true;
1✔
407
        } finally {
408
            if (!itr1Closed) {
1✔
409
                this.doBreak();
1✔
410
            }
411
            if (!itr2Closed) {
1✔
412
                obj.doBreak();
1✔
413
            }
414
        }
415
    }
416

417
    public int deepHashCode() {
418
        int hash = 3;
1✔
419
        hash = 11 * hash + (this.elementType != null ? this.elementType.hashCode() : 0);
1✔
420
        lock();
1✔
421
        try {
422
            for (int i = 0; i < length; i++) {
1✔
423
                ColumnImpl c = columns[i];
1✔
424
                if (c != null) {
1✔
425
                    hash = 11 * hash + c.deepHashCode();
1✔
426
                }
427
            }
428
        } finally {
429
            unlock();
1✔
430
        }
431
        // TODO what about timestampmap
432
        return hash;
1✔
433
    }
434
}
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