ReductionContext.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.trie;
32
33
import java.util.Collections;
34
import java.util.IdentityHashMap;
35
import java.util.LinkedHashMap;
36
import java.util.Map;
37
import java.util.Objects;
38
import java.util.Set;
39
40
import org.egothor.stemmer.ReductionSettings;
41
42
/**
43
 * Mutable state confined to one bottom-up trie-reduction pass.
44
 *
45
 * <p>
46
 * The context owns the canonical-node table for the configured semantic
47
 * reduction mode. It also tracks provenance when a compiled DAG has been expanded
48
 * into mutable logical paths, ensuring that already-aggregated counts are not
49
 * multiplied during recompilation.
50
 * </p>
51
 *
52
 * <p>
53
 * Instances are not thread-safe and must not be reused across concurrent or
54
 * sequential builder compilations.
55
 * </p>
56
 *
57
 * @param <V> value type
58
 */
59
public final class ReductionContext<V> {
60
61
    /**
62
     * Reduction settings.
63
     */
64
    private final ReductionSettings settings;
65
66
    /**
67
     * Canonical nodes by signature.
68
     */
69
    private final Map<ReductionSignature<V>, ReducedNode<V>> canonicalNodes;
70
71
    /**
72
     * Source compiled-node identities already represented by each canonical node.
73
     */
74
    private final Map<ReducedNode<V>, Set<Object>> contributedCompiledSources;
75
76
    /**
77
     * Creates an empty reduction context for one compilation.
78
     *
79
     * @param settings immutable reduction settings governing canonical equality
80
     * @throws NullPointerException if {@code settings} is {@code null}
81
     */
82
    public ReductionContext(final ReductionSettings settings) {
83
        this.settings = Objects.requireNonNull(settings, "settings");
84
        this.canonicalNodes = new LinkedHashMap<>();
85
        this.contributedCompiledSources = new IdentityHashMap<>();
86
    }
87
88
    /**
89
     * Looks up the canonical node previously registered for {@code signature}.
90
     *
91
     * @param signature semantic subtree signature
92
     * @return canonical node, or {@code null} if absent
93
     * @throws NullPointerException if {@code signature} is {@code null}
94
     */
95
    public ReducedNode<V> lookup(final ReductionSignature<V> signature) {
96 1 1. lookup : replaced return value with null for org/egothor/stemmer/trie/ReductionContext::lookup → KILLED
        return this.canonicalNodes.get(Objects.requireNonNull(signature, "signature"));
97
    }
98
99
    /**
100
     * Registers a canonical node for {@code signature}, replacing any previous
101
     * association. Normal bottom-up reduction registers each signature once; the
102
     * replacement behavior keeps this context usable by controlled reconstruction
103
     * and test infrastructure.
104
     *
105
     * @param signature semantic subtree signature
106
     * @param node      canonical reduced node
107
     * @throws NullPointerException if either argument is {@code null}
108
     */
109
    public void register(final ReductionSignature<V> signature, final ReducedNode<V> node) {
110
        Objects.requireNonNull(signature, "signature");
111
        Objects.requireNonNull(node, "node");
112
        this.canonicalNodes.put(signature, node);
113
    }
114
115
    /**
116
     * Records that one canonical node now includes the counts of a source compiled
117
     * DAG node.
118
     *
119
     * <p>
120
     * Reconstruction expands a shared compiled node at every logical path. Its
121
     * counts are already aggregated, so only the first expanded occurrence merged
122
     * into a given canonical node may contribute them again. Both map levels use
123
     * identity semantics because compiled and reduced nodes are graph vertices,
124
     * not value objects.
125
     * </p>
126
     *
127
     * @param canonical     canonical node receiving the contribution
128
     * @param sourceIdentity identity of the source compiled node
129
     * @return {@code true} if this is the first contribution of that source to the
130
     *         canonical node; {@code false} if its counts are already represented
131
     * @throws NullPointerException if either argument is {@code null}
132
     */
133
    public boolean recordCompiledSourceContribution(final ReducedNode<V> canonical, final Object sourceIdentity) {
134
        Objects.requireNonNull(canonical, "canonical");
135
        Objects.requireNonNull(sourceIdentity, "sourceIdentity");
136
        final Set<Object> sources = this.contributedCompiledSources.computeIfAbsent(canonical,
137 1 1. lambda$recordCompiledSourceContribution$0 : replaced return value with Collections.emptySet for org/egothor/stemmer/trie/ReductionContext::lambda$recordCompiledSourceContribution$0 → KILLED
                ignored -> Collections.newSetFromMap(new IdentityHashMap<>()));
138 2 1. recordCompiledSourceContribution : replaced boolean return with false for org/egothor/stemmer/trie/ReductionContext::recordCompiledSourceContribution → SURVIVED
2. recordCompiledSourceContribution : replaced boolean return with true for org/egothor/stemmer/trie/ReductionContext::recordCompiledSourceContribution → KILLED
        return sources.add(sourceIdentity);
139
    }
140
141
    /**
142
     * Returns the immutable settings governing this reduction pass.
143
     *
144
     * @return non-null reduction settings
145
     */
146
    public ReductionSettings settings() {
147 1 1. settings : replaced return value with null for org/egothor/stemmer/trie/ReductionContext::settings → KILLED
        return this.settings;
148
    }
149
150
    /**
151
     * Returns the number of distinct semantic subtree signatures registered so
152
     * far.
153
     *
154
     * @return canonical node count
155
     */
156
    public int canonicalNodeCount() {
157 1 1. canonicalNodeCount : replaced int return with 0 for org/egothor/stemmer/trie/ReductionContext::canonicalNodeCount → KILLED
        return this.canonicalNodes.size();
158
    }
159
}

Mutations

96

1.1
Location : lookup
Killed by : org.egothor.stemmer.trie.ReductionContextTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReductionContextTest]/[method:shouldExposeSettingsAndManageCanonicalNodeRegistry()]
replaced return value with null for org/egothor/stemmer/trie/ReductionContext::lookup → KILLED

137

1.1
Location : lambda$recordCompiledSourceContribution$0
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldReconstructEmptyTrie()]
replaced return value with Collections.emptySet for org/egothor/stemmer/trie/ReductionContext::lambda$recordCompiledSourceContribution$0 → KILLED

138

1.1
Location : recordCompiledSourceContribution
Killed by : org.egothor.stemmer.FrequencyTrieBuildersTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieBuildersTest]/[method:shouldPreserveAggregatedCountsOfSharedCompiledNodes()]
replaced boolean return with true for org/egothor/stemmer/trie/ReductionContext::recordCompiledSourceContribution → KILLED

2.2
Location : recordCompiledSourceContribution
Killed by : none
replaced boolean return with false for org/egothor/stemmer/trie/ReductionContext::recordCompiledSourceContribution → SURVIVED
Covering tests

147

1.1
Location : settings
Killed by : org.egothor.stemmer.trie.ReductionContextTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReductionContextTest]/[method:shouldExposeSettingsAndManageCanonicalNodeRegistry()]
replaced return value with null for org/egothor/stemmer/trie/ReductionContext::settings → KILLED

157

1.1
Location : canonicalNodeCount
Killed by : org.egothor.stemmer.trie.ReductionContextTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReductionContextTest]/[method:shouldExposeSettingsAndManageCanonicalNodeRegistry()]
replaced int return with 0 for org/egothor/stemmer/trie/ReductionContext::canonicalNodeCount → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1