FrequencyTrieBuilders.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.util.IdentityHashMap;
34
import java.util.Map;
35
import java.util.Objects;
36
import java.util.function.Function;
37
import java.util.function.IntFunction;
38
import java.util.logging.Level;
39
import java.util.logging.Logger;
40
41
import org.egothor.stemmer.trie.CompiledNode;
42
43
/**
44
 * Factory utilities related to {@link FrequencyTrie.Builder}.
45
 *
46
 * <p>
47
 * This helper reconstructs writable builders from compiled read-only tries. The
48
 * reconstruction preserves the semantics and local counts of the compiled trie
49
 * as currently stored, which makes it suitable for subsequent modifications
50
 * followed by recompilation.
51
 *
52
 * <p>
53
 * Reconstruction operates on the compiled form. Therefore, if the compiled trie
54
 * was produced using a reduction mode that merged semantically equivalent
55
 * subtrees, the recreated builder reflects that reduced compiled state rather
56
 * than the exact original unreduced insertion history.
57
 */
58
public final class FrequencyTrieBuilders {
59
60
    /**
61
     * Logger of this class.
62
     */
63
    private static final Logger LOGGER = Logger.getLogger(FrequencyTrieBuilders.class.getName());
64
65
    /**
66
     * Utility class.
67
     */
68
    private FrequencyTrieBuilders() {
69
        throw new AssertionError("No instances.");
70
    }
71
72
    /**
73
     * Reconstructs a new writable builder from a compiled read-only trie.
74
     *
75
     * <p>
76
     * The returned builder contains the same key-local value counts as the supplied
77
     * compiled trie. Callers may continue modifying the returned builder and then
78
     * compile a new {@link FrequencyTrie} instance.
79
     *
80
     * @param source            source compiled trie
81
     * @param arrayFactory      array factory for the reconstructed builder
82
     * @param reductionSettings reduction settings to associate with the new builder
83
     * @param <V>               value type
84
     * @return reconstructed writable builder
85
     * @throws NullPointerException if any argument is {@code null}
86
     */
87
    public static <V> FrequencyTrie.Builder<V> copyOf(final FrequencyTrie<V> source,
88
            final IntFunction<V[]> arrayFactory, final ReductionSettings reductionSettings) {
89
        Objects.requireNonNull(source, "source");
90
        Objects.requireNonNull(arrayFactory, "arrayFactory");
91
        Objects.requireNonNull(reductionSettings, "reductionSettings");
92
93
        final FrequencyTrie.Builder<V> builder = new FrequencyTrie.Builder<>(arrayFactory, reductionSettings,
94
                source.traversalDirection(), source.metadata().caseProcessingMode(),
95
                source.metadata().diacriticProcessingMode());
96
        final StringBuilder keyBuilder = new StringBuilder(64);
97
98 1 1. copyOf : removed call to org/egothor/stemmer/FrequencyTrieBuilders::copyNode → KILLED
        copyNode(source.root(), keyBuilder, builder, source.traversalDirection());
99
100
        LOGGER.log(Level.FINE, "Reconstructed writable builder from compiled trie.");
101 1 1. copyOf : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::copyOf → KILLED
        return builder;
102
    }
103
104
    /**
105
     * Reconstructs a new writable builder from a compiled read-only trie using
106
     * default settings for the supplied reduction mode.
107
     *
108
     * @param source        source compiled trie
109
     * @param arrayFactory  array factory for the reconstructed builder
110
     * @param reductionMode reduction mode to associate with the new builder
111
     * @param <V>           value type
112
     * @return reconstructed writable builder
113
     * @throws NullPointerException if any argument is {@code null}
114
     */
115
    public static <V> FrequencyTrie.Builder<V> copyOf(final FrequencyTrie<V> source,
116
            final IntFunction<V[]> arrayFactory, final ReductionMode reductionMode) {
117
        Objects.requireNonNull(reductionMode, "reductionMode");
118 1 1. copyOf : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::copyOf → KILLED
        return copyOf(source, arrayFactory, ReductionSettings.withDefaults(reductionMode));
119
    }
120
121
    /**
122
     * Reconstructs a compiled trie with every stored value transformed to another
123
     * value type.
124
     *
125
     * <p>
126
     * The method preserves logical keys, local value counts, trie metadata, and the
127
     * supplied reduction settings. It is intended for runtime specialization, such
128
     * as replacing serialized patch-command strings with precompiled patch command
129
     * objects without changing the persisted binary trie format.
130
     * </p>
131
     *
132
     * @param source            source compiled trie
133
     * @param arrayFactory      array factory for mapped values
134
     * @param reductionSettings reduction settings for the mapped trie
135
     * @param valueMapper       value mapping function
136
     * @param <S>               source value type
137
     * @param <T>               target value type
138
     * @return compiled trie containing mapped values
139
     * @throws NullPointerException if any argument is {@code null}
140
     */
141
    public static <S, T> FrequencyTrie<T> mapValues(final FrequencyTrie<S> source,
142
            final IntFunction<T[]> arrayFactory, final ReductionSettings reductionSettings,
143
            final Function<? super S, ? extends T> valueMapper) {
144
        Objects.requireNonNull(source, "source");
145
        Objects.requireNonNull(arrayFactory, "arrayFactory");
146
        Objects.requireNonNull(reductionSettings, "reductionSettings");
147
        Objects.requireNonNull(valueMapper, "valueMapper");
148
149
        final Map<CompiledNode<S>, CompiledNode<T>> cache = new IdentityHashMap<>();
150
        final CompiledNode<T> mappedRoot = mapCompiledNode(source.root(), arrayFactory, valueMapper, cache);
151
        final TrieMetadata metadata = TrieMetadata.forCompilation(source.traversalDirection(), reductionSettings,
152
                source.metadata().diacriticProcessingMode(), source.metadata().caseProcessingMode());
153
154
        LOGGER.log(Level.FINE, "Mapped compiled trie values to a specialized value type.");
155 1 1. mapValues : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::mapValues → KILLED
        return FrequencyTrie.fromCompiled(arrayFactory, mappedRoot, metadata);
156
    }
157
158
    /**
159
     * Reconstructs a compiled trie with every stored value transformed to another
160
     * value type using default settings for the supplied reduction mode.
161
     *
162
     * @param source        source compiled trie
163
     * @param arrayFactory  array factory for mapped values
164
     * @param reductionMode reduction mode for the mapped trie
165
     * @param valueMapper   value mapping function
166
     * @param <S>           source value type
167
     * @param <T>           target value type
168
     * @return compiled trie containing mapped values
169
     * @throws NullPointerException if any argument is {@code null}
170
     */
171
    public static <S, T> FrequencyTrie<T> mapValues(final FrequencyTrie<S> source,
172
            final IntFunction<T[]> arrayFactory, final ReductionMode reductionMode,
173
            final Function<? super S, ? extends T> valueMapper) {
174
        Objects.requireNonNull(reductionMode, "reductionMode");
175 1 1. mapValues : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::mapValues → NO_COVERAGE
        return mapValues(source, arrayFactory, ReductionSettings.withDefaults(reductionMode), valueMapper);
176
    }
177
178
    /**
179
     * Copies one compiled node and all reachable descendants into the target
180
     * builder.
181
     *
182
     * @param node               current compiled node
183
     * @param keyBuilder         current key builder
184
     * @param builder            target mutable builder
185
     * @param traversalDirection logical key traversal direction used by the source
186
     * @param <V>                value type
187
     */
188
    private static <V> void copyNode(final CompiledNode<V> node, final StringBuilder keyBuilder,
189
            final FrequencyTrie.Builder<V> builder, final WordTraversalDirection traversalDirection) {
190
        final String logicalKey = traversalDirection.traversalPathToLogicalKey(keyBuilder);
191 2 1. copyNode : changed conditional boundary → KILLED
2. copyNode : negated conditional → KILLED
        for (int valueIndex = 0; valueIndex < node.orderedValues().length; valueIndex++) {
192
            builder.put(logicalKey, node.orderedValues()[valueIndex], node.orderedCounts()[valueIndex]);
193
        }
194
195 2 1. copyNode : changed conditional boundary → KILLED
2. copyNode : negated conditional → KILLED
        for (int childIndex = 0; childIndex < node.edgeLabels().length; childIndex++) {
196
            keyBuilder.append(node.edgeLabels()[childIndex]);
197 1 1. copyNode : removed call to org/egothor/stemmer/FrequencyTrieBuilders::copyNode → KILLED
            copyNode(node.children()[childIndex], keyBuilder, builder, traversalDirection);
198 2 1. copyNode : removed call to java/lang/StringBuilder::setLength → KILLED
2. copyNode : Replaced integer subtraction with addition → KILLED
            keyBuilder.setLength(keyBuilder.length() - 1);
199
        }
200
    }
201
202
    /**
203
     * Maps one compiled node graph while preserving canonical sharing and accepting
204
     * leaf semantics.
205
     *
206
     * @param node         source node
207
     * @param arrayFactory target value array factory
208
     * @param valueMapper  value mapper
209
     * @param cache        identity cache for shared compiled nodes
210
     * @param <S>          source value type
211
     * @param <T>          target value type
212
     * @return mapped compiled node
213
     */
214
    private static <S, T> CompiledNode<T> mapCompiledNode(final CompiledNode<S> node,
215
            final IntFunction<T[]> arrayFactory, final Function<? super S, ? extends T> valueMapper,
216
            final Map<CompiledNode<S>, CompiledNode<T>> cache) {
217
        final CompiledNode<T> existing = cache.get(node);
218 1 1. mapCompiledNode : negated conditional → KILLED
        if (existing != null) {
219 1 1. mapCompiledNode : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::mapCompiledNode → KILLED
            return existing;
220
        }
221
222
        final CompiledNode<S>[] sourceChildren = node.children();
223
        @SuppressWarnings("unchecked")
224
        final CompiledNode<T>[] mappedChildren = new CompiledNode[sourceChildren.length];
225 2 1. mapCompiledNode : changed conditional boundary → KILLED
2. mapCompiledNode : negated conditional → KILLED
        for (int childIndex = 0; childIndex < sourceChildren.length; childIndex++) {
226
            mappedChildren[childIndex] = mapCompiledNode(sourceChildren[childIndex], arrayFactory, valueMapper, cache);
227
        }
228
229
        final S[] sourceValues = node.orderedValues();
230
        final T[] mappedValues = arrayFactory.apply(sourceValues.length);
231 2 1. mapCompiledNode : changed conditional boundary → KILLED
2. mapCompiledNode : negated conditional → KILLED
        for (int valueIndex = 0; valueIndex < sourceValues.length; valueIndex++) {
232
            mappedValues[valueIndex] = valueMapper.apply(sourceValues[valueIndex]);
233
        }
234
235
        final CompiledNode<T> mapped = new CompiledNode<>(node.edgeLabels().clone(), mappedChildren, mappedValues,
236
                node.acceptsRemainingInput(), CompiledNode.DEFAULT_MAX_EXPANDED_INDEX, node.orderedCounts().clone());
237
        cache.put(node, mapped);
238 1 1. mapCompiledNode : replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::mapCompiledNode → KILLED
        return mapped;
239
    }
240
}

Mutations

98

1.1
Location : copyOf
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldPreserveLocalCountsAndOrdering()]
removed call to org/egothor/stemmer/FrequencyTrieBuilders::copyNode → KILLED

101

1.1
Location : copyOf
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/FrequencyTrieBuilders::copyOf → KILLED

118

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

155

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

175

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

191

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

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

195

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

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

197

1.1
Location : copyNode
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldPreserveLocalCountsAndOrdering()]
removed call to org/egothor/stemmer/FrequencyTrieBuilders::copyNode → KILLED

198

1.1
Location : copyNode
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldReconstructUsingReductionModeShortcut()]
removed call to java/lang/StringBuilder::setLength → KILLED

2.2
Location : copyNode
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldReconstructUsingReductionModeShortcut()]
Replaced integer subtraction with addition → KILLED

218

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

219

1.1
Location : mapCompiledNode
Killed by : org.egothor.stemmer.StemmerModelDocumentationExamplesTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelDocumentationExamplesTest]/[method:loadsExplicitDescriptor()]
replaced return value with null for org/egothor/stemmer/FrequencyTrieBuilders::mapCompiledNode → KILLED

225

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

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

231

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

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

238

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

Active mutators

Tests examined


Report generated by PIT 1.22.1