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
            final int valueCount = dataInput.readInt();
430 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            if (valueCount < 0) {
431
                throw new IOException("Negative value count at node " + nodeIndex + ": " + valueCount);
432
            }
433
434
            final V[] orderedValues = arrayFactory.apply(valueCount);
435
            final int[] orderedCounts = new int[valueCount];
436
437 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
            for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
438
                orderedValues[valueIndex] = valueCodec.read(dataInput);
439
                orderedCounts[valueIndex] = dataInput.readInt();
440 2 1. readNodes : negated conditional → KILLED
2. readNodes : changed conditional boundary → KILLED
                if (orderedCounts[valueIndex] <= 0) {
441
                    throw new IOException("Non-positive stored count at node " + nodeIndex + ", value index "
442
                            + valueIndex + ": " + orderedCounts[valueIndex]);
443
                }
444
            }
445
446
            nodeDataList.add(new NodeData<>(edgeLabels, childNodeIds, orderedValues, orderedCounts));
447
        }
448
449
        @SuppressWarnings("unchecked")
450
        final CompiledNode<V>[] nodes = new CompiledNode[nodeCount];
451
452 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
        for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
453
            final NodeData<V> nodeData = nodeDataList.get(nodeIndex);
454
            @SuppressWarnings("unchecked")
455
            final CompiledNode<V>[] children = new CompiledNode[nodeData.childNodeIds().length];
456
            nodes[nodeIndex] = new CompiledNode<>(nodeData.edgeLabels(), children, nodeData.orderedValues(),
457
                    nodeData.orderedCounts());
458
        }
459
460 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
        for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
461
            final NodeData<V> nodeData = nodeDataList.get(nodeIndex);
462
            final CompiledNode<V> node = nodes[nodeIndex];
463
464 2 1. readNodes : changed conditional boundary → KILLED
2. readNodes : negated conditional → KILLED
            for (int edgeIndex = 0; edgeIndex < nodeData.childNodeIds().length; edgeIndex++) {
465
                final int childNodeId = nodeData.childNodeIds()[edgeIndex];
466 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) {
467
                    throw new IOException("Invalid child node id at node " + nodeIndex + ", edge index " + edgeIndex
468
                            + ": " + childNodeId);
469
                }
470
                node.children()[edgeIndex] = nodes[childNodeId];
471
            }
472
        }
473
474 1 1. readNodes : replaced return value with null for org/egothor/stemmer/FrequencyTrie::readNodes → KILLED
        return nodes;
475
    }
476
477
    /**
478
     * Locates the compiled node for the supplied key.
479
     *
480
     * @param key key to resolve
481
     * @return compiled node, or {@code null} if the path does not exist
482
     */
483
    private CompiledNode<V> findNode(final String key) {
484
        CompiledNode<V> current = this.root;
485 2 1. findNode : changed conditional boundary → KILLED
2. findNode : negated conditional → KILLED
        for (int index = 0; index < key.length(); index++) {
486
            current = current.findChild(key.charAt(index));
487 1 1. findNode : negated conditional → KILLED
            if (current == null) {
488
                return null;
489
            }
490
        }
491 1 1. findNode : replaced return value with null for org/egothor/stemmer/FrequencyTrie::findNode → KILLED
        return current;
492
    }
493
494
    /**
495
     * Builder of {@link FrequencyTrie}.
496
     *
497
     * <p>
498
     * The builder is intentionally mutable and optimized for repeated
499
     * {@link #put(String, Object)} calls. The final trie is created by
500
     * {@link #build()}, which performs bottom-up subtree reduction and converts the
501
     * structure to a compact immutable representation optimized for read
502
     * operations.
503
     *
504
     * @param <V> value type
505
     */
