ReducedNode.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.LinkedHashMap;
34
import java.util.Map;
35
36
/**
37
 * Canonical reduced node used during subtree merging.
38
 *
39
 * <p>
40
 * The maps exposed by the accessors are the internal backing state of the
41
 * canonical reduced node. They are returned directly for efficiency and are
42
 * intended only for closely related trie-reduction infrastructure.
43
 *
44
 * @param <V> value type
45
 */
46
public final class ReducedNode<V> {
47
48
    /**
49
     * Reduction signature.
50
     */
51
    private final ReductionSignature<V> signature;
52
53
    /**
54
     * Aggregated local value counts.
55
     */
56
    private final Map<V, Integer> localCounts;
57
58
    /**
59
     * Canonical children by edge.
60
     */
61
    private final Map<Character, ReducedNode<V>> children;
62
63
    /**
64
     * Whether this reduced node accepts any remaining lookup input.
65
     */
66
    private final boolean acceptsRemainingInput;
67
68
    /**
69
     * Creates a new reduced node.
70
     *
71
     * @param signature   reduction signature
72
     * @param localCounts local counts
73
     * @param children    children
74
     * @param acceptsRemainingInput whether this node accepts any remaining lookup
75
     *                              input
76
     */
77
    public ReducedNode(final ReductionSignature<V> signature, final Map<V, Integer> localCounts,
78
            final Map<Character, ReducedNode<V>> children, final boolean acceptsRemainingInput) {
79
        this.signature = signature;
80
        this.localCounts = new LinkedHashMap<>(localCounts);
81
        this.children = new LinkedHashMap<>(children);
82
        this.acceptsRemainingInput = acceptsRemainingInput;
83
    }
84
85
    /**
86
     * Creates a new non-accepting reduced node.
87
     *
88
     * @param signature   reduction signature
89
     * @param localCounts local counts
90
     * @param children    children
91
     */
92
    public ReducedNode(final ReductionSignature<V> signature, final Map<V, Integer> localCounts,
93
            final Map<Character, ReducedNode<V>> children) {
94
        this(signature, localCounts, children, false);
95
    }
96
97
    /**
98
     * Returns the reduction signature of this canonical node.
99
     *
100
     * @return reduction signature
101
     */
102
    public ReductionSignature<V> signature() {
103 1 1. signature : replaced return value with null for org/egothor/stemmer/trie/ReducedNode::signature → KILLED
        return this.signature;
104
    }
105
106
    /**
107
     * Returns the internal aggregated local value-count map.
108
     *
109
     * <p>
110
     * The returned map is the internal backing state of this canonical reduced node
111
     * and is exposed only for efficient cooperation with trie-reduction
112
     * infrastructure.
113
     *
114
     * @return internal aggregated local value-count map
115
     */
116
    public Map<V, Integer> localCounts() {
117 1 1. localCounts : replaced return value with Collections.emptyMap for org/egothor/stemmer/trie/ReducedNode::localCounts → KILLED
        return this.localCounts;
118
    }
119
120
    /**
121
     * Returns the internal canonical child map indexed by transition character.
122
     *
123
     * <p>
124
     * The returned map is the internal backing state of this canonical reduced node
125
     * and is exposed only for efficient cooperation with trie-reduction
126
     * infrastructure.
127
     *
128
     * @return internal canonical child map
129
     */
130
    public Map<Character, ReducedNode<V>> children() {
131 1 1. children : replaced return value with Collections.emptyMap for org/egothor/stemmer/trie/ReducedNode::children → KILLED
        return this.children;
132
    }
133
134
    /**
135
     * Returns whether this node accepts any remaining lookup input.
136
     *
137
     * @return {@code true} for a contracted accepting leaf
138
     */
139
    public boolean acceptsRemainingInput() {
140 2 1. acceptsRemainingInput : replaced boolean return with false for org/egothor/stemmer/trie/ReducedNode::acceptsRemainingInput → KILLED
2. acceptsRemainingInput : replaced boolean return with true for org/egothor/stemmer/trie/ReducedNode::acceptsRemainingInput → KILLED
        return this.acceptsRemainingInput;
141
    }
142
143
    /**
144
     * Merges additional local counts into this node.
145
     *
146
     * @param additionalCounts additional local counts
147
     */
148
    public void mergeLocalCounts(final Map<V, Integer> additionalCounts) {
149
        for (Map.Entry<V, Integer> entry : additionalCounts.entrySet()) {
150
            final Integer previous = this.localCounts.get(entry.getKey());
151 1 1. mergeLocalCounts : negated conditional → KILLED
            if (previous == null) {
152
                this.localCounts.put(entry.getKey(), entry.getValue());
153
            } else {
154 1 1. mergeLocalCounts : Replaced integer addition with subtraction → KILLED
                this.localCounts.put(entry.getKey(), previous + entry.getValue());
155
            }
156
        }
157
    }
158
159
    /**
160
     * Merges child references into this node.
161
     *
162
     * <p>
163
     * For nodes with the same reduction signature, child edge sets and child
164
     * signatures must be compatible. This method therefore only needs to verify
165
     * consistency and store the canonical child instance.
166
     *
167
     * @param additionalChildren additional children
168
     */
169
    public void mergeChildren(final Map<Character, ReducedNode<V>> additionalChildren) {
170
        for (Map.Entry<Character, ReducedNode<V>> entry : additionalChildren.entrySet()) {
171
            final ReducedNode<V> existing = this.children.get(entry.getKey());
172 1 1. mergeChildren : negated conditional → KILLED
            if (existing == null) {
173
                this.children.put(entry.getKey(), entry.getValue());
174 1 1. mergeChildren : negated conditional → KILLED
            } else if (existing != entry.getValue()) { // NOPMD - we have canonical instances
175
                throw new IllegalStateException("Incompatible canonical child encountered during reduction.");
176
            }
177
        }
178
    }
179
}

Mutations

103

1.1
Location : signature
Killed by : org.egothor.stemmer.trie.ReductionSignatureTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReductionSignatureTest]/[method:shouldIncludeChildSignaturesInEquality()]
replaced return value with null for org/egothor/stemmer/trie/ReducedNode::signature → KILLED

117

1.1
Location : localCounts
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldExposeInternalBackingLocalCountsMap()]
replaced return value with Collections.emptyMap for org/egothor/stemmer/trie/ReducedNode::localCounts → KILLED

131

1.1
Location : children
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldExposeInternalBackingChildrenMap()]
replaced return value with Collections.emptyMap for org/egothor/stemmer/trie/ReducedNode::children → KILLED

140

1.1
Location : acceptsRemainingInput
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:shouldContractUniformInternalSubtreeIntoAcceptingLeaf()]
replaced boolean return with false for org/egothor/stemmer/trie/ReducedNode::acceptsRemainingInput → KILLED

2.2
Location : acceptsRemainingInput
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:trieRejectsNullLookupKeys()]
replaced boolean return with true for org/egothor/stemmer/trie/ReducedNode::acceptsRemainingInput → KILLED

151

1.1
Location : mergeLocalCounts
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldMergeLocalCountsBySummingAndAppending()]
negated conditional → KILLED

154

1.1
Location : mergeLocalCounts
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldMergeLocalCountsBySummingAndAppending()]
Replaced integer addition with subtraction → KILLED

172

1.1
Location : mergeChildren
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldRejectIncompatibleCanonicalChildInstanceForSameEdge()]
negated conditional → KILLED

174

1.1
Location : mergeChildren
Killed by : org.egothor.stemmer.trie.ReducedNodeTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.ReducedNodeTest]/[method:shouldAllowSameCanonicalChildInstanceForSameEdge()]
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1