StemmerPatchTrieBinaryIO.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.BufferedInputStream;
34
import java.io.BufferedOutputStream;
35
import java.io.DataInputStream;
36
import java.io.DataOutputStream;
37
import java.io.IOException;
38
import java.io.InputStream;
39
import java.io.OutputStream;
40
import java.nio.file.Files;
41
import java.nio.file.Path;
42
import java.util.HashMap;
43
import java.util.Map;
44
import java.util.Objects;
45
import java.util.function.BiFunction;
46
import java.util.logging.Level;
47
import java.util.logging.Logger;
48
import java.util.zip.GZIPInputStream;
49
import java.util.zip.GZIPOutputStream;
50
51
/**
52
 * Binary persistence helper for patch-command stemmer tries.
53
 *
54
 * <p>
55
 * This class persists {@link FrequencyTrie} instances whose values are compact
56
 * patch commands represented as {@link String}. The serialized trie payload is
57
 * the native binary format of {@link FrequencyTrie}, wrapped in GZip
58
 * compression.
59
 * Binary reads can either preserve those serialized strings or materialize
60
 * {@link CompiledPatchCommand} values directly in the final trie nodes.
61
 *
62
 * <p>
63
 * The helper centralizes the codec and compression details so that higher-level
64
 * loader APIs can remain focused on source selection rather than stream
65
 * mechanics.
66
 */
67
public final class StemmerPatchTrieBinaryIO {
68
69
    /**
70
     * Logger of this class.
71
     */
72
    private static final Logger LOGGER = Logger.getLogger(StemmerPatchTrieBinaryIO.class.getName());
73
74
    /**
75
     * Value codec for persisted patch-command strings.
76
     */
77
    private static final FrequencyTrie.ValueStreamCodec<String> STRING_CODEC = new StringValueStreamCodec();
78
79
    /**
80
     * Maximum serialized patch-command length included in validation diagnostics.
81
     */
82
    private static final int MAX_DIAGNOSTIC_PATCH_LENGTH = 128;
83
84
    /**
85
     * Null-check parameter name for filesystem paths.
86
     */
87
    private static final String PATH_PARAMETER = "path";
88
89
    /**
90
     * Null-check parameter name for filesystem path strings.
91
     */
92
    private static final String FILE_NAME_PARAMETER = "fileName";
93
94
    /**
95
     * Utility class.
96
     */
97
    private StemmerPatchTrieBinaryIO() {
98
        throw new AssertionError("No instances.");
99
    }
100
101
    /**
102
     * Reads a GZip-compressed binary patch-command trie from a filesystem path.
103
     *
104
     * @param path source file
105
     * @return deserialized trie
106
     * @throws NullPointerException if {@code path} is {@code null}
107
     * @throws IOException          if reading or decompression fails
108
     */
109
    public static FrequencyTrie<String> read(final Path path) throws IOException {
110
        Objects.requireNonNull(path, PATH_PARAMETER);
111
112
        try (InputStream fileInputStream = Files.newInputStream(path)) {
113 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
            return read(fileInputStream);
114
        }
115
    }
116
117
    /**
118
     * Reads a GZip-compressed binary patch-command trie from a filesystem path with
119
     * an optional dense child lookup span override.
120
     * <p>
121
     * This is a runtime-only tuning parameter. The dense-span setting is not
122
     * persisted in the file and does not change the compiled metadata.
123
     * </p>
124
     *
125
     * @param path             source file
126
     * @param maxExpandedIndex dense lookup span override; negative values use
127
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
128
     * @return deserialized trie
129
     * @throws NullPointerException if {@code path} is {@code null}
130
     * @throws IOException          if reading or decompression fails
131
     */
132
    public static FrequencyTrie<String> read(final Path path, final int maxExpandedIndex) throws IOException {
133
        Objects.requireNonNull(path, PATH_PARAMETER);
134
135
        try (InputStream fileInputStream = Files.newInputStream(path)) {
136 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
            return read(fileInputStream, maxExpandedIndex);
137
        }
138
    }
139
140
    /**
141
     * Reads a GZip-compressed binary patch-command trie from a filesystem path
142
     * string.
143
     *
144
     * @param fileName source file name or path string
145
     * @return deserialized trie
146
     * @throws NullPointerException if {@code fileName} is {@code null}
147
     * @throws IOException          if reading or decompression fails
148
     */
149
    public static FrequencyTrie<String> read(final String fileName) throws IOException {
150
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
151 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
        return read(Path.of(fileName));
152
    }
153
154
    /**
155
     * Reads a GZip-compressed binary patch-command trie from a filesystem path
156
     * string with an optional dense child lookup span override.
157
     * <p>
158
     * This is a runtime-only tuning parameter. The dense-span setting is not
159
     * persisted in the file and does not change the compiled metadata.
160
     * </p>
161
     *
162
     * @param fileName         source file name or path string
163
     * @param maxExpandedIndex dense lookup span override; negative values use
164
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
165
     * @return deserialized trie
166
     * @throws NullPointerException if {@code fileName} is {@code null}
167
     * @throws IOException          if reading or decompression fails
168
     */
169
    public static FrequencyTrie<String> read(final String fileName, final int maxExpandedIndex) throws IOException {
170
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
171 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
        return read(Path.of(fileName), maxExpandedIndex);
172
    }
173
174
    /**
175
     * Reads a GZip-compressed binary patch-command trie from an input stream.
176
     *
177
     * <p>
178
     * The supplied stream is consumed but not interpreted as plain trie bytes; it
179
     * is first decompressed using {@link GZIPInputStream}.
180
     *
181
     * @param inputStream source stream
182
     * @return deserialized trie
183
     * @throws NullPointerException if {@code inputStream} is {@code null}
184
     * @throws IOException          if reading or decompression fails
185
     */
186
    public static FrequencyTrie<String> read(final InputStream inputStream) throws IOException {
187
        Objects.requireNonNull(inputStream, "inputStream");
188
189
        try (GZIPInputStream gzipInputStream = new GZIPInputStream(new BufferedInputStream(inputStream));
190
                DataInputStream dataInputStream = new DataInputStream(gzipInputStream)) {
191 1 1. lambda$read$0 : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$read$0 → KILLED
            final FrequencyTrie<String> trie = FrequencyTrie.readFrom(dataInputStream, String[]::new, STRING_CODEC);
192
193
            LOGGER.log(Level.FINE, "Read compressed binary stemmer trie.");
194 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
            return trie;
195
        }
196
    }
197
198
    /**
199
     * Reads a GZip-compressed binary patch-command trie from an input stream with
200
     * an optional dense child lookup span override.
201
     * <p>
202
     * This is a runtime-only tuning parameter. The dense-span setting is not
203
     * persisted in the file and does not change the compiled metadata.
204
     * </p>
205
     *
206
     * @param inputStream      source stream
207
     * @param maxExpandedIndex dense lookup span override; negative values use
208
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
209
     * @return deserialized trie
210
     * @throws NullPointerException if {@code inputStream} is {@code null}
211
     * @throws IOException          if reading or decompression fails
212
     */
213
    public static FrequencyTrie<String> read(final InputStream inputStream, final int maxExpandedIndex)
214
            throws IOException {
215
        Objects.requireNonNull(inputStream, "inputStream");
216
217
        try (GZIPInputStream gzipInputStream = new GZIPInputStream(new BufferedInputStream(inputStream));
218
                DataInputStream dataInputStream = new DataInputStream(gzipInputStream)) {
219 1 1. lambda$read$1 : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$read$1 → NO_COVERAGE
            final FrequencyTrie<String> trie = FrequencyTrie.readFrom(dataInputStream, String[]::new, STRING_CODEC,
220
                    maxExpandedIndex);
221
222
            LOGGER.log(Level.FINE, "Read compressed binary stemmer trie.");
223 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED
            return trie;
224
        }
225
    }
226
227
    /**
228
     * Reads a compressed binary patch-command trie directly as compiled values from
229
     * a filesystem path.
230
     *
231
     * @param path source file
232
     * @return directly materialized compiled patch-command trie
233
     * @throws NullPointerException if {@code path} is {@code null}
234
     * @throws IOException          if reading, decompression, or command compilation
235
     *                              fails
236
     */
237
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final Path path) throws IOException {
238
        Objects.requireNonNull(path, PATH_PARAMETER);
239
240
        try (InputStream fileInputStream = Files.newInputStream(path)) {
241 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE
            return readCompiled(fileInputStream);
242
        }
243
    }
244
245
    /**
246
     * Reads a compressed binary patch-command trie directly as compiled values from
247
     * a filesystem path with a dense child lookup span override.
248
     *
249
     * @param path             source file
250
     * @param maxExpandedIndex dense lookup span override; negative values use
251
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
252
     * @return directly materialized compiled patch-command trie
253
     * @throws NullPointerException if {@code path} is {@code null}
254
     * @throws IOException          if reading, decompression, or command compilation
255
     *                              fails
256
     */
257
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final Path path,
258
            final int maxExpandedIndex)
259
            throws IOException {
260
        Objects.requireNonNull(path, PATH_PARAMETER);
261
262
        try (InputStream fileInputStream = Files.newInputStream(path)) {
263 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE
            return readCompiled(fileInputStream, maxExpandedIndex);
264
        }
265
    }
266
267
    /**
268
     * Reads a compressed binary patch-command trie directly as compiled values from
269
     * a filesystem path string.
270
     *
271
     * @param fileName source file name or path string
272
     * @return directly materialized compiled patch-command trie
273
     * @throws NullPointerException if {@code fileName} is {@code null}
274
     * @throws IOException          if reading, decompression, or command compilation
275
     *                              fails
276
     */
277
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final String fileName) throws IOException {
278
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
279 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE
        return readCompiled(Path.of(fileName));
280
    }
281
282
    /**
283
     * Reads a compressed binary patch-command trie directly as compiled values from
284
     * a filesystem path string with a dense child lookup span override.
285
     *
286
     * @param fileName         source file name or path string
287
     * @param maxExpandedIndex dense lookup span override; negative values use
288
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
289
     * @return directly materialized compiled patch-command trie
290
     * @throws NullPointerException if {@code fileName} is {@code null}
291
     * @throws IOException          if reading, decompression, or command compilation
292
     *                              fails
293
     */
294
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final String fileName,
295
            final int maxExpandedIndex)
296
            throws IOException {
297
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
298 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE
        return readCompiled(Path.of(fileName), maxExpandedIndex);
299
    }
300
301
    /**
302
     * Reads a compressed binary patch-command trie directly as compiled values from
303
     * an input stream.
304
     *
305
     * @param inputStream source stream
306
     * @return directly materialized compiled patch-command trie
307
     * @throws NullPointerException if {@code inputStream} is {@code null}
308
     * @throws IOException          if reading, decompression, or command compilation
309
     *                              fails
310
     */
311
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final InputStream inputStream)
312
            throws IOException {
313 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED
        return readCompiled(inputStream, -1);
314
    }
315
316
    /**
317
     * Reads a compressed binary patch-command trie directly as compiled values from
318
     * an input stream with a dense child lookup span override.
319
     *
320
     * @param inputStream      source stream
321
     * @param maxExpandedIndex dense lookup span override; negative values use
322
     *                         {@link FrequencyTrie#DEFAULT_MAX_EXPANDED_INDEX}
323
     * @return directly materialized compiled patch-command trie
324
     * @throws NullPointerException if {@code inputStream} is {@code null}
325
     * @throws IOException          if reading, decompression, or command compilation
326
     *                              fails
327
     */
328
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final InputStream inputStream,
329
            final int maxExpandedIndex) throws IOException {
330 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED
        return readCompiled(inputStream, maxExpandedIndex, CompiledPatchCommand::compile);
331
    }
332
333
    /**
334
     * Reads a compressed binary patch-command trie using a caller-supplied command
335
     * compiler.
336
     *
337
     * <p>
338
     * This package-private seam permits deterministic compilation-count testing
339
     * without global counters. Production callers use
340
     * {@link CompiledPatchCommand#compile(String, WordTraversalDirection)}.
341
     * </p>
342
     *
343
     * @param inputStream      source stream
344
     * @param maxExpandedIndex dense lookup span override
345
     * @param commandCompiler  compiler for one serialized command and traversal
346
     *                         direction
347
     * @return directly materialized compiled patch-command trie
348
     * @throws NullPointerException if any argument is {@code null}
349
     * @throws IOException          if reading, decompression, or command compilation
350
     *                              fails
351
     */
352
    /* default */ static FrequencyTrie<CompiledPatchCommand> readCompiled(final InputStream inputStream,
353
            final int maxExpandedIndex,
354
            final BiFunction<String, WordTraversalDirection, CompiledPatchCommand> commandCompiler)
355
            throws IOException {
356
        Objects.requireNonNull(inputStream, "inputStream");
357
        Objects.requireNonNull(commandCompiler, "commandCompiler");
358
        final CompiledPatchValueReader valueReader = new CompiledPatchValueReader(commandCompiler);
359
360
        try (GZIPInputStream gzipInputStream = new GZIPInputStream(new BufferedInputStream(inputStream));
361
                DataInputStream dataInputStream = new DataInputStream(gzipInputStream)) {
362
            final FrequencyTrie<CompiledPatchCommand> trie = FrequencyTrie.readFromWithMetadata(dataInputStream,
363 1 1. lambda$readCompiled$2 : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$readCompiled$2 → KILLED
                    CompiledPatchCommand[]::new, valueReader, maxExpandedIndex);
364
365
            LOGGER.log(Level.FINE, "Read compressed binary stemmer trie directly as compiled patch commands.");
366 1 1. readCompiled : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED
            return trie;
367
        }
368
    }
369
370
    /**
371
     * Reads only metadata from a GZip-compressed binary patch-command trie stored
372
     * at a filesystem path.
373
     *
374
     * @param path source file
375
     * @return deserialized trie metadata
376
     * @throws NullPointerException if {@code path} is {@code null}
377
     * @throws IOException          if reading or decompression fails
378
     */
379
    public static TrieMetadata readMetadata(final Path path) throws IOException {
380
        Objects.requireNonNull(path, PATH_PARAMETER);
381 1 1. readMetadata : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED
        return read(path).metadata();
382
    }
383
384
    /**
385
     * Reads only metadata from a GZip-compressed binary patch-command trie stored
386
     * at a filesystem path string.
387
     *
388
     * @param fileName source file name or path string
389
     * @return deserialized trie metadata
390
     * @throws NullPointerException if {@code fileName} is {@code null}
391
     * @throws IOException          if reading or decompression fails
392
     */
393
    public static TrieMetadata readMetadata(final String fileName) throws IOException {
394
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
395 1 1. readMetadata : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED
        return readMetadata(Path.of(fileName));
396
    }
397
398
    /**
399
     * Reads only metadata from a GZip-compressed binary patch-command trie from an
400
     * input stream.
401
     *
402
     * @param inputStream source stream
403
     * @return deserialized trie metadata
404
     * @throws NullPointerException if {@code inputStream} is {@code null}
405
     * @throws IOException          if reading or decompression fails
406
     */
407
    public static TrieMetadata readMetadata(final InputStream inputStream) throws IOException {
408
        Objects.requireNonNull(inputStream, "inputStream");
409 1 1. readMetadata : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED
        return read(inputStream).metadata();
410
    }
411
412
    /**
413
     * Writes a GZip-compressed binary patch-command trie to a filesystem path.
414
     *
415
     * @param trie trie to persist
416
     * @param path target file
417
     * @throws NullPointerException if any argument is {@code null}
418
     * @throws IOException          if writing fails
419
     */
420
    public static void write(final FrequencyTrie<String> trie, final Path path) throws IOException {
421
        Objects.requireNonNull(trie, "trie");
422
        Objects.requireNonNull(path, PATH_PARAMETER);
423
424
        final Path parent = path.toAbsolutePath().getParent();
425 1 1. write : negated conditional → KILLED
        if (parent != null) {
426
            Files.createDirectories(parent);
427
        }
428
429
        try (OutputStream fileOutputStream = Files.newOutputStream(path)) {
430 1 1. write : removed call to org/egothor/stemmer/StemmerPatchTrieBinaryIO::write → KILLED
            write(trie, fileOutputStream);
431
        }
432
    }
433
434
    /**
435
     * Writes a GZip-compressed binary patch-command trie to a filesystem path
436
     * string.
437
     *
438
     * @param trie     trie to persist
439
     * @param fileName target file name or path string
440
     * @throws NullPointerException if any argument is {@code null}
441
     * @throws IOException          if writing fails
442
     */
443
    public static void write(final FrequencyTrie<String> trie, final String fileName) throws IOException {
444
        Objects.requireNonNull(fileName, FILE_NAME_PARAMETER);
445 1 1. write : removed call to org/egothor/stemmer/StemmerPatchTrieBinaryIO::write → KILLED
        write(trie, Path.of(fileName));
446
    }
447
448
    /**
449
     * Writes a GZip-compressed binary patch-command trie to an output stream.
450
     *
451
     * @param trie         trie to persist
452
     * @param outputStream target stream
453
     * @throws NullPointerException if any argument is {@code null}
454
     * @throws IOException          if writing fails
455
     */
456
    public static void write(final FrequencyTrie<String> trie, final OutputStream outputStream) throws IOException {
457
        Objects.requireNonNull(trie, "trie");
458
        Objects.requireNonNull(outputStream, "outputStream");
459
460
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new BufferedOutputStream(outputStream));
461
                DataOutputStream dataOutputStream = new DataOutputStream(gzipOutputStream)) {
462 1 1. write : removed call to org/egothor/stemmer/FrequencyTrie::writeTo → KILLED
            trie.writeTo(dataOutputStream, STRING_CODEC);
463
        }
464
465
        LOGGER.log(Level.FINE, "Wrote compressed binary stemmer trie.");
466
    }
467
468
    /**
469
     * Binary stream codec for persisted patch-command strings.
470
     */
471
    private static final class StringValueStreamCodec implements FrequencyTrie.ValueStreamCodec<String> {
472
473
        /**
474
         * Creates a codec instance.
475
         */
476
        private StringValueStreamCodec() {
477
        }
478
479
        @Override
480
        public void write(final DataOutputStream dataOutput, final String value) throws IOException {
481 1 1. write : removed call to java/io/DataOutputStream::writeUTF → KILLED
            dataOutput.writeUTF(value);
482
        }
483
484
        @Override
485
        public String read(final DataInputStream dataInput) throws IOException {
486 1 1. read : replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO$StringValueStreamCodec::read → KILLED
            return dataInput.readUTF();
487
        }
488
    }
489
490
    /**
491
     * Metadata-aware reader that compiles serialized patch commands into final trie
492
     * values.
493
     *
494
     * <p>
495
     * Version 7 value tables already contain distinct serialized values, so each
496
     * entry is compiled directly. Historical inline formats use the reader-local
497
     * equality cache to compile repeated serialized commands once. Neither the cache
498
     * nor this reader is retained after trie loading.
499
     * </p>
500
     */
501
    private static final class CompiledPatchValueReader
502
            implements FrequencyTrie.MetadataValueStreamReader<CompiledPatchCommand> {
503
504
        /**
505
         * Compiler used to materialize one final command.
506
         */
507
        private final BiFunction<String, WordTraversalDirection, CompiledPatchCommand> commandCompiler;
508
509
        /**
510
         * Compatibility cache used only by historical inline-value streams.
511
         */
512
        private final Map<String, CompiledPatchCommand> legacyCompiledCommands = new HashMap<>();
513
514
        /**
515
         * Creates one reader with a caller-supplied compiler.
516
         *
517
         * @param commandCompiler compiler for serialized patch commands
518
         */
519
        private CompiledPatchValueReader(
520
                final BiFunction<String, WordTraversalDirection, CompiledPatchCommand> commandCompiler) {
521
            this.commandCompiler = commandCompiler;
522
        }
523
524
        /**
525
         * Reads and compiles one serialized patch command.
526
         *
527
         * @param dataInput source data input
528
         * @param metadata  parsed trie metadata
529
         * @return final compiled patch command
530
         * @throws IOException if reading or command compilation fails
531
         */
532
        @Override
533
        public CompiledPatchCommand read(final DataInputStream dataInput, final TrieMetadata metadata)
534
                throws IOException {
535
            final String serializedPatch = dataInput.readUTF();
536 1 1. read : negated conditional → KILLED
            if (FrequencyTrie.usesValueTableFormat(metadata)) {
537 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED
                return compile(serializedPatch, metadata.traversalDirection());
538
            }
539
540
            final CompiledPatchCommand cachedCommand = this.legacyCompiledCommands.get(serializedPatch);
541 1 1. read : negated conditional → KILLED
            if (cachedCommand != null) {
542 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED
                return cachedCommand;
543
            }
544
            final CompiledPatchCommand compiledCommand = compile(serializedPatch, metadata.traversalDirection());
545
            this.legacyCompiledCommands.put(serializedPatch, compiledCommand);
546 1 1. read : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED
            return compiledCommand;
547
        }
548
549
        /**
550
         * Compiles one serialized command and converts validation failures to
551
         * trust-boundary {@link IOException} instances.
552
         *
553
         * @param serializedPatch   serialized patch command
554
         * @param traversalDirection traversal direction from persisted metadata
555
         * @return compiled patch command
556
         * @throws IOException if the serialized command is invalid
557
         */
558
        private CompiledPatchCommand compile(final String serializedPatch,
559
                final WordTraversalDirection traversalDirection) throws IOException {
560
            try {
561 1 1. compile : replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::compile → KILLED
                return this.commandCompiler.apply(serializedPatch, traversalDirection);
562
            } catch (IllegalArgumentException exception) {
563
                throw new IOException("Invalid persisted patch command '" + boundedPatch(serializedPatch)
564
                        + "' for traversal direction " + traversalDirection + '.', exception);
565
            }
566
        }
567
    }
568
569
    /**
570
     * Returns a safely bounded patch-command representation for diagnostics.
571
     *
572
     * @param serializedPatch serialized patch command
573
     * @return complete or length-bounded diagnostic representation
574
     */
575
    private static String boundedPatch(final String serializedPatch) {
576 2 1. boundedPatch : negated conditional → KILLED
2. boundedPatch : changed conditional boundary → KILLED
        if (serializedPatch.length() <= MAX_DIAGNOSTIC_PATCH_LENGTH) {
577 1 1. boundedPatch : replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO::boundedPatch → KILLED
            return serializedPatch;
578
        }
579 1 1. boundedPatch : replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO::boundedPatch → KILLED
        return serializedPatch.substring(0, MAX_DIAGNOSTIC_PATCH_LENGTH) + "... (length "
580
                + serializedPatch.length() + ')';
581
    }
582
}

Mutations

113

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadGzipPayloadFromFileNameString()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

136

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldDelegatePathReadWithDenseSpanOverride()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

151

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadGzipPayloadFromFileNameString()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

171

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldDelegateStringReadWithDenseSpanOverride()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

191

1.1
Location : lambda$read$0
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadMetadataFromGzipPayload()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$read$0 → KILLED

194

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadGzipPayloadFromPath()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

219

1.1
Location : lambda$read$1
Killed by : none
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$read$1 → NO_COVERAGE

223

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldDelegateInputStreamReadWithDenseSpanOverride()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::read → KILLED

241

1.1
Location : readCompiled
Killed by : none
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE

263

1.1
Location : readCompiled
Killed by : none
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE

279

1.1
Location : readCompiled
Killed by : none
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE

298

1.1
Location : readCompiled
Killed by : none
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → NO_COVERAGE

313

1.1
Location : readCompiled
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldMaterializeVersionSevenDirectlyAsSharedCompiledCommands()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED

330

1.1
Location : readCompiled
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldMaterializeVersionSevenDirectlyAsSharedCompiledCommands()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED

363

1.1
Location : lambda$readCompiled$2
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldCompileRepeatedVersionSixInlineCommandsOnce()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::lambda$readCompiled$2 → KILLED

366

1.1
Location : readCompiled
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldCompileRepeatedVersionSixInlineCommandsOnce()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readCompiled → KILLED

381

1.1
Location : readMetadata
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadMetadataFromPath()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED

395

1.1
Location : readMetadata
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadMetadataFromStringPath()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED

409

1.1
Location : readMetadata
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldReadMetadataFromGzipPayload()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO::readMetadata → KILLED

425

1.1
Location : write
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:WriteTests]/[method:shouldCreateParentDirectoriesAndWriteGzipFile()]
negated conditional → KILLED

430

1.1
Location : write
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:WriteTests]/[method:shouldWriteToFilesystemWhenFileNameStringIsUsed()]
removed call to org/egothor/stemmer/StemmerPatchTrieBinaryIO::write → KILLED

445

1.1
Location : write
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:WriteTests]/[method:shouldWriteToFilesystemWhenFileNameStringIsUsed()]
removed call to org/egothor/stemmer/StemmerPatchTrieBinaryIO::write → KILLED

462

1.1
Location : write
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:WriteTests]/[method:shouldPropagateWriteFailureFromTrieSerialization()]
removed call to org/egothor/stemmer/FrequencyTrie::writeTo → KILLED

481

1.1
Location : write
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldRetainInvalidCommandAtDiagnosticBoundary()]
removed call to java/io/DataOutputStream::writeUTF → KILLED

486

1.1
Location : read
Killed by : org.egothor.stemmer.CompiledTrieArtifactRegressionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.CompiledTrieArtifactRegressionTest]/[test-template:shouldKeepGoldenArtifactReadableAndHashStable(org.egothor.stemmer.CompiledTrieArtifactRegressionTest$ArtifactCase)]/[test-template-invocation:#2]
replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO$StringValueStreamCodec::read → KILLED

536

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldCompileRepeatedVersionSixInlineCommandsOnce()]
negated conditional → KILLED

537

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldCompileEachVersionSevenTableEntryOnce()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED

541

1.1
Location : read
Killed by : org.egothor.stemmer.CompiledTrieArtifactRegressionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.CompiledTrieArtifactRegressionTest]/[test-template:shouldLoadGoldenArtifactsDirectlyAsCompiledCommands(org.egothor.stemmer.CompiledTrieArtifactRegressionTest$ArtifactCase)]/[test-template-invocation:#3]
negated conditional → KILLED

542

1.1
Location : read
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldCompileRepeatedVersionSixInlineCommandsOnce()]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED

546

1.1
Location : read
Killed by : org.egothor.stemmer.CompiledTrieArtifactRegressionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.CompiledTrieArtifactRegressionTest]/[test-template:shouldLoadGoldenArtifactsDirectlyAsCompiledCommands(org.egothor.stemmer.CompiledTrieArtifactRegressionTest$ArtifactCase)]/[test-template-invocation:#3]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::read → KILLED

561

1.1
Location : compile
Killed by : org.egothor.stemmer.CompiledTrieArtifactRegressionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.CompiledTrieArtifactRegressionTest]/[test-template:shouldLoadGoldenArtifactsDirectlyAsCompiledCommands(org.egothor.stemmer.CompiledTrieArtifactRegressionTest$ArtifactCase)]/[test-template-invocation:#3]
replaced return value with null for org/egothor/stemmer/StemmerPatchTrieBinaryIO$CompiledPatchValueReader::compile → KILLED

576

1.1
Location : boundedPatch
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldTruncateInvalidCommandAboveDiagnosticBoundary()]
negated conditional → KILLED

2.2
Location : boundedPatch
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldRetainInvalidCommandAtDiagnosticBoundary()]
changed conditional boundary → KILLED

577

1.1
Location : boundedPatch
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldRetainInvalidCommandAtDiagnosticBoundary()]
replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO::boundedPatch → KILLED

579

1.1
Location : boundedPatch
Killed by : org.egothor.stemmer.StemmerPatchTrieBinaryIOTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerPatchTrieBinaryIOTest]/[nested-class:ReadTests]/[method:shouldTruncateInvalidCommandAboveDiagnosticBoundary()]
replaced return value with "" for org/egothor/stemmer/StemmerPatchTrieBinaryIO::boundedPatch → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1