FrequencyTrie.java

1
/*******************************************************************************
2
 * Copyright (C) 2026, Leo Galambos
3
 * All rights reserved.
4
 * 
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 * 
8
 * 1. Redistributions of source code must retain the above copyright notice,
9
 *    this list of conditions and the following disclaimer.
10
 * 
11
 * 2. Redistributions in binary form must reproduce the above copyright notice,
12
 *    this list of conditions and the following disclaimer in the documentation
13
 *    and/or other materials provided with the distribution.
14
 * 
15
 * 3. Neither the name of the copyright holder nor the names of its contributors
16
 *    may be used to endorse or promote products derived from this software
17
 *    without specific prior written permission.
18
 * 
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
 * POSSIBILITY OF SUCH DAMAGE.
30
 ******************************************************************************/
31
package org.egothor.stemmer;
32
33
import java.io.DataInputStream;
34
import java.io.DataOutputStream;
35
import java.io.IOException;
36
import java.io.InputStream;
37
import java.io.OutputStream;
38
import java.util.ArrayList;
39
import java.util.Arrays;
40
import java.util.Collections;
41
import java.util.IdentityHashMap;
42
import java.util.LinkedHashMap;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Objects;
46
import java.util.function.IntFunction;
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
49
50
import org.egothor.stemmer.trie.CompiledNode;
51
import org.egothor.stemmer.trie.LocalValueSummary;
52
import org.egothor.stemmer.trie.MutableNode;
53
import org.egothor.stemmer.trie.NodeData;
54
import org.egothor.stemmer.trie.ReducedNode;
55
import org.egothor.stemmer.trie.ReductionContext;
56
import org.egothor.stemmer.trie.ReductionSignature;
57
58
/**
59
 * Read-only trie mapping {@link String} keys to one or more values with
60
 * frequency tracking.
61
 *
62
 * <p>
63
 * A key may be associated with multiple values. Each value keeps the number of
64
 * times it was inserted during the build phase. The method {@link #get(String)}
65
 * returns the locally most frequent value stored at the terminal node of the
66
 * supplied key, while {@link #getAll(String)} returns all locally stored values
67
 * ordered by descending frequency.
68
 *
69
 * <p>
70
 * If multiple values have the same local frequency, their ordering is
71
 * deterministic. The preferred value is selected by the following tie-breaking
72
 * rules, in order:
73
 * <ol>
74
 * <li>shorter {@link String} representation wins, based on
75
 * {@code value.toString()}</li>
76
 * <li>if the lengths are equal, lexicographically lower {@link String}
77
 * representation wins</li>
78
 * <li>if the textual representations are still equal, first-seen insertion
79
 * order remains stable</li>
80
 * </ol>
81
 *
82
 * <p>
83
 * Values may be stored at any trie node, including internal nodes and leaf
84
 * nodes. Therefore, reduction and canonicalization always operate on both the
85
 * node-local terminal values and the structure of all descendant edges.
86
 *
87
 * @param <V> value type
88
 */
