AbstractDb.java

1
package com.renomad.minum.database;
2
3
import com.renomad.minum.logging.ILogger;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.utils.IFileUtils;
6
7
import java.io.IOException;
8
import java.nio.file.Path;
9
import java.time.ZoneId;
10
import java.time.format.DateTimeFormatter;
11
import java.util.*;
12
import java.util.concurrent.Callable;
13
import java.util.concurrent.atomic.AtomicLong;
14
import java.util.concurrent.locks.ReentrantLock;
15
import java.util.function.Function;
16
17
/**
18
 * The abstract database class is a representation of the essential capabilities of
19
 * a Minum database.
20
 * <p>
21
 *     There are two kinds of database provided, which only differ in how they
22
 *     store data on disk.  The "classic" kind, {@link Db}, stores each piece of
23
 *     data in its own file.  This is the simplest approach.
24
 * </p>
25
 * <p>
26
 *     However, for significant speed gains, the new {@link DbEngine2} will
27
 *     store each change as an append to a file, and will consolidate the on-disk
28
 *     data occasionally, and on start.  That way is thousands of times faster
29
 *     to write to disk and to read from disk at startup.
30
 * </p>
31
 * @param <T> This is the type of data, which is always an implementation of
32
 *           the {@link DbData} class.  See the code of {@link com.renomad.minum.security.Inmate}
33
 *           for an example of how this should look.
34
 */