506
    public static final class Builder<V> {
507
508
        /**
509
         * Logger of this class.
510
         */
511
        private static final Logger LOGGER = Logger.getLogger(Builder.class.getName());
512
513
        /**
514
         * Factory used to create typed arrays.
515
         */
516
        private final IntFunction<V[]> arrayFactory;
517
518
        /**
519
         * Reduction configuration.
520
         */
521
        private final ReductionSettings reductionSettings;
522
523
        /**
524
         * Mutable root node.
525
         */
526
        private final MutableNode<V> root;
527
528
        /**
529
         * Creates a new builder with the provided settings.
530
         *
531
         * @param arrayFactory      array factory
532
         * @param reductionSettings reduction configuration
533
         * @throws NullPointerException if any argument is {@code null}
534
         */
535
        public Builder(final IntFunction<V[]> arrayFactory, final ReductionSettings reductionSettings) {
536
            this.arrayFactory = Objects.requireNonNull(arrayFactory, "arrayFactory");
537
            this.reductionSettings = Objects.requireNonNull(reductionSettings, "reductionSettings");
538
            this.root = new MutableNode<>();
539
        }
540
541
        /**
542
         * Creates a new builder using default thresholds for the supplied reduction
543
         * mode.
544
         *
545
         * @param arrayFactory  array factory
546
         * @param reductionMode reduction mode
547
         * @throws NullPointerException if any argument is {@code null}
548
         */
549
        public Builder(final IntFunction<V[]> arrayFactory, final ReductionMode reductionMode) {
550
            this(arrayFactory, ReductionSettings.withDefaults(reductionMode));
551
        }
552
553
        /**
554
         * Stores a value for the supplied key and increments its local frequency.
555
         *
556
         * <p>
557
         * Values are stored at the node addressed by the full key. Since trie values
558
         * may also appear on internal nodes, an empty key is valid and stores a value
559
         * directly at the root.
560
         *
561
         * @param key   key
562
         * @param value value
563
         * @return this builder
564
         * @throws NullPointerException if {@code key} or {@code value} is {@code null}
565
         */
566
        public Builder<V> put(final String key, final V value) {
567 1 1. put : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED
            return put(key, value, 1);
568
        }
569
570
        /**
571
         * Builds a compiled read-only trie.
572
         *
573
         * @return compiled trie
574
         */
575
        public FrequencyTrie<V> build() {
576
            if (LOGGER.isLoggable(Level.FINE)) {
577
                LOGGER.log(Level.FINE, "Starting trie compilation with reduction mode {0}.",
578
                        this.reductionSettings.reductionMode());
579
            }
580
581
            final ReductionContext<V> reductionContext = new ReductionContext<>(this.reductionSettings);
582
            final ReducedNode<V> reducedRoot = reduce(this.root, reductionContext);
583
            final CompiledNode<V> compiledRoot = freeze(reducedRoot, new IdentityHashMap<>());
584
585
            if (LOGGER.isLoggable(Level.FINE)) {
586
                LOGGER.log(Level.FINE, "Trie compilation finished. Canonical node count: {0}.",
587
                        reductionContext.canonicalNodeCount());
588
            }
589
590 1 1. build : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::build → KILLED
            return new FrequencyTrie<>(this.arrayFactory, compiledRoot);
591
        }
592
593
        /**
594
         * Stores a value for the supplied key and increments its local frequency by the
595
         * specified positive count.
596
         *
597
         * <p>
598
         * Values are stored at the node addressed by the full key. Since trie values
599
         * may also appear on internal nodes, an empty key is valid and stores a value
600
         * directly at the root.
601
         *
602
         * <p>
603
         * This method is functionally equivalent to calling
604
         * {@link #put(String, Object)} repeatedly {@code count} times, but it avoids
605
         * unnecessary repeated map updates and is therefore preferable for bulk
606
         * reconstruction from compiled tries or other aggregated sources.
607
         *
608
         * @param key   key
609
         * @param value value
610
         * @param count positive frequency increment
611
         * @return this builder
612
         * @throws NullPointerException     if {@code key} or {@code value} is
613
         *                                  {@code null}
614
         * @throws IllegalArgumentException if {@code count} is less than {@code 1}
615
         */
616
        public Builder<V> put(final String key, final V value, final int count) {
617
            Objects.requireNonNull(key, "key");
618
            Objects.requireNonNull(value, "value");
619
620 2 1. put : changed conditional boundary → KILLED
2. put : negated conditional → KILLED
            if (count < 1) { // NOPMD
621
                throw new IllegalArgumentException("count must be at least 1.");
622
            }
623
624
            MutableNode<V> current = this.root;
625 2 1. put : negated conditional → KILLED
2. put : changed conditional boundary → KILLED
            for (int index = 0; index < key.length(); index++) {
626
                final Character edge = key.charAt(index);
627
                MutableNode<V> child = current.children().get(edge);
628 1 1. put : negated conditional → KILLED
                if (child == null) {
629
                    child = new MutableNode<>(); // NOPMD
630
                    current.children().put(edge, child);
631
                }
632
                current = child;
633
            }
634
635
            final Integer previous = current.valueCounts().get(value);
636 1 1. put : negated conditional → KILLED
            if (previous == null) {
637
                current.valueCounts().put(value, count);
638
            } else {
639 1 1. put : Replaced integer addition with subtraction → KILLED
                current.valueCounts().put(value, previous + count);
640
            }
641 1 1. put : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::put → KILLED
            return this;
642
        }
643
644
        /**
645
         * Returns the number of mutable build-time nodes currently reachable from the
646
         * builder root.
647
         *
648
         * <p>
649
         * This metric is intended mainly for diagnostics and tests that compare the
650
         * unreduced build-time structure with the final reduced compiled trie.
651
         *
652
         * @return number of mutable build-time nodes
653
         */
654
        /* default */ int buildTimeSize() {
655 1 1. buildTimeSize : replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::buildTimeSize → KILLED
            return countMutableNodes(this.root);
656
        }
657
658
        /**
659
         * Counts mutable nodes recursively.
660
         *
661
         * @param node current node
662
         * @return reachable mutable node count
663
         */
664
        private int countMutableNodes(final MutableNode<V> node) {
665
            int count = 1;
666
            for (MutableNode<V> child : node.children().values()) {
667 1 1. countMutableNodes : Replaced integer addition with subtraction → KILLED
                count += countMutableNodes(child);
668
            }
669 1 1. countMutableNodes : replaced int return with 0 for org/egothor/stemmer/FrequencyTrie$Builder::countMutableNodes → KILLED
            return count;
670
        }
671
672
        /**
673
         * Reduces a mutable node to a canonical reduced node.
674
         *
675
         * @param source  source mutable node
676
         * @param context reduction context
677
         * @return canonical reduced node
678
         */
679
        private ReducedNode<V> reduce(final MutableNode<V> source, final ReductionContext<V> context) {
680
            final Map<Character, ReducedNode<V>> reducedChildren = new LinkedHashMap<>();
681
682
            for (Map.Entry<Character, MutableNode<V>> childEntry : source.children().entrySet()) {
683
                final ReducedNode<V> reducedChild = reduce(childEntry.getValue(), context);
684
                reducedChildren.put(childEntry.getKey(), reducedChild);
685
            }
686
687
            final Map<V, Integer> localCounts = copyCounts(source.valueCounts());
688
            final LocalValueSummary<V> localSummary = LocalValueSummary.of(localCounts, this.arrayFactory);
689
            final ReductionSignature<V> signature = ReductionSignature.create(localSummary, reducedChildren,
690
                    context.settings());
691
692
            ReducedNode<V> canonical = context.lookup(signature);
693 1 1. reduce : negated conditional → KILLED
            if (canonical == null) {
694
                canonical = new ReducedNode<>(signature, localCounts, reducedChildren);
695 1 1. reduce : removed call to org/egothor/stemmer/trie/ReductionContext::register → KILLED
                context.register(signature, canonical);
696 1 1. reduce : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED
                return canonical;
697
            }
698
699 1 1. reduce : removed call to org/egothor/stemmer/trie/ReducedNode::mergeLocalCounts → KILLED
            canonical.mergeLocalCounts(localCounts);
700 1 1. reduce : removed call to org/egothor/stemmer/trie/ReducedNode::mergeChildren → SURVIVED
            canonical.mergeChildren(reducedChildren);
701
702 1 1. reduce : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::reduce → KILLED
            return canonical;
703
        }
704
705
        /**
706
         * Freezes a reduced node into an immutable compiled node.
707
         *
708
         * @param reducedNode reduced node
709
         * @param cache       already frozen nodes
710
         * @return immutable compiled node
711
         */
712
        private CompiledNode<V> freeze(final ReducedNode<V> reducedNode,
713
                final Map<ReducedNode<V>, CompiledNode<V>> cache) {
714
            final CompiledNode<V> existing = cache.get(reducedNode);
715 1 1. freeze : negated conditional → KILLED
            if (existing != null) {
716 1 1. freeze : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED
                return existing;
717
            }
718
719
            final LocalValueSummary<V> localSummary = LocalValueSummary.of(reducedNode.localCounts(),
720
                    this.arrayFactory);
721
722
            final List<Map.Entry<Character, ReducedNode<V>>> childEntries = new ArrayList<>(
723
                    reducedNode.children().entrySet());
724 1 1. freeze : removed call to java/util/List::sort → KILLED
            childEntries.sort(Map.Entry.comparingByKey());
725
726
            final char[] edges = new char[childEntries.size()];
727
            @SuppressWarnings("unchecked")
728
            final CompiledNode<V>[] childNodes = new CompiledNode[childEntries.size()];
729
730 2 1. freeze : negated conditional → KILLED
2. freeze : changed conditional boundary → KILLED
            for (int index = 0; index < childEntries.size(); index++) {
731
                final Map.Entry<Character, ReducedNode<V>> entry = childEntries.get(index);
732
                edges[index] = entry.getKey();
733
                childNodes[index] = freeze(entry.getValue(), cache);
734
            }
735
736
            final CompiledNode<V> frozen = new CompiledNode<>(edges, childNodes, localSummary.orderedValues(),
737
                    localSummary.orderedCounts());
738
            cache.put(reducedNode, frozen);
739 1 1. freeze : replaced return value with null for org/egothor/stemmer/FrequencyTrie$Builder::freeze → KILLED
            return frozen;
740
        }
741
742
        /**
743
         * Creates a shallow frequency copy preserving deterministic insertion order of
744
         * first occurrence.
745
         *
746
         * @param source source counts
747
         * @return copied counts
748
         */
749
        private Map<V, Integer> copyCounts(final Map<V, Integer> source) {
750 1 1. copyCounts : replaced return value with Collections.emptyMap for org/egothor/stemmer/FrequencyTrie$Builder::copyCounts → KILLED
            return new LinkedHashMap<>(source);
751
        }
752
    }
753
754
    /**
755
     * Codec used to persist values stored in the trie.
756
     *
757
     * @param <V> value type
758
     */
759
    public interface ValueStreamCodec<V> {
760
761
        /**
762
         * Writes one value to the supplied data output.
763
         *
764
         * @param dataOutput target data output
765
         * @param value      value to write
766
         * @throws IOException if writing fails
767
         */
768
        void write(DataOutputStream dataOutput, V value) throws IOException;
769
770
        /**
771
         * Reads one value from the supplied data input.
772
         *
773
         * @param dataInput source data input
774
         * @return read value
775
         * @throws IOException if reading fails
776
         */
777
        V read(DataInputStream dataInput) throws IOException;
778
    }
779
780
}

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

430

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

437

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

440

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

452

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

460

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

464

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

466

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 : none
changed conditional boundary → SURVIVED
Covering tests

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

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

474

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

485

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

487

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

491

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

567

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

590

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

620

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

625

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

628

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

636

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

639

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

641

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

655

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

667

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

669

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

693

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

695

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

696

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

699

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

700

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

702

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

715

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

716

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

724

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

730

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

739

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

750

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