89
public final class FrequencyTrie<V> {
90
91
    /**
92
     * Logger of this class.
93
     */
94
    private static final Logger LOGGER = Logger.getLogger(FrequencyTrie.class.getName());
95
96
    /**
97
     * Binary format magic header.
98
     */
99
    private static final int STREAM_MAGIC = 0x45475452;
100
101
    /**
102
     * Binary format version.
103
     */
104
    private static final int STREAM_VERSION = 1;
105
106
    /**
107
     * Factory used to create correctly typed arrays for {@link #getAll(String)}.
108
     */
109
    private final IntFunction<V[]> arrayFactory;
110
111
    /**
112
     * Root node of the compiled read-only trie.
113
     */
114
    private final CompiledNode<V> root;
115
116
    /**
117
     * Creates a new compiled trie instance.
118
     *
119
     * @param arrayFactory array factory
120
     * @param root         compiled root node
121
     * @throws NullPointerException if any argument is {@code null}
122
     */
123
    private FrequencyTrie(final IntFunction<V[]> arrayFactory, final CompiledNode<V> root) {
124
        this.arrayFactory = Objects.requireNonNull(arrayFactory, "arrayFactory");
125
        this.root = Objects.requireNonNull(root, "root");
126
    }
127
128
    /**
129
     * Returns the most frequent value stored at the node addressed by the supplied
130
     * key.
131
     *
132
     * <p>
133
     * If multiple values have the same local frequency, the returned value is
134
     * selected deterministically by shorter {@code toString()} value first, then by
135
     * lexicographically lower {@code toString()}, and finally by stable first-seen
136
     * order.
137
     * 
138
     * @param key key to resolve
139
     * @return most frequent value, or {@code null} if the key does not exist or no
140
     *         value is stored at the addressed node
141
     * @throws NullPointerException if {@code key} is {@code null}
142
     */
143
    public V get(final String key) {
144
        Objects.requireNonNull(key, "key");
145
        final CompiledNode<V> node = findNode(key);
146 2 1. get : negated conditional → KILLED
2. get : negated conditional → KILLED
        if (node == null || node.orderedValues().length == 0) {
147
            return null;
148
        }
149 1 1. get : replaced return value with null for org/egothor/stemmer/FrequencyTrie::get → KILLED
        return node.orderedValues()[0];
150
    }
151
152
    /**
153
     * Returns all values stored at the node addressed by the supplied key, ordered
154
     * by descending frequency.
155
     *
156
     * <p>
157
     * If multiple values have the same local frequency, the ordering is
158
     * deterministic by shorter {@code toString()} value first, then by
159
     * lexicographically lower {@code toString()}, and finally by stable first-seen
160
     * order.
161
     *
162
     * <p>
163
     * The returned array is a defensive copy.
164
     *
165
     * @param key key to resolve
166
     * @return all values stored at the addressed node, ordered by descending
167
     *         frequency; returns an empty array if the key does not exist or no
168
     *         value is stored at the addressed node
169
     * @throws NullPointerException if {@code key} is {@code null}
170
     */
171
    public V[] getAll(final String key) {
172
        Objects.requireNonNull(key, "key");
173
        final CompiledNode<V> node = findNode(key);
174 2 1. getAll : negated conditional → KILLED
2. getAll : negated conditional → KILLED
        if (node == null || node.orderedValues().length == 0) {
175 1 1. getAll : replaced return value with null for org/egothor/stemmer/FrequencyTrie::getAll → KILLED
            return this.arrayFactory.apply(0);
176
        }
177 1 1. getAll : replaced return value with null for org/egothor/stemmer/FrequencyTrie::getAll → KILLED
        return Arrays.copyOf(node.orderedValues(), node.orderedValues().length);
178
    }
179
180
    /**
181
     * Returns all values stored at the node addressed by the supplied key together
182
     * with their occurrence counts, ordered by the same rules as
183
     * {@link #getAll(String)}.
184
     *
185
     * <p>
186
     * The returned list is aligned with the arrays returned by
187
     * {@link #getAll(String)} and the internal compiled count representation.
188
     *
189
     * <p>
190
     * The returned list is immutable.
191
     *
192
     * <p>
193
     * In reduction modes that merge semantically equivalent subtrees, the returned
194
     * counts may be aggregated across multiple original build-time nodes that were
195
     * reduced into the same canonical compiled node.
196
     *
197
     * @param key key to resolve
198
     * @return immutable ordered list of value-count entries; returns an empty list
199
     *         if the key does not exist or no value is stored at the addressed node
200
     * @throws NullPointerException if {@code key} is {@code null}
201
     */
202
    public List<ValueCount<V>> getEntries(final String key) {
203
        Objects.requireNonNull(key, "key");
204
        final CompiledNode<V> node = findNode(key);
205 2 1. getEntries : negated conditional → KILLED
2. getEntries : negated conditional → KILLED
        if (node == null || node.orderedValues().length == 0) {
206
            return List.of();
207
        }
208
209
        final List<ValueCount<V>> entries = new ArrayList<>(node.orderedValues().length);
210 2 1. getEntries : changed conditional boundary → KILLED
2. getEntries : negated conditional → KILLED
        for (int index = 0; index < node.orderedValues().length; index++) {
211
            entries.add(new ValueCount<>(node.orderedValues()[index], node.orderedCounts()[index]));
212
        }
213 1 1. getEntries : replaced return value with Collections.emptyList for org/egothor/stemmer/FrequencyTrie::getEntries → KILLED
        return Collections.unmodifiableList(entries);
214
    }
215
216
    /**
217
     * Returns the root node mainly for diagnostics and tests within the package.
218
     *
219
     * @return compiled root node
220
     */
221
    /* default */ CompiledNode<V> root() {
222 1 1. root : replaced return value with null for org/egothor/stemmer/FrequencyTrie::root → KILLED
        return this.root;
223
    }
224
225
    /**
226
     * Writes this compiled trie to the supplied output stream.
227
     *
228
     * <p>
229
     * The binary format is versioned and preserves canonical shared compiled nodes,
230
     * therefore the serialized representation remains compact even for tries
231
     * reduced by subtree merging.
232
     *
233
     * <p>
234
     * The supplied codec is responsible for persisting individual values of type
235
     * {@code V}.
236
     *
237
     * @param outputStream target output stream
238
     * @param valueCodec   codec used to write values
239
     * @throws NullPointerException if any argument is {@code null}
240
     * @throws IOException          if writing fails
241
     */
242
    public void writeTo(final OutputStream outputStream, final ValueStreamCodec<V> valueCodec) throws IOException {
243
        Objects.requireNonNull(outputStream, "outputStream");
244
        Objects.requireNonNull(valueCodec, "valueCodec");
245
246
        final DataOutputStream dataOutput; // NOPMD
247 1 1. writeTo : negated conditional → KILLED
        if (outputStream instanceof DataOutputStream) {
248
            dataOutput = (DataOutputStream) outputStream;
249
        } else {
250
            dataOutput = new DataOutputStream(outputStream);
251
        }
252
253
        final Map<CompiledNode<V>, Integer> nodeIds = new IdentityHashMap<>();
254
        final List<CompiledNode<V>> orderedNodes = new ArrayList<>();
255 1 1. writeTo : removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED
        assignNodeIds(this.root, nodeIds, orderedNodes);
256
257
        if (LOGGER.isLoggable(Level.FINE)) {
258
            LOGGER.log(Level.FINE, "Writing compiled trie with {0} canonical nodes.", orderedNodes.size());
259
        }
260
261 1 1. writeTo : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(STREAM_MAGIC);
262 1 1. writeTo : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(STREAM_VERSION);
263 1 1. writeTo : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(orderedNodes.size());
264 1 1. writeTo : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(nodeIds.get(this.root));
265
266
        for (CompiledNode<V> node : orderedNodes) {
267 1 1. writeTo : removed call to org/egothor/stemmer/FrequencyTrie::writeNode → KILLED
            writeNode(dataOutput, valueCodec, node, nodeIds);
268
        }
269
270 1 1. writeTo : removed call to java/io/DataOutputStream::flush → SURVIVED
        dataOutput.flush();
271
    }
272
273
    /**
274
     * Reads a compiled trie from the supplied input stream.
275
     *
276
     * <p>
277
     * The caller must provide the same value codec semantics that were used during
278
     * persistence as well as the array factory required for typed result arrays.
279
     *
280
     * @param inputStream  source input stream
281
     * @param arrayFactory factory used to create typed arrays
282
     * @param valueCodec   codec used to read values
283
     * @param <V>          value type
284
     * @return deserialized compiled trie
285
     * @throws NullPointerException if any argument is {@code null}
286
     * @throws IOException          if reading fails or the binary format is invalid
287
     */
288
    public static <V> FrequencyTrie<V> readFrom(final InputStream inputStream, final IntFunction<V[]> arrayFactory,
289
            final ValueStreamCodec<V> valueCodec) throws IOException {
290
        Objects.requireNonNull(inputStream, "inputStream");
291
        Objects.requireNonNull(arrayFactory, "arrayFactory");
292
        Objects.requireNonNull(valueCodec, "valueCodec");
293
294
        final DataInputStream dataInput; // NOPMD
295 1 1. readFrom : negated conditional → KILLED
        if (inputStream instanceof DataInputStream) {
296
            dataInput = (DataInputStream) inputStream;
297
        } else {
298
            dataInput = new DataInputStream(inputStream);
299
        }
300
301
        final int magic = dataInput.readInt();
302 1 1. readFrom : negated conditional → KILLED
        if (magic != STREAM_MAGIC) {
303
            throw new IOException("Unsupported trie stream header: " + Integer.toHexString(magic));
304
        }
305
306
        final int version = dataInput.readInt();
307 1 1. readFrom : negated conditional → KILLED
        if (version != STREAM_VERSION) {
308
            throw new IOException("Unsupported trie stream version: " + version);
309
        }
310
311
        final int nodeCount = dataInput.readInt();
312 2 1. readFrom : changed conditional boundary → SURVIVED
2. readFrom : negated conditional → KILLED
        if (nodeCount < 0) {
313
            throw new IOException("Negative node count: " + nodeCount);
314
        }
315
316
        final int rootNodeId = dataInput.readInt();
317 4 1. readFrom : changed conditional boundary → KILLED
2. readFrom : negated conditional → KILLED
3. readFrom : negated conditional → KILLED
4. readFrom : changed conditional boundary → KILLED
        if (rootNodeId < 0 || rootNodeId >= nodeCount) {
318
            throw new IOException("Invalid root node id: " + rootNodeId);
319
        }
320
321
        final CompiledNode<V>[] nodes = readNodes(dataInput, arrayFactory, valueCodec, nodeCount);
322
        final CompiledNode<V> rootNode = nodes[rootNodeId];
323
324
        if (LOGGER.isLoggable(Level.FINE)) {
325
            LOGGER.log(Level.FINE, "Read compiled trie with {0} canonical nodes.", nodeCount);
326
        }
327
328 1 1. readFrom : replaced return value with null for org/egothor/stemmer/FrequencyTrie::readFrom → KILLED
        return new FrequencyTrie<>(arrayFactory, rootNode);
329
    }
330
331
    /**
332
     * Returns the number of canonical compiled nodes reachable from the root.
333
     *
334
     * <p>
335
     * The returned value reflects the size of the final reduced immutable trie, not
336
     * the number of mutable build-time nodes inserted before reduction. Shared
337
     * canonical subtrees are counted only once.
338
     *
339
     * @return number of canonical compiled nodes in this trie
340
     */
341
    public int size() {
342
        final Map<CompiledNode<V>, Integer> nodeIds = new IdentityHashMap<>();
343
        final List<CompiledNode<V>> orderedNodes = new ArrayList<>();
344 1 1. size : removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED
        assignNodeIds(this.root, nodeIds, orderedNodes);
345 1 1. size : replaced int return with 0 for org/egothor/stemmer/FrequencyTrie::size → KILLED
        return orderedNodes.size();
346
    }
347
348
    /**
349
     * Assigns deterministic identifiers to all canonical compiled nodes reachable
350
     * from the supplied root.
351
     *
352
     * @param node         current node
353
     * @param nodeIds      assigned node identifiers
354
     * @param orderedNodes ordered nodes in identifier order
355
     */
356
    private static <V> void assignNodeIds(final CompiledNode<V> node, final Map<CompiledNode<V>, Integer> nodeIds,
357
            final List<CompiledNode<V>> orderedNodes) {
358 1 1. assignNodeIds : negated conditional → KILLED
        if (nodeIds.containsKey(node)) {
359
            return;
360
        }
361
362
        final int nodeId = orderedNodes.size();
363
        nodeIds.put(node, nodeId);
364
        orderedNodes.add(node);
365
366
        for (CompiledNode<V> child : node.children()) {
367 1 1. assignNodeIds : removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED
            assignNodeIds(child, nodeIds, orderedNodes);
368
        }
369
    }
370
371
    /**
372
     * Writes one compiled node.
373
     *
374
     * @param dataOutput output
375
     * @param valueCodec value codec
376
     * @param node       node to write
377
     * @param nodeIds    node identifiers
378
     * @throws IOException if writing fails
379
     */
380
    private static <V> void writeNode(final DataOutputStream dataOutput, final ValueStreamCodec<V> valueCodec,
381
            final CompiledNode<V> node, final Map<CompiledNode<V>, Integer> nodeIds) throws IOException {
382 1 1. writeNode : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(node.edgeLabels().length);
383 2 1. writeNode : changed conditional boundary → KILLED
2. writeNode : negated conditional → KILLED
        for (int index = 0; index < node.edgeLabels().length; index++) {
384 1 1. writeNode : removed call to java/io/DataOutputStream::writeChar → KILLED
            dataOutput.writeChar(node.edgeLabels()[index]);
385
            final Integer childNodeId = nodeIds.get(node.children()[index]);
386 1 1. writeNode : negated conditional → KILLED
            if (childNodeId == null) {
387
                throw new IOException("Missing child node identifier during serialization.");
388
            }
389 1 1. writeNode : removed call to java/io/DataOutputStream::writeInt → KILLED
            dataOutput.writeInt(childNodeId);
390
        }
391
392 1 1. writeNode : removed call to java/io/DataOutputStream::writeInt → KILLED
        dataOutput.writeInt(node.orderedValues().length);
393 2 1. writeNode : changed conditional boundary → KILLED
2. writeNode : negated conditional → KILLED
        for (int index = 0; index < node.orderedValues().length; index++) {
394 1 1. writeNode : removed call to org/egothor/stemmer/FrequencyTrie$ValueStreamCodec::write → KILLED
            valueCodec.write(dataOutput, node.orderedValues()[index]);
395 1 1. writeNode : removed call to java/io/DataOutputStream::writeInt → KILLED
            dataOutput.writeInt(node.orderedCounts()[index]);
396
        }
397
    }
398
399
    /**
400
     * Reads all compiled nodes and resolves child references.
401
     *
402
     * @param dataInput    input
403
     * @param arrayFactory array factory
404
     * @param valueCodec   value codec
405
     * @param nodeCount    number of nodes
406
     * @param <V>          value type
407
     * @return array of nodes indexed by serialized node identifier
408
     * @throws IOException if reading fails or the stream is invalid
409
     */
410
    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
411
    private static <V> CompiledNode<V>[] readNodes(final DataInputStream dataInput, final IntFunction<V[]> arrayFactory,
412
            final ValueStreamCodec<V> valueCodec, final int nodeCount) throws IOException {
413
        final List<NodeData<V>> nodeDataList = new ArrayList<>(nodeCount);
414
415 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
        for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
416
            final int edgeCount = dataInput.readInt();
417 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            if (edgeCount < 0) {
418
                throw new IOException("Negative edge count at node " + nodeIndex + ": " + edgeCount);
419
            }
420
421
            final char[] edgeLabels = new char[edgeCount];
422
            final int[] childNodeIds = new int[edgeCount];
423
424 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            for (int edgeIndex = 0; edgeIndex < edgeCount; edgeIndex++) {
425
                edgeLabels[edgeIndex] = dataInput.readChar();
426
                childNodeIds[edgeIndex] = dataInput.readInt();
427
            }
428
429 1 1. readNodes : removed call to org/egothor/stemmer/FrequencyTrie::validateSerializedEdges → KILLED
            validateSerializedEdges(nodeIndex, edgeLabels);
430
431
            final int valueCount = dataInput.readInt();
432 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            if (valueCount < 0) {
433
                throw new IOException("Negative value count at node " + nodeIndex + ": " + valueCount);
434
            }
435
436
            final V[] orderedValues = arrayFactory.apply(valueCount);
437
            final int[] orderedCounts = new int[valueCount];
438
439 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
            for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
440
                orderedValues[valueIndex] = valueCodec.read(dataInput);
441
                orderedCounts[valueIndex] = dataInput.readInt();
442 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
                if (orderedCounts[valueIndex] <= 0) {
443
                    throw new IOException("Non-positive stored count at node " + nodeIndex + ", value index "
444
                            + valueIndex + ": " + orderedCounts[valueIndex]);
445
                }
446
            }
447
448
            nodeDataList.add(new NodeData<>(edgeLabels, childNodeIds, orderedValues, orderedCounts));
449
        }
450
451
        @SuppressWarnings("unchecked")
452
        final CompiledNode<V>[] nodes = new CompiledNode[nodeCount];
453
454 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
        for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
455
            final NodeData<V> nodeData = nodeDataList.get(nodeIndex);
456
            @SuppressWarnings("unchecked")
457
            final CompiledNode<V>[] children = new CompiledNode[nodeData.childNodeIds().length];
458
            nodes[nodeIndex] = new CompiledNode<>(nodeData.edgeLabels(), children, nodeData.orderedValues(),
459
                    nodeData.orderedCounts());
460
        }
461
462 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
        for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
463
            final NodeData<V> nodeData = nodeDataList.get(nodeIndex);
464
            final CompiledNode<V> node = nodes[nodeIndex];
465
466 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            for (int edgeIndex = 0; edgeIndex < nodeData.childNodeIds().length; edgeIndex++) {
467
                final int childNodeId = nodeData.childNodeIds()[edgeIndex];
468 4 1. readNodes : changed conditional boundary → SURVIVED
2. readNodes : changed conditional boundary → SURVIVED
3. readNodes : negated conditional → KILLED
4. readNodes : negated conditional → KILLED
                if (childNodeId < 0 || childNodeId >= nodeCount) {
469
                    throw new IOException("Invalid child node id at node " + nodeIndex + ", edge index " + edgeIndex
470
                            + ": " + childNodeId);
471
                }
472
                node.children()[edgeIndex] = nodes[childNodeId];
473
            }
474
        }
475
476 1 1. readNodes : replaced return value with null for org/egothor/stemmer/FrequencyTrie::readNodes → KILLED
        return nodes;
477
    }
478
479
    /**
480
     * Validates the serialized edge-label sequence for one node.
481
     *
482
     * <p>
483
     * Compiled nodes rely on binary search for child lookup and therefore require
484
     * edge labels to be stored in strict ascending order without duplicates.
485
     * Rejecting malformed streams here keeps lookup semantics deterministic and
486
     * avoids silently constructing a trie whose search behavior would be undefined.
487
     *
488
     * @param nodeIndex  serialized node identifier
489
     * @param edgeLabels serialized edge labels
490
     * @throws IOException if the edge labels are not strictly ascending
491
     */
492
    private static void validateSerializedEdges(final int nodeIndex, final char... edgeLabels) throws IOException {
493 2 1. validateSerializedEdges : negated conditional → KILLED
2. validateSerializedEdges : changed conditional boundary → KILLED
        for (int edgeIndex = 1; edgeIndex < edgeLabels.length; edgeIndex++) {
494 3 1. validateSerializedEdges : changed conditional boundary → SURVIVED
2. validateSerializedEdges : negated conditional → KILLED
3. validateSerializedEdges : Replaced integer subtraction with addition → KILLED
            if (edgeLabels[edgeIndex - 1] >= edgeLabels[edgeIndex]) {
495 1 1. validateSerializedEdges : Replaced integer subtraction with addition → KILLED
                throw new IOException("Edge labels must be strictly ascending at node " + nodeIndex + ", edge index "
496
                        + edgeIndex + ": '" + edgeLabels[edgeIndex - 1] + "' then '" + edgeLabels[edgeIndex] + "'.");
497
            }
498
        }
499
    }
500
501
    /**
502
     * Locates the compiled node for the supplied key.
503
     *
504
     * @param key key to resolve
505
     * @return compiled node, or {@code null} if the path does not exist
506
     */
507
    private CompiledNode<V> findNode(final String key) {
508
        CompiledNode<V> current = this.root;
509 2 1. findNode : changed conditional boundary → KILLED
2. findNode : negated conditional → KILLED
        for (int index = 0; index < key.length(); index++) {
510
            current = current.findChild(key.charAt(index));
511 1 1. findNode : negated conditional → KILLED
            if (current == null) {
512
                return null;
513
            }
514
        }
515 1 1. findNode : replaced return value with null for org/egothor/stemmer/FrequencyTrie::findNode → KILLED
        return current;
516
    }
517
518
    /**
519
     * Builder of {@link FrequencyTrie}.
520
     *
521
     * <p>
522
     * The builder is intentionally mutable and optimized for repeated
523
     * {@link #put(String, Object)} calls. The final trie is created by
524
     * {@link #build()}, which performs bottom-up subtree reduction and converts the
525
     * structure to a compact immutable representation optimized for read
526
     * operations.
527
     *
528
     * @param <V> value type
529
     */
530
    public static final class Builder<V> {
531
532
        /**
533
         * Logger of this class.
534
         */
535
        private static final Logger LOGGER = Logger.getLogger(Builder.class.getName());
536
537
        /**
538
         * Factory used to create typed arrays.
539
         */
540
        private final IntFunction<V[]> arrayFactory;
541
542
        /**
543
         * Reduction configuration.
544
         */
545
        private final ReductionSettings reductionSettings;
546
547
        /**
548
         * Mutable root node.
549
         */
550
        private final MutableNode<V> root;
551
552
        /**
553
         * Creates a new builder with the provided settings.
554
         *
555
         * @param arrayFactory      array factory
556
         * @param reductionSettings reduction configuration
557
         * @throws NullPointerException if any argument is {@code null}
558
         */
559
        public Builder(final IntFunction<V[]> arrayFactory, final ReductionSettings reductionSettings) {
560
            this.arrayFactory = Objects.requireNonNull(arrayFactory, "arrayFactory");
561
            this.reductionSettings = Objects.requireNonNull(reductionSettings, "reductionSettings");
562
            this.root = new MutableNode<>();
563
        }
564
565
        /**
566
         * Creates a new builder using default thresholds for the supplied reduction
567
         * mode.
568
         *
569
         * @param arrayFactory  array factory
570
         * @param reductionMode reduction mode
571
         * @throws NullPointerException if any argument is {@code null}
572
         */
573
        public Builder(final IntFunction<V[]> arrayFactory, final ReductionMode reductionMode) {
574
            this(arrayFactory, ReductionSettings.withDefaults(reductionMode));
575
        }
576
577
        /**
578
         * Stores a value for the supplied key and increments its local frequency.
579
         *
580
         * <p>
581
         * Values are stored at the node addressed by the full key. Since trie values
582
         * may also appear on internal nodes, an empty key is valid and stores a value
583
         * directly at the root.
584
         *
585
         * @param key   key
586
         * @param value value
587
         * @return this builder
588
         * @throws NullPointerException if {@code key} or {@code value} is {@code null}
589
         */
590
        public Builder<V> put(final String key, final V value) {
591 1 1. put : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED
            return put(key, value, 1);
592
        }
593
594
        /**
595
         * Builds a compiled read-only trie.
596
         *
597
         * @return compiled trie
598
         */
599
        public FrequencyTrie<V> build() {
600
            if (LOGGER.isLoggable(Level.FINE)) {
601
                LOGGER.log(Level.FINE, "Starting trie compilation with reduction mode {0}.",
602
                        this.reductionSettings.reductionMode());
603
            }
604
605
            final ReductionContext<V> reductionContext = new ReductionContext<>(this.reductionSettings);
606
            final ReducedNode<V> reducedRoot = reduce(this.root, reductionContext);
607
            final CompiledNode<V> compiledRoot = freeze(reducedRoot, new IdentityHashMap<>());
608
609
            if (LOGGER.isLoggable(Level.FINE)) {
610
                LOGGER.log(Level.FINE, "Trie compilation finished. Canonical node count: {0}.",
611
                        reductionContext.canonicalNodeCount());
612
            }
613
614 1 1. build : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::build → KILLED
            return new FrequencyTrie<>(this.arrayFactory, compiledRoot);
615
        }
616
617
        /**
618
         * Stores a value for the supplied key and increments its local frequency by the
619
         * specified positive count.
620
         *
621
         * <p>
622
         * Values are stored at the node addressed by the full key. Since trie values
623
         * may also appear on internal nodes, an empty key is valid and stores a value
624
         * directly at the root.
625
         *
626
         * <p>
627
         * This method is functionally equivalent to calling
628
         * {@link #put(String, Object)} repeatedly {@code count} times, but it avoids
629
         * unnecessary repeated map updates and is therefore preferable for bulk
630
         * reconstruction from compiled tries or other aggregated sources.
631
         *
632
         * @param key   key
633
         * @param value value
634
         * @param count positive frequency increment
635
         * @return this builder
636
         * @throws NullPointerException     if {@code key} or {@code value} is
637
         *                                  {@code null}
638
         * @throws IllegalArgumentException if {@code count} is less than {@code 1}
639
         */
640
        public Builder<V> put(final String key, final V value, final int count) {
641
            Objects.requireNonNull(key, "key");
642
            Objects.requireNonNull(value, "value");
643
644 2 1. put : changed conditional boundary → KILLED
2. put : negated conditional → KILLED
            if (count < 1) { // NOPMD
645
                throw new IllegalArgumentException("count must be at least 1.");
646
            }
647
648
            MutableNode<V> current = this.root;
649 2 1. put : negated conditional → KILLED
2. put : changed conditional boundary → KILLED
            for (int index = 0; index < key.length(); index++) {
650
                final Character edge = key.charAt(index);
651
                MutableNode<V> child = current.children().get(edge);
652 1 1. put : negated conditional → KILLED
                if (child == null) {
653
                    child = new MutableNode<>(); // NOPMD
654
                    current.children().put(edge, child);
655
                }
656
                current = child;
657
            }
658
659
            final Integer previous = current.valueCounts().get(value);
660 1 1. put : negated conditional → KILLED
            if (previous == null) {
661
                current.valueCounts().put(value, count);
662
            } else {
663 1 1. put : Replaced integer addition with subtraction → KILLED
                current.valueCounts().put(value, previous + count);
664
            }
665 1 1. put : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED
            return this;
666
        }
667
668
        /**
669
         * Returns the number of mutable build-time nodes currently reachable from the
670
         * builder root.
671
         *
672
         * <p>
673
         * This metric is intended mainly for diagnostics and tests that compare the
674
         * unreduced build-time structure with the final reduced compiled trie.
675
         *
676
         * @return number of mutable build-time nodes
677
         */
678
        /* default */ int buildTimeSize() {
679 1 1. buildTimeSize : replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::buildTimeSize → KILLED
            return countMutableNodes(this.root);
680
        }
681
682
        /**
683
         * Counts mutable nodes recursively.
684
         *
685
         * @param node current node
686
         * @return reachable mutable node count
687
         */
688
        private int countMutableNodes(final MutableNode<V> node) {
689
            int count = 1;
690
            for (MutableNode<V> child : node.children().values()) {
691 1 1. countMutableNodes : Replaced integer addition with subtraction → KILLED
                count += countMutableNodes(child);
692
            }
693 1 1. countMutableNodes : replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::countMutableNodes → KILLED
            return count;
694
        }
695
696
        /**
697
         * Reduces a mutable node to a canonical reduced node.
698
         *
699
         * @param source  source mutable node
700
         * @param context reduction context
701
         * @return canonical reduced node
702
         */
703
        private ReducedNode<V> reduce(final MutableNode<V> source, final ReductionContext<V> context) {
704
            final Map<Character, ReducedNode<V>> reducedChildren = new LinkedHashMap<>();
705
706
            for (Map.Entry<Character, MutableNode<V>> childEntry : source.children().entrySet()) {
707
                final ReducedNode<V> reducedChild = reduce(childEntry.getValue(), context);
708
                reducedChildren.put(childEntry.getKey(), reducedChild);
709
            }
710
711
            final Map<V, Integer> localCounts = copyCounts(source.valueCounts());
712
            final LocalValueSummary<V> localSummary = LocalValueSummary.of(localCounts, this.arrayFactory);
713
            final ReductionSignature<V> signature = ReductionSignature.create(localSummary, reducedChildren,
714
                    context.settings());
715
716
            ReducedNode<V> canonical = context.lookup(signature);
717 1 1. reduce : negated conditional → KILLED
            if (canonical == null) {
718
                canonical = new ReducedNode<>(signature, localCounts, reducedChildren);
719 1 1. reduce : removed call to org/egothor/stemmer/trie/ReductionContext::register → KILLED
                context.register(signature, canonical);
720 1 1. reduce : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED
                return canonical;
721
            }
722
723 1 1. reduce : removed call to org/egothor/stemmer/trie/ReducedNode::mergeLocalCounts → KILLED
            canonical.mergeLocalCounts(localCounts);
724 1 1. reduce : removed call to org/egothor/stemmer/trie/ReducedNode::mergeChildren → SURVIVED
            canonical.mergeChildren(reducedChildren);
725
726 1 1. reduce : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED
            return canonical;
727
        }
728
729
        /**
730
         * Freezes a reduced node into an immutable compiled node.
731
         *
732
         * @param reducedNode reduced node
733
         * @param cache       already frozen nodes
734
         * @return immutable compiled node
735
         */
736
        private CompiledNode<V> freeze(final ReducedNode<V> reducedNode,
737
                final Map<ReducedNode<V>, CompiledNode<V>> cache) {
738
            final CompiledNode<V> existing = cache.get(reducedNode);
739 1 1. freeze : negated conditional → KILLED
            if (existing != null) {
740 1 1. freeze : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED
                return existing;
741
            }
742
743
            final LocalValueSummary<V> localSummary = LocalValueSummary.of(reducedNode.localCounts(),
744
                    this.arrayFactory);
745
746
            final List<Map.Entry<Character, ReducedNode<V>>> childEntries = new ArrayList<>(
747
                    reducedNode.children().entrySet());
748 1 1. freeze : removed call to java/util/List::sort → KILLED
            childEntries.sort(Map.Entry.comparingByKey());
749
750
            final char[] edges = new char[childEntries.size()];
751
            @SuppressWarnings("unchecked")
752
            final CompiledNode<V>[] childNodes = new CompiledNode[childEntries.size()];
753
754 2 1. freeze : negated conditional → KILLED
2. freeze : changed conditional boundary → KILLED
            for (int index = 0; index < childEntries.size(); index++) {
755
                final Map.Entry<Character, ReducedNode<V>> entry = childEntries.get(index);
756
                edges[index] = entry.getKey();
757
                childNodes[index] = freeze(entry.getValue(), cache);
758
            }
759
760
            final CompiledNode<V> frozen = new CompiledNode<>(edges, childNodes, localSummary.orderedValues(),
761
                    localSummary.orderedCounts());
762
            cache.put(reducedNode, frozen);
763 1 1. freeze : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED
            return frozen;
764
        }
765
766
        /**
767
         * Creates a shallow frequency copy preserving deterministic insertion order of
768
         * first occurrence.
769
         *
770
         * @param source source counts
771
         * @return copied counts
772
         */
773
        private Map<V, Integer> copyCounts(final Map<V, Integer> source) {
774 1 1. copyCounts : replaced return value with Collections.emptyMap for org/egothor/stemmer/FrequencyTrie$Builder::copyCounts → KILLED
            return new LinkedHashMap<>(source);
775
        }
776
    }
777
778
    /**
779
     * Codec used to persist values stored in the trie.
780
     *
781
     * @param <V> value type
782
     */
783
    public interface ValueStreamCodec<V> {
784
785
        /**
786
         * Writes one value to the supplied data output.
787
         *
788
         * @param dataOutput target data output
789
         * @param value      value to write
790
         * @throws IOException if writing fails
791
         */
792
        void write(DataOutputStream dataOutput, V value) throws IOException;
793
794
        /**
795
         * Reads one value from the supplied data input.
796
         *
797
         * @param dataInput source data input
798
         * @return read value
799
         * @throws IOException if reading fails
800
         */
801
        V read(DataInputStream dataInput) throws IOException;
802
    }
803
804
}

Mutations

146

1.1
Location : get
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
negated conditional → KILLED

2.2
Location : get
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

149

1.1
Location : get
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::get → KILLED

174

1.1
Location : getAll
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
negated conditional → KILLED

2.2
Location : getAll
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

175

1.1
Location : getAll
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::getAll → KILLED

177

1.1
Location : getAll
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::getAll → KILLED

205

1.1
Location : getEntries
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

2.2
Location : getEntries
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
negated conditional → KILLED

210

1.1
Location : getEntries
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
changed conditional boundary → KILLED

2.2
Location : getEntries
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

213

1.1
Location : getEntries
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
replaced return value with Collections.emptyList for org/egothor/stemmer/FrequencyTrie::getEntries → KILLED

222

1.1
Location : root
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldReconstructEmptyTrie()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::root → KILLED

247

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
negated conditional → KILLED

255

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED

261

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

262

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

263

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

264

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

267

1.1
Location : writeTo
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to org/egothor/stemmer/FrequencyTrie::writeNode → KILLED

270

1.1
Location : writeTo
Killed by : none
removed call to java/io/DataOutputStream::flush → SURVIVED
Covering tests

295

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsUnsupportedStreamVersion()]
negated conditional → KILLED

302

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsUnsupportedStreamVersion()]
negated conditional → KILLED

307

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsUnsupportedStreamVersion()]
negated conditional → KILLED

312

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsInvalidRootNodeIdentifier()]
negated conditional → KILLED

2.2
Location : readFrom
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

317

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
changed conditional boundary → KILLED

2.2
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

3.3
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsInvalidRootNodeIdentifier()]
negated conditional → KILLED

4.4
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsInvalidRootNodeIdentifier()]
changed conditional boundary → KILLED

328

1.1
Location : readFrom
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::readFrom → KILLED

344

1.1
Location : size
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED

345

1.1
Location : size
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
replaced int return with 0 for org/egothor/stemmer/FrequencyTrie::size → KILLED

358

1.1
Location : assignNodeIds
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
negated conditional → KILLED

367

1.1
Location : assignNodeIds
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
removed call to org/egothor/stemmer/FrequencyTrie::assignNodeIds → KILLED

382

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

383

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
changed conditional boundary → KILLED

2.2
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
negated conditional → KILLED

384

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeChar → KILLED

386

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

389

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

392

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

393

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
changed conditional boundary → KILLED

2.2
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRejectNullArguments()]
negated conditional → KILLED

394

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to org/egothor/stemmer/FrequencyTrie$ValueStreamCodec::write → KILLED

395

1.1
Location : writeNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
removed call to java/io/DataOutputStream::writeInt → KILLED

415

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

417

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
changed conditional boundary → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

424

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
changed conditional boundary → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

429

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonAscendingSerializedEdgeLabels()]
removed call to org/egothor/stemmer/FrequencyTrie::validateSerializedEdges → KILLED

432

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

439

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

442

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
changed conditional boundary → KILLED

454

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

462

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

466

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

468

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

2.2
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
negated conditional → KILLED

3.3
Location : readNodes
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

4.4
Location : readNodes
Killed by : none
changed conditional boundary → SURVIVED Covering tests

476

1.1
Location : readNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::readNodes → KILLED

493

1.1
Location : validateSerializedEdges
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonPositiveStoredCounts()]
negated conditional → KILLED

2.2
Location : validateSerializedEdges
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:writeToAndReadFromRoundTripTrieContent()]
changed conditional boundary → KILLED

494

1.1
Location : validateSerializedEdges
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonAscendingSerializedEdgeLabels()]
negated conditional → KILLED

2.2
Location : validateSerializedEdges
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

3.3
Location : validateSerializedEdges
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonAscendingSerializedEdgeLabels()]
Replaced integer subtraction with addition → KILLED

495

1.1
Location : validateSerializedEdges
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:readFromRejectsNonAscendingSerializedEdgeLabels()]
Replaced integer subtraction with addition → KILLED

509

1.1
Location : findNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
changed conditional boundary → KILLED

2.2
Location : findNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

511

1.1
Location : findNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
negated conditional → KILLED

515

1.1
Location : findNode
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie::findNode → KILLED

591

1.1
Location : put
Killed by : org.egothor.stemmer.StemmerPatchTrieLoaderTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieLoaderTest]/[nested-class:ApiContractTests]/[test-template:shouldRejectNullArguments(java.lang.String, org.egothor.stemmer.StemmerPatchTrieLoaderTest$ExecutableOperation, java.lang.String)]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED

614

1.1
Location : build
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyTrieReturnsNullEmptyArrayAndEmptyEntries()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::build → KILLED

644

1.1
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
changed conditional boundary → KILLED

2.2
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:builderRejectsNonPositiveCountedInsertion()]
negated conditional → KILLED

649

1.1
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

2.2
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
changed conditional boundary → KILLED

652

1.1
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:missingPathBelowExistingPrefixReturnsEmptyResults()]
negated conditional → KILLED

660

1.1
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
negated conditional → KILLED

663

1.1
Location : put
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
Replaced integer addition with subtraction → KILLED

665

1.1
Location : put
Killed by : org.egothor.stemmer.StemmerPatchTrieLoaderTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieLoaderTest]/[nested-class:ApiContractTests]/[test-template:shouldRejectNullArguments(java.lang.String, org.egothor.stemmer.StemmerPatchTrieLoaderTest$ExecutableOperation, java.lang.String)]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED

679

1.1
Location : buildTimeSize
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::buildTimeSize → KILLED

691

1.1
Location : countMutableNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
Replaced integer addition with subtraction → KILLED

693

1.1
Location : countMutableNodes
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionMateriallyDecreasesCompiledTrieSizeForRepeatedEquivalentSuffixes()]
replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::countMutableNodes → KILLED

717

1.1
Location : reduce
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
negated conditional → KILLED

719

1.1
Location : reduce
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:unorderedReductionMergesNodesWithSameGetAllValueSet()]
removed call to org/egothor/stemmer/trie/ReductionContext::register → KILLED

720

1.1
Location : reduce
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED

723

1.1
Location : reduce
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:dominantReductionMergesQualifiedDominantWinnerNodes()]
removed call to org/egothor/stemmer/trie/ReducedNode::mergeLocalCounts → KILLED

724

1.1
Location : reduce
Killed by : none
removed call to org/egothor/stemmer/trie/ReducedNode::mergeChildren → SURVIVED
Covering tests

726

1.1
Location : reduce
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionTakesInternalNodeLocalValuesIntoAccount()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED

739

1.1
Location : freeze
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
negated conditional → KILLED

740

1.1
Location : freeze
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:reductionTakesInternalNodeLocalValuesIntoAccount()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED

748

1.1
Location : freeze
Killed by : org.egothor.stemmer.StemmerPatchTrieLoaderTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieLoaderTest]/[nested-class:FilesystemAndParserTests]/[method:shouldIgnoreHashAndDoubleSlashRemarks()]
removed call to java/util/List::sort → KILLED

754

1.1
Location : freeze
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
negated conditional → KILLED

2.2
Location : freeze
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
changed conditional boundary → KILLED

763

1.1
Location : freeze
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED

774

1.1
Location : copyCounts
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:emptyKeyStoresValuesAtRootNode()]
replaced return value with Collections.emptyMap for org/egothor/stemmer/FrequencyTrie$Builder::copyCounts → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1