35
public abstract class AbstractDb<T extends DbData<?>> {
36
37
    /**
38
     * The directory of the database on disk
39
     */
40
    protected final Path dbDirectory;
41
42
    /**
43
     * An empty instance of the type of data stored by this
44
     * database, used for better handling of generics.
45
     */
46
    protected final T emptyInstance;
47
48
    /**
49
     * Used for handling some file utilities in the database like creating directories
50
     */
51
    protected final IFileUtils fileUtils;
52
53
    /**
54
     * Holds some system-wide information that is beneficial for components of the database
55
     */
56
    protected final Context context;
57
58
    /**
59
     * Used for providing logging throughout the database
60
     */
61
    protected final ILogger logger;
62
63
    /**
64
     * The internal data structure of the database that resides in memory.  The beating heart
65
     * of the database while it runs.
66
     */
67
    protected Map<Long, T> data;
68
69
    /**
70
     * used to place locks around certain actions that need to avoid
71
     * thread interleaving.
72
     */
73
    protected final InspectableLock dbLock;
74
75
    /**
76
     * The current index, used when creating new data items.  Each item has its own
77
     * index value, this is where it is tracked.
78
     */
79
    protected AtomicLong index;
80
81
    // components for registered indexes (for faster read performance)
82
83
    /**
84
     * This data structure is a nested map used for providing indexed data search.
85
     * <br>
86
     * The outer map is between the name of the index and the inner map.
87
     * <br>
88
     * The inner map is between strings and sets of items related to that string.
89
     */
90
    protected final Map<String, Map<String, Set<T>>> registeredIndexes;
91
92
    /**
93
     * This map holds the functions that are registered to indexes, which are used
94
     * to construct the mappings between string values and items in the database.
95
     */
96
    protected final Map<String, Function<T, String>> partitioningMap;
97
98
    private final ReentrantLock indexLock;
99
100
    /**
101
     * Results in output like "2025_08_30_13_01_49_123", which is year_month_day_hour_minute_second_millisecond.
102
     * This can be used to parse the file names to {@link java.time.Instant} so we can process the oldest
103
     * file first.
104
     */
105
    static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy_MM_dd_HH_mm_ss_SSS").withZone(ZoneId.of("UTC"));
106
107
108
    protected AbstractDb(Path dbDirectory, Context context, T instance, IFileUtils fileUtils) {
109 1 1. <init> : negated conditional → KILLED
        if (context.isDbPathRegistered(dbDirectory)) {
110
            throw new DbException("Attempted to register more than one database to the same path: " + dbDirectory);
111
        }
112 1 1. <init> : removed call to com/renomad/minum/state/Context::addToDbPaths → KILLED
        context.addToDbPaths(dbDirectory);
113
        this.dbDirectory = dbDirectory;
114
        this.context = context;
115
        this.emptyInstance = instance;
116
        this.logger = context.getLogger();
117
        this.registeredIndexes = new HashMap<>();
118
        this.partitioningMap = new HashMap<>();
119
        this.fileUtils = fileUtils;
120
        this.dbLock = new InspectableLock();
121
        this.indexLock = new ReentrantLock();
122
    }
123
124
    /**
125
     * Used to cleanly stop the database.
126
     * <br>
127
     * In the case of {@link Db} this will interrupt its internal queue and tell it
128
     * to finish up processing.
129
     * <br>
130
     * In the case of {@link DbEngine2} this will flush data to disk.
131
     */
132
    public abstract void stop() throws IOException;
133
134
    /**
135
     * Used to cleanly stop the database, with extra allowance of time
136
     * for cleanup.
137
     * <br>
138
     * Note that this method mostly applies to {@link Db}, and not as much
139
     * to {@link DbEngine2}.  Only Db uses a processing queue on a thread which
140
     * is what requires a longer shutdown time for interruption.
141
     * @param count number of loops before we are done waiting for a clean close
142
     *              and instead crash the instance closed.
143
     * @param sleepTime how long to wait, in milliseconds, for each iteration of the waiting loop.
144
     */
145
    public abstract void stop(int count, long sleepTime) throws IOException;
146
147
148
    /**
149
     * Write data to the database.  Use an index of 0 to store new data, and a positive
150
     * non-zero value to update data.
151
     * <p><em>
152
     * Example of adding new data to the database:
153
     * </em></p>
154
     * {@snippet :
155
     *          final var newSalt = StringUtils.generateSecureRandomString(10);
156
     *          final var hashedPassword = CryptoUtils.createPasswordHash(newPassword, newSalt);
157
     *          final var newUser = new User(0L, newUsername, hashedPassword, newSalt);
158
     *          userDb.write(newUser);
159
     * }
160
     * <p><em>
161
     * Example of updating data:
162
     * </em></p>
163
     * {@snippet :
164
     *         // write the updated salted password to the database
165
     *         final var updatedUser = new User(
166
     *                 user().getIndex(),
167
     *                 user().getUsername(),
168
     *                 hashedPassword,
169
     *                 newSalt);
170
     *         userDb.write(updatedUser);
171
     * }
172
     *
173
     * @param newData the data we are writing
174
     * @return the data with its new index assigned.
175
     */
176
    public abstract T write(T newData);
177
178
    /**
179
     * Write database data into memory
180
     * @param newData the new data may be totally new or an update
181
     * @param newElementCreated if true, this is a create.  If false, an update.
182
     */
183
    protected void writeToMemory(T newData, boolean newElementCreated) {
184
        // if we got here, we are safe to proceed with putting the data into memory and disk
185
        logger.logTrace(() -> String.format("in thread %d, writing data %s", Thread.currentThread().threadId(), newData));
186
        T oldData = data.put(newData.getIndex(), newData);
187
188
        // handle the indexes differently depending on whether this is a create or delete
189 1 1. writeToMemory : negated conditional → KILLED
        if (newElementCreated) {
190 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
191
        } else {
192 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED
            removeFromIndexes(oldData);
193 1 1. writeToMemory : removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED
            addToIndexes(newData);
194
        }
195
    }
196
197
    /**
198
     * When new data comes in, we look at its "index" value. If
199
     * it is zero, it's a create, and we assign it a new value.  If it is
200
     * positive, it is an update, and we had better find it in the database
201
     * already, or else throw an exception.
202
     * @return true if a create, false if an update
203
     */
204
    protected boolean processDataIndex(T newData) {
205
        // *** deal with the in-memory portion ***
206
        boolean newElementCreated = false;
207
        // create a new index for the data, if needed
208 1 1. processDataIndex : negated conditional → KILLED
        if (newData.getIndex() == 0L) {
209 1 1. processDataIndex : removed call to com/renomad/minum/database/DbData::setIndex → KILLED
            newData.setIndex(index.getAndIncrement());
210
            newElementCreated = true;
211
        } else {
212
            // if the data does not exist, and a positive non-zero
213
            // index was provided, throw an exception.
214
            boolean dataEntryExists = data.containsKey(newData.getIndex());
215 1 1. processDataIndex : negated conditional → KILLED
            if (!dataEntryExists) {
216
                throw new DbException(
217
                        String.format("Positive indexes are only allowed when updating existing data. Index: %d",
218
                                newData.getIndex()));
219
            }
220
        }
221 2 1. processDataIndex : replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → TIMED_OUT
2. processDataIndex : replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED
        return newElementCreated;
222
    }
223
224
    /**
225
     * Delete data
226
     * <p><em>Example:</em></p>
227
     * {@snippet :
228
     *      userDb.delete(user);
229
     * }
230
     *
231
     * @param dataToDelete the data we are serializing and writing
232
     */
233
    public abstract void delete(T dataToDelete);
234
235
236
    /**
237
     * Remove a particular item from the internal data structure in memory
238
     */
239
    protected void deleteFromMemory(T dataToDelete) {
240
        long dataIndex;
241
        dataIndex = dataToDelete.getIndex();
242 1 1. deleteFromMemory : negated conditional → TIMED_OUT
        if (!data.containsKey(dataIndex)) {
243
            throw new DbException("no data was found with index of " + dataIndex);
244
        }
245
        long finalDataIndex = dataIndex;
246
        logger.logTrace(() -> String.format("in thread %d, deleting data with index %d", Thread.currentThread().threadId(), finalDataIndex));
247
248
        // this will never be null, because we check earlier in this method
249
        // that the data contains this key, and it throws an exception if not
250
        T removed = data.remove(dataIndex);
251 1 1. deleteFromMemory : removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → TIMED_OUT
        removeFromIndexes(removed);
252
253
        // if all the data was just now deleted, we need to
254
        // reset the index back to 1
255 1 1. deleteFromMemory : negated conditional → KILLED
        if (data.isEmpty()) {
256 1 1. deleteFromMemory : removed call to java/util/concurrent/atomic/AtomicLong::set → TIMED_OUT
            index.set(1L);
257
        }
258
    }
259
260
261
    /**
262
     *  add the data to registered indexes.
263
     *  <br>
264
     *  For each of the registered indexes,
265
     *  get the stored function to obtain a string value which helps divide
266
     *  the overall data into partitions.
267
     */
268
    protected void addToIndexes(T dbData) {
269
270
        for (var entry : partitioningMap.entrySet()) {
271
            // a function provided by the user to obtain an index-key: a unique or semi-unique
272
            // value to help partition / index the data
273
            Function<T, String> indexStringFunction = entry.getValue();
274
            String propertyAsString = indexStringFunction.apply(dbData);
275
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
276 1 1. addToIndexes : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
            indexLock.lock();
277
            try {
278 1 1. lambda$addToIndexes$2 : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$2 → KILLED
                stringIndexMap.computeIfAbsent(propertyAsString, k -> new HashSet<>());
279
                // if the index-key provides a 1-to-1 mapping to items, like UUIDs, then
280
                // each value will have only one item in the collection.  In other cases,
281
                // like when partitioning the data into multiple groups, there could easily
282
                // be many items per index value.
283
                Set<T> dataSet = stringIndexMap.get(propertyAsString);
284
                dataSet.add(dbData);
285
            } finally {
286 1 1. addToIndexes : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
                indexLock.unlock();
287
            }
288
        }
289
    }
290
291
    /**
292
     * Run when an item is deleted from the database
293
     */
294
    private void removeFromIndexes(T dbData) {
295
        for (var entry : partitioningMap.entrySet()) {
296
            // a function provided by the user to obtain an index-key: a unique or semi-unique
297
            // value to help partition / index the data
298
            Function<T, String> indexStringFunction = entry.getValue();
299
            String propertyAsString = indexStringFunction.apply(dbData);
300
            Map<String, Set<T>> stringIndexMap = registeredIndexes.get(entry.getKey());
301 1 1. removeFromIndexes : removed call to java/util/concurrent/locks/ReentrantLock::lock → TIMED_OUT
            indexLock.lock();
302
            try {
303 2 1. lambda$removeFromIndexes$3 : replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$3 → TIMED_OUT
2. lambda$removeFromIndexes$3 : negated conditional → KILLED
                stringIndexMap.get(propertyAsString).removeIf(x -> x.getIndex() == dbData.getIndex());
304
305
                // in certain cases, we're removing one of the items that is indexed but
306
                // there are more left.  If there's nothing left though, we'll remove the mapping.
307 1 1. removeFromIndexes : negated conditional → TIMED_OUT
                if (stringIndexMap.get(propertyAsString).isEmpty()) {
308
                    stringIndexMap.remove(propertyAsString);
309
                }
310
            } finally {
311 1 1. removeFromIndexes : removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT
                indexLock.unlock();
312
            }
313
        }
314
    }
315
316
317
    /**
318
     * Cause the database to immediately load all its data.
319
     * <p>
320
     *     If this method is not invoked by the developer, then the data will be loaded
321
     *     lazily, at the first point where it is needed, such as when
322
     *     getting or writing data.
323
     * </p>
324
     * <p>
325
     *     It is a good idea to regularly use this method at database
326
     *     initialization.  By doing so, the check for data corruption issues will
327
     *     throw an exception that will cause the whole application to halt
328
     *     immediately, which is far preferable to having the exception show
329
     *     as a complaint in the logs.
330
     * </p>
331
     * <p>
332
     *     An example database initialization with bells and whistles
333
     *     is as follows:
334
     * </p>
335
     * <pre>
336
     *     {@code
337
     *     AbstractDb<PersonName> sampleDomainDb = context.getDb2("names", PersonName.EMPTY)
338
     *          .registerIndex("name_index", name -> name)
339
     *          .loadData();
340
     *     }
341
     * </pre>
342
     */
343
    public abstract AbstractDb<T> loadData();
344
345
    /**
346
     * This method returns a read-only view of the values of a database.
347
     *
348
     * <p><em>Example:</em></p>
349
     * {@snippet :
350
     * boolean doesUserAlreadyExist(String username) {
351
     *     return userDb.values().stream().anyMatch(x -> x.getUsername().equals(username));
352
     * }
353
     * }
354
     */
355
    public abstract Collection<T> values();
356
357
    /**
358
     * Register an index in the database for higher performance data access.
359
     * <p>
360
     *     This command should be run immediately after database declaration,
361
     *     or more specifically, before any data is loaded from disk. Otherwise,
362
     *     it would be possible to skip indexing that data.
363
     * </p>
364
     * <br>
365
     * Example:
366
     * <pre>
367
     *     {@code
368
     *      final var myDatabase = context.getDb("photos", Photograph.EMPTY)
369
     *               .registerIndex("url", photo -> photo.getUrl())
370
     *               .loadData();
371
     *     }
372
     * </pre>
373
     * @param indexName a string used to distinguish this index.  This string will be used again
374
     *                  when requesting data in a method like {@link #getIndexedData} or {@link #findExactlyOne}
375
     * @param keyObtainingFunction a function which obtains data from the data in this database, used
376
     *                             to partition the data into groups (potentially up to a 1-to-1 correspondence
377
     *                             between id and object)
378
     * @return the database instance if the registration succeeded
379
     * @throws DbException if the parameters are not entered properly, if the index has already
380
     * been registered, or if the data has already been loaded. It is necessary that
381
     * this is run immediately after declaring the database. To explain further: the data is not
382
     * actually loaded until the first time it is needed, such as running a write or delete, or
383
     * if the {@link #loadData()} ()} method is run.  Creating an index map for the data that
384
     * is read from disk only occurs once, at data load time.  Thus, it is crucial that the
385
     * registerIndex command is run before any data is loaded.
386
     */
387
    public AbstractDb<T> registerIndex(String indexName, Function<T, String> keyObtainingFunction) {
388 1 1. registerIndex : negated conditional → KILLED
        if (keyObtainingFunction == null) {
389
            throw new DbException("When registering an index, the partitioning algorithm must not be null");
390
        }
391 2 1. registerIndex : negated conditional → KILLED
2. registerIndex : negated conditional → KILLED
        if (indexName == null || indexName.isBlank()) {
392
            throw new DbException("When registering an index, value must be a non-empty string");
393
        }
394 1 1. registerIndex : negated conditional → KILLED
        if (registeredIndexes.containsKey(indexName)) {
395
            throw new DbException("It is forbidden to register the same index more than once.  Duplicate index: \""+indexName+"\"");
396
        }
397
        HashMap<String, Set<T>> stringCollectionHashMap = new HashMap<>();
398
        registeredIndexes.put(indexName, stringCollectionHashMap);
399
        partitioningMap.put(indexName, keyObtainingFunction);
400 1 1. registerIndex : replaced return value with null for com/renomad/minum/database/AbstractDb::registerIndex → KILLED
        return this;
401
    }
402
403
    /**
404
     * Given the name of a registered index (see {@link #registerIndex(String, Function)}),
405
     * use the key to find the collection of data that matches it.
406
     * @param indexName the name of an index
407
     * @param key a string value that matches a partition calculated from the partition
408
     *            function provided to {@link #registerIndex(String, Function)}
409
     * @return a collection of data, an empty collection if nothing found
410
     */
411
    public Collection<T> getIndexedData(String indexName, String key) {
412 1 1. getIndexedData : negated conditional → KILLED
        if (!registeredIndexes.containsKey(indexName)) {
413
            throw new DbException("There is no index registered on the database Db<"+this.emptyInstance.getClass().getSimpleName()+"> with a name of \""+indexName+"\"");
414
        }
415 1 1. getIndexedData : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        indexLock.lock();
416
        try {
417
            Set<T> ts = registeredIndexes.get(indexName).get(key);
418 1 1. getIndexedData : negated conditional → KILLED
            if (ts != null) {
419 1 1. getIndexedData : replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED
                return new HashSet<>(ts);
420
            } else {
421
                return Set.of();
422
            }
423
        } finally {
424 1 1. getIndexedData : removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT
            indexLock.unlock();
425
        }
426
    }
427
428
    /**
429
     * Get a set of the currently-registered indexes on this database, useful
430
     * for debugging.
431
     */
432
    public Set<String> getSetOfIndexes() {
433 1 1. getSetOfIndexes : replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → TIMED_OUT
        return partitioningMap.keySet();
434
    }
435
436
    /**
437
     * A utility to find exactly one item from the database.
438
     * <br>
439
     * This utility will search the indexes for a particular data by
440
     * indexName and indexKey.  If not found, it will return null. If
441
     * found, it will be returned. If more than one are found, an exception
442
     * will be thrown.  Use this tool when the data has been uniquely
443
     * indexed, like for example when setting a unique identifier into
444
     * each data.
445
     * @param indexName the name of the index, an arbitrary value set by the
446
     *                  user to help distinguish among potentially many indexes
447
     *                  set on this data
448
     * @param indexKey the key for this particular value, such as a UUID or a name
449
     *                 or any other way to partition the data
450
     * @see #findExactlyOne(String, String, Callable)
451
     */
452
    public T findExactlyOne(String indexName, String indexKey) {
453 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
        return findExactlyOne(indexName, indexKey, () -> null);
454
    }
455
456
    /**
457
     * Find one item, with an alternate value if nothing was found
458
     * <br>
459
     * This utility will search the indexes for a particular data by
460
     * indexName and indexKey.  If not found, it will return an alternate
461
     * value provided by the "alternate" parameter. If
462
     * found, it will be returned. If more than one are found, an exception
463
     * will be thrown.  Use this tool when the data has been uniquely
464
     * indexed, like for example when setting a unique identifier into
465
     * each data.
466
     * @param indexName the name of the index, an arbitrary value set by the
467
     *                  user to help distinguish among potentially many indexes
468
     *                  set on this data
469
     * @param indexKey the key for this particular value, such as a UUID or a name
470
     *                 or any other way to partition the data
471
     * @param alternate a functional interface that will be run if no result
472
     *                  was found
473
     * @see #findExactlyOne(String, String)
474
     */
475
    public T findExactlyOne(String indexName, String indexKey, Callable<T> alternate) {
476
        Collection<T> indexedData = getIndexedData(indexName, indexKey);
477 1 1. findExactlyOne : negated conditional → KILLED
        if (indexedData.isEmpty()) {
478
            try {
479 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
                return alternate.call();
480
            } catch (Exception ex) {
481
                throw new DbException(ex);
482
            }
483 1 1. findExactlyOne : negated conditional → KILLED
        } else if (indexedData.size() == 1) {
484 1 1. findExactlyOne : replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED
            return indexedData.stream().findFirst().orElseThrow();
485
        } else {
486
            throw new DbException("More than one item found when searching database Db<%s> on index \"%s\" with key %s"
487
                    .formatted(emptyInstance.getClass().getSimpleName(), indexName, indexKey));
488
        }
489
    }
490
491
    /**
492
     * Provides access to the lock that is used around all
493
     * modifications to the database.  Useful for wrapping
494
     * around multiple statements when you need to ensure
495
     * nothing else can intervene while they run.
496
     * <br>
497
     * Here is an example of usage:
498
     * <pre>
499
     * {@code
500
     *     ReentrantLock dbLock = db.getDbLock();
501
     *     dbLock.lock();
502
     *     try {
503
     *         db.write(foo);
504
     *         db.write(bar);
505
     *     } finally {
506
     *         dbLock.unlock();
507
     *     }
508
     * }
509
     * </pre>
510
     */
511
    public InspectableLock getDbLock() {
512 1 1. getDbLock : replaced return value with null for com/renomad/minum/database/AbstractDb::getDbLock → TIMED_OUT
        return dbLock;
513
    }
514
515
    /**
516
     * Check that the input is non-null and has a positive index
517
     */
518
    protected static <T extends DbData<?>> void basicDataChecks(T newData) {
519
        // disallowed to be null input
520 1 1. basicDataChecks : negated conditional → KILLED
        if (newData == null) {
521
            throw new DbException("Incoming data was null");
522
        }
523
        // disallowed for input to have a negative index
524 2 1. basicDataChecks : negated conditional → KILLED
2. basicDataChecks : changed conditional boundary → KILLED
        if (newData.getIndex() < 0L) throw new DbException("Negative indexes are disallowed");
525
    }
526
527
}

Mutations

109

1.1
Location : <init>
Killed by : com.renomad.minum.utils.ActionQueueKillerTests
negated conditional → KILLED

112

1.1
Location : <init>
Killed by : com.renomad.minum.web.FindingsWebTests
removed call to com/renomad/minum/state/Context::addToDbPaths → KILLED

189

1.1
Location : writeToMemory
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

190

1.1
Location : writeToMemory
Killed by : com.renomad.minum.web.WebTests
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

192

1.1
Location : writeToMemory
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → KILLED

193

1.1
Location : writeToMemory
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/AbstractDb::addToIndexes → KILLED

208

1.1
Location : processDataIndex
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

209

1.1
Location : processDataIndex
Killed by : com.renomad.minum.web.WebTests
removed call to com/renomad/minum/database/DbData::setIndex → KILLED

215

1.1
Location : processDataIndex
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

221

1.1
Location : processDataIndex
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with false for com/renomad/minum/database/AbstractDb::processDataIndex → KILLED

2.2
Location : processDataIndex
Killed by : none
replaced boolean return with true for com/renomad/minum/database/AbstractDb::processDataIndex → TIMED_OUT

242

1.1
Location : deleteFromMemory
Killed by : none
negated conditional → TIMED_OUT

251

1.1
Location : deleteFromMemory
Killed by : none
removed call to com/renomad/minum/database/AbstractDb::removeFromIndexes → TIMED_OUT

255

1.1
Location : deleteFromMemory
Killed by : com.renomad.minum.database.FindingsDatabaseTests
negated conditional → KILLED

256

1.1
Location : deleteFromMemory
Killed by : none
removed call to java/util/concurrent/atomic/AtomicLong::set → TIMED_OUT

276

1.1
Location : addToIndexes
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

278

1.1
Location : lambda$addToIndexes$2
Killed by : com.renomad.minum.web.WebTests
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::lambda$addToIndexes$2 → KILLED

286

1.1
Location : addToIndexes
Killed by : com.renomad.minum.web.WebTests
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

301

1.1
Location : removeFromIndexes
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::lock → TIMED_OUT

303

1.1
Location : lambda$removeFromIndexes$3
Killed by : none
replaced boolean return with true for com/renomad/minum/database/AbstractDb::lambda$removeFromIndexes$3 → TIMED_OUT

2.2
Location : lambda$removeFromIndexes$3
Killed by : com.renomad.minum.database.FindingsDatabaseTests
negated conditional → KILLED

307

1.1
Location : removeFromIndexes
Killed by : none
negated conditional → TIMED_OUT

311

1.1
Location : removeFromIndexes
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT

388

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.FindingsWebTests
negated conditional → KILLED

391

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.FindingsWebTests
negated conditional → KILLED

2.2
Location : registerIndex
Killed by : com.renomad.minum.web.FindingsWebTests
negated conditional → KILLED

394

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.FindingsWebTests
negated conditional → KILLED

400

1.1
Location : registerIndex
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
replaced return value with null for com/renomad/minum/database/AbstractDb::registerIndex → KILLED

412

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

415

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

418

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

419

1.1
Location : getIndexedData
Killed by : com.renomad.minum.web.WebTests
replaced return value with Collections.emptyList for com/renomad/minum/database/AbstractDb::getIndexedData → KILLED

424

1.1
Location : getIndexedData
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::unlock → TIMED_OUT

433

1.1
Location : getSetOfIndexes
Killed by : none
replaced return value with Collections.emptySet for com/renomad/minum/database/AbstractDb::getSetOfIndexes → TIMED_OUT

453

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

477

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

479

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebPerformanceTests.webPerfTest(com.renomad.minum.web.WebPerformanceTests)
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

483

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

484

1.1
Location : findExactlyOne
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/database/AbstractDb::findExactlyOne → KILLED

512

1.1
Location : getDbLock
Killed by : none
replaced return value with null for com/renomad/minum/database/AbstractDb::getDbLock → TIMED_OUT

520

1.1
Location : basicDataChecks
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

524

1.1
Location : basicDataChecks
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

2.2
Location : basicDataChecks
Killed by : com.renomad.minum.web.WebTests
changed conditional boundary → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0