StemmerModelRegistry.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.BufferedReader;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import java.io.InputStreamReader;
37
import java.net.URL;
38
import java.nio.charset.StandardCharsets;
39
import java.util.ArrayList;
40
import java.util.Collections;
41
import java.util.Enumeration;
42
import java.util.LinkedHashMap;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Objects;
46
import java.util.Properties;
47
48
/**
49
 * Immutable deterministic registry of models discovered from classpath indexes.
50
 *
51
 * <p>Discovery enumerates every {@value #INDEX_RESOURCE} visible to the selected
52
 * class loader, validates the referenced descriptors, sorts them by stable model
53
 * identifier, and rejects duplicate identifiers. Selection never depends on
54
 * classpath order. Registry creation validates metadata and resource presence;
55
 * {@link StemmerPatchTrieLoader} verifies resource bytes when loading a model.</p>
56
 *
57
 * <p>Registry creation is not globally cached. Applications should normally
58
 * discover once for a class-loader scope and retain the immutable result.</p>
59
 */
60
@SuppressWarnings({ "PMD.UseProperClassLoader", "PMD.ControlStatementBraces" })
61
public final class StemmerModelRegistry {
62
    /** Fixed classpath index name used by every model artifact. */
63
    public static final String INDEX_RESOURCE = "META-INF/radixor/models.index";
64
    private static final String FORMAT = "radixor-dictionary-tsv-gzip";
65
    private static final int FORMAT_VERSION = 1;
66
    private final Map<String, StemmerModelDescriptor> descriptors;
67
68
    /** Creates an immutable registry from already validated descriptors. */
69
    private StemmerModelRegistry(final Map<String, StemmerModelDescriptor> descriptors) {
70
        this.descriptors = Collections.unmodifiableMap(new LinkedHashMap<>(descriptors));
71
    }
72
73
    /**
74
     * Discovers models through the current thread context class loader.
75
     *
76
     * <p>If the context loader is {@code null}, the defining class loader of this
77
     * registry is used.</p>
78
     *
79
     * @return immutable registry in stable model-ID order
80
     * @throws IOException if index or descriptor resources cannot be enumerated or read
81
     * @throws DuplicateStemmerModelException if two descriptors declare one model ID
82
     * @throws StemmerModelIntegrityException if an index, descriptor, or declared resource is invalid
83
     * @throws UnsupportedStemmerModelFormatException if a descriptor uses an unsupported format
84
     */
85
    public static StemmerModelRegistry fromContextClassLoader() throws IOException {
86
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
87 2 1. fromContextClassLoader : negated conditional → SURVIVED
2. fromContextClassLoader : replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::fromContextClassLoader → KILLED
        return fromClassLoader(classLoader == null ? StemmerModelRegistry.class.getClassLoader() : classLoader);
88
    }
89
90
    /**
91
     * Discovers and validates every indexed descriptor visible to an explicit class loader.
92
     *
93
     * @param classLoader class loader whose indexed model resources are visible
94
     * @return immutable registry in stable model-ID order
95
     * @throws NullPointerException if {@code classLoader} is {@code null}
96
     * @throws IOException if index or descriptor resources cannot be enumerated or read
97
     * @throws DuplicateStemmerModelException if two descriptors declare one model ID
98
     * @throws StemmerModelIntegrityException if an index, descriptor, or declared resource is invalid
99
     * @throws UnsupportedStemmerModelFormatException if a descriptor uses an unsupported format
100
     */
101
    public static StemmerModelRegistry fromClassLoader(final ClassLoader classLoader) throws IOException {
102
        Objects.requireNonNull(classLoader, "classLoader");
103
        final List<URL> indexes = Collections.list(classLoader.getResources(INDEX_RESOURCE));
104 2 1. fromClassLoader : removed call to java/util/List::sort → SURVIVED
2. lambda$fromClassLoader$0 : replaced int return with 0 for org/egothor/stemmer/StemmerModelRegistry::lambda$fromClassLoader$0 → SURVIVED
        indexes.sort((left, right) -> left.toExternalForm().compareTo(right.toExternalForm()));
105
        final List<StemmerModelDescriptor> discovered = new ArrayList<>();
106
        for (URL index : indexes) {
107 1 1. fromClassLoader : removed call to org/egothor/stemmer/StemmerModelRegistry::readIndex → KILLED
            readIndex(index, classLoader, discovered);
108
        }
109 1 1. fromClassLoader : removed call to java/util/Collections::sort → SURVIVED
        Collections.sort(discovered);
110
        final Map<String, StemmerModelDescriptor> byId = new LinkedHashMap<>();
111
        for (StemmerModelDescriptor descriptor : discovered) {
112
            final StemmerModelDescriptor previous = byId.putIfAbsent(descriptor.id(), descriptor);
113 1 1. fromClassLoader : negated conditional → KILLED
            if (previous != null) {
114
                throw new DuplicateStemmerModelException("Duplicate model ID '" + descriptor.id() + "' at "
115
                        + previous.source() + " and " + descriptor.source() + ".");
116
            }
117
        }
118 1 1. fromClassLoader : replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::fromClassLoader → KILLED
        return new StemmerModelRegistry(byId);
119
    }
120
121
    /** Returns all descriptors in stable model-identifier order. */
122 1 1. models : replaced return value with Collections.emptyList for org/egothor/stemmer/StemmerModelRegistry::models → SURVIVED
    public List<StemmerModelDescriptor> models() { return List.copyOf(this.descriptors.values()); }
123
124
    /**
125
     * Returns the model with an exact stable identifier.
126
     *
127
     * @param modelId exact stable model identifier
128
     * @return matching descriptor
129
     * @throws NullPointerException if {@code modelId} is {@code null}
130
     * @throws StemmerModelNotFoundException if the selected class loader exposes no matching descriptor
131
     */
132
    public StemmerModelDescriptor require(final String modelId) {
133
        Objects.requireNonNull(modelId, "modelId");
134
        final StemmerModelDescriptor descriptor = this.descriptors.get(modelId);
135 1 1. require : negated conditional → KILLED
        if (descriptor == null) {
136
            throw new StemmerModelNotFoundException("No model '" + modelId + "' is available. Add org.egothor:radixor-model-"
137
                    + modelId + ":<version> to the runtime classpath.");
138
        }
139 1 1. require : replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::require → KILLED
        return descriptor;
140
    }
141
142
    /**
143
     * Returns all models for a language in stable model-identifier order.
144
     *
145
     * @param language language to filter
146
     * @return immutable list, possibly empty
147
     * @throws NullPointerException if {@code language} is {@code null}
148
     */
149
    public List<StemmerModelDescriptor> findByLanguage(final StemmerPatchTrieLoader.Language language) {
150
        Objects.requireNonNull(language, "language");
151 3 1. findByLanguage : replaced return value with Collections.emptyList for org/egothor/stemmer/StemmerModelRegistry::findByLanguage → KILLED
2. lambda$findByLanguage$1 : negated conditional → KILLED
3. lambda$findByLanguage$1 : replaced boolean return with true for org/egothor/stemmer/StemmerModelRegistry::lambda$findByLanguage$1 → KILLED
        return this.descriptors.values().stream().filter(value -> value.language() == language).toList();
152
    }
153
154
    /**
155
     * Resolves the language's documented default model without classpath-order fallback.
156
     *
157
     * @param language language whose {@link StemmerPatchTrieLoader.Language#defaultModelId()} is required
158
     * @return exact default-model descriptor
159
     * @throws NullPointerException if {@code language} is {@code null}
160
     * @throws StemmerModelNotFoundException if the default model is not visible
161
     * @throws StemmerModelIntegrityException if the default descriptor declares another language
162
     */
163
    public StemmerModelDescriptor requireDefault(final StemmerPatchTrieLoader.Language language) {
164
        Objects.requireNonNull(language, "language");
165
        final StemmerModelDescriptor descriptor = this.descriptors.get(language.defaultModelId());
166 1 1. requireDefault : negated conditional → KILLED
        if (descriptor == null) {
167
            throw new StemmerModelNotFoundException("No default model '" + language.defaultModelId()
168
                    + "' is available for language " + language + ". Add org.egothor:radixor-model-"
169
                    + language.defaultModelId() + ":<version> to the runtime classpath.");
170
        }
171 1 1. requireDefault : negated conditional → KILLED
        if (descriptor.language() != language) {
172
            throw new StemmerModelIntegrityException("Default model '" + descriptor.id() + "' declares language "
173
                    + descriptor.language() + " instead of " + language + ".");
174
        }
175 1 1. requireDefault : replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::requireDefault → KILLED
        return descriptor;
176
    }
177
178
    /** Reads one deterministic index and appends its descriptors. */
179
    private static void readIndex(final URL index, final ClassLoader classLoader,
180
            final List<StemmerModelDescriptor> descriptors) throws IOException {
181
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(index.openStream(), StandardCharsets.UTF_8))) {
182
            String line;
183
            int lineNumber = 0;
184 1 1. readIndex : negated conditional → KILLED
            while ((line = reader.readLine()) != null) {
185 1 1. readIndex : Changed increment from 1 to -1 → SURVIVED
                lineNumber++;
186
                final String path = line.trim();
187 2 1. readIndex : negated conditional → KILLED
2. readIndex : negated conditional → KILLED
                if (path.isEmpty() || path.startsWith("#")) continue;
188 1 1. readIndex : negated conditional → KILLED
                if (!path.matches("META-INF/radixor/models/[a-z0-9-]+\\.properties")) {
189
                    throw new StemmerModelIntegrityException("Malformed model index entry at " + index + ":" + lineNumber + ": " + path);
190
                }
191
                final Enumeration<URL> resources = classLoader.getResources(path);
192 1 1. readIndex : negated conditional → KILLED
                if (!resources.hasMoreElements()) throw new StemmerModelIntegrityException("Indexed descriptor is missing: " + path + " from " + index);
193 1 1. readIndex : negated conditional → KILLED
                while (resources.hasMoreElements()) descriptors.add(readDescriptor(resources.nextElement(), classLoader));
194
            }
195
        }
196
    }
197
198
    /** Parses and validates one immutable descriptor. */
199
    private static StemmerModelDescriptor readDescriptor(final URL source, final ClassLoader classLoader) throws IOException {
200
        final Properties properties = new Properties();
201 1 1. readDescriptor : removed call to java/util/Properties::load → KILLED
        try (InputStream input = source.openStream()) { properties.load(input); }
202
        final String id = required(properties, "model.id", source);
203 1 1. readDescriptor : negated conditional → KILLED
        if (!id.matches("[a-z]{2}(?:-[a-z]{2})?-[a-z0-9]+(?:-[a-z0-9]+)*")) throw new StemmerModelIntegrityException("Invalid model.id '" + id + "' at " + source);
204
        final String format = required(properties, "model.format", source);
205
        final int version;
206
        try { version = Integer.parseInt(required(properties, "model.formatVersion", source)); }
207
        catch (NumberFormatException exception) { throw new StemmerModelIntegrityException("Invalid model.formatVersion at " + source, exception); }
208 2 1. readDescriptor : negated conditional → KILLED
2. readDescriptor : negated conditional → KILLED
        if (!FORMAT.equals(format) || version != FORMAT_VERSION) throw new UnsupportedStemmerModelFormatException("Unsupported model format " + format + " version " + version + " at " + source + ".");
209
        final StemmerPatchTrieLoader.Language language;
210
        try { language = StemmerPatchTrieLoader.Language.valueOf(required(properties, "model.language", source)); }
211
        catch (IllegalArgumentException exception) { throw new StemmerModelIntegrityException("Invalid model.language at " + source, exception); }
212
        final String resource = required(properties, "model.resource", source);
213 1 1. readDescriptor : negated conditional → KILLED
        if (!resource.equals("org/egothor/stemmer/models/" + id + "/stemmer.gz")) throw new StemmerModelIntegrityException("Invalid model.resource for '" + id + "' at " + source);
214 1 1. readDescriptor : negated conditional → KILLED
        if (classLoader.getResource(resource) == null) throw new StemmerModelIntegrityException("Model resource is missing: " + resource + " declared at " + source);
215
        final String checksum = required(properties, "model.sha256", source);
216 1 1. readDescriptor : negated conditional → KILLED
        if (!checksum.matches("[0-9a-f]{64}")) throw new StemmerModelIntegrityException("Invalid model.sha256 at " + source);
217 1 1. readDescriptor : replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::readDescriptor → KILLED
        return new StemmerModelDescriptor(id, required(properties, "model.version", source), language,
218
                required(properties, "model.displayName", source), resource,
219
                Boolean.parseBoolean(required(properties, "model.default", source)), format, version, checksum, source, classLoader);
220
    }
221
222
    /** Returns a required nonblank property. */
223
    private static String required(final Properties properties, final String key, final URL source) {
224
        final String value = properties.getProperty(key);
225 2 1. required : negated conditional → KILLED
2. required : negated conditional → KILLED
        if (value == null || value.isBlank()) throw new StemmerModelIntegrityException("Required property '" + key + "' is missing at " + source);
226 1 1. required : replaced return value with "" for org/egothor/stemmer/StemmerModelRegistry::required → KILLED
        return value.trim();
227
    }
228
}

Mutations

87

1.1
Location : fromContextClassLoader
Killed by : none
negated conditional → SURVIVED
Covering tests

2.2
Location : fromContextClassLoader
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::fromContextClassLoader → KILLED

104

1.1
Location : fromClassLoader
Killed by : none
removed call to java/util/List::sort → SURVIVED
Covering tests

2.2
Location : lambda$fromClassLoader$0
Killed by : none
replaced int return with 0 for org/egothor/stemmer/StemmerModelRegistry::lambda$fromClassLoader$0 → SURVIVED Covering tests

107

1.1
Location : fromClassLoader
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
removed call to org/egothor/stemmer/StemmerModelRegistry::readIndex → KILLED

109

1.1
Location : fromClassLoader
Killed by : none
removed call to java/util/Collections::sort → SURVIVED
Covering tests

113

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

118

1.1
Location : fromClassLoader
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::fromClassLoader → KILLED

122

1.1
Location : models
Killed by : none
replaced return value with Collections.emptyList for org/egothor/stemmer/StemmerModelRegistry::models → SURVIVED
Covering tests

135

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

139

1.1
Location : require
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::require → KILLED

151

1.1
Location : findByLanguage
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with Collections.emptyList for org/egothor/stemmer/StemmerModelRegistry::findByLanguage → KILLED

2.2
Location : lambda$findByLanguage$1
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
negated conditional → KILLED

3.3
Location : lambda$findByLanguage$1
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced boolean return with true for org/egothor/stemmer/StemmerModelRegistry::lambda$findByLanguage$1 → KILLED

166

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

171

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

175

1.1
Location : requireDefault
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::requireDefault → KILLED

184

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

185

1.1
Location : readIndex
Killed by : none
Changed increment from 1 to -1 → SURVIVED
Covering tests

187

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

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

188

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

192

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

193

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

201

1.1
Location : readDescriptor
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
removed call to java/util/Properties::load → KILLED

203

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

208

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

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

213

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

214

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

216

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

217

1.1
Location : readDescriptor
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with null for org/egothor/stemmer/StemmerModelRegistry::readDescriptor → KILLED

225

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

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

226

1.1
Location : required
Killed by : org.egothor.stemmer.StemmerModelRegistryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.StemmerModelRegistryTest]/[method:discoversModelsDeterministically()]
replaced return value with "" for org/egothor/stemmer/StemmerModelRegistry::required → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1