WordTraversalDirection.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.Objects;
34
35
/**
36
 * Defines the logical direction in which word characters are traversed.
37
 *
38
 * <p>
39
 * The same direction is used consistently in two places:
40
 * </p>
41
 * <ul>
42
 * <li>when a word key is traversed through a trie</li>
43
 * <li>when patch commands are serialized and then applied back to a source
44
 * word</li>
45
 * </ul>
46
 *
47
 * <p>
48
 * {@link #FORWARD} means that processing starts at the logical beginning of the
49
 * stored form and moves toward its end. {@link #BACKWARD} means that processing
50
 * starts at the logical end of the stored form and moves toward its beginning.
51
 * </p>
52
 *
53
 * <p>
54
 * For traditional suffix-oriented Egothor data, {@link #BACKWARD} matches the
55
 * historical behavior. For right-to-left languages whose affix logic should
56
 * operate on the stored form as written, {@link #FORWARD} can be used so that
57
 * neither trie construction nor patch application needs to reverse words
58
 * externally.
59
 * </p>
60
 */
61
public enum WordTraversalDirection {
62
63
    /**
64
     * Traverses a word from its logical beginning toward its logical end.
65
     */
66
    FORWARD,
67
68
    /**
69
     * Traverses a word from its logical end toward its logical beginning.
70
     */
71
    BACKWARD;
72
73
    /**
74
     * Returns the traversal start index for a character sequence of the supplied
75
     * length.
76
     *
77
     * @param length sequence length
78
     * @return start index, or {@code -1} when the sequence is empty and traversal
79
     *         should therefore not begin
80
     * @throws IllegalArgumentException if {@code length} is negative
81
     */
82
    public int startIndex(final int length) {
83 2 1. startIndex : changed conditional boundary → KILLED
2. startIndex : negated conditional → KILLED
        if (length < 0) {
84
            throw new IllegalArgumentException("length must not be negative.");
85
        }
86 1 1. startIndex : negated conditional → KILLED
        if (length == 0) {
87 1 1. startIndex : replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::startIndex → KILLED
            return -1;
88
        }
89 3 1. startIndex : negated conditional → KILLED
2. startIndex : replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::startIndex → KILLED
3. startIndex : Replaced integer subtraction with addition → KILLED
        return this == FORWARD ? 0 : length - 1;
90
    }
91
92
    /**
93
     * Returns the logical character index addressed by the supplied traversal
94
     * offset.
95
     *
96
     * <p>
97
     * A traversal offset of {@code 0} addresses the first character seen in this
98
     * direction, {@code 1} the second character, and so on.
99
     * </p>
100
     *
101
     * @param length          sequence length
102
     * @param traversalOffset zero-based offset from the traversal start
103
     * @return corresponding logical character index
104
     * @throws IllegalArgumentException if any argument is outside the valid range
105
     */
106
    public int logicalIndex(final int length, final int traversalOffset) {
107 2 1. logicalIndex : changed conditional boundary → SURVIVED
2. logicalIndex : negated conditional → KILLED
        if (length < 0) {
108
            throw new IllegalArgumentException("length must not be negative.");
109
        }
110 4 1. logicalIndex : negated conditional → KILLED
2. logicalIndex : changed conditional boundary → KILLED
3. logicalIndex : negated conditional → KILLED
4. logicalIndex : changed conditional boundary → KILLED
        if (traversalOffset < 0 || traversalOffset >= length) {
111
            throw new IllegalArgumentException("traversalOffset is outside the valid range.");
112
        }
113 4 1. logicalIndex : negated conditional → KILLED
2. logicalIndex : Replaced integer subtraction with addition → KILLED
3. logicalIndex : Replaced integer subtraction with addition → KILLED
4. logicalIndex : replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::logicalIndex → KILLED
        return this == FORWARD ? traversalOffset : length - 1 - traversalOffset;
114
    }
115
116
    /**
117
     * Returns the characters of the supplied word in this traversal order.
118
     *
119
     * @param word source word
120
     * @return traversal-ordered characters
121
     * @throws NullPointerException if {@code word} is {@code null}
122
     */
123
    public char[] toTraversalCharacters(final String word) {
124
        Objects.requireNonNull(word, "word");
125
        final char[] characters = word.toCharArray();
126 1 1. toTraversalCharacters : negated conditional → KILLED
        if (this == FORWARD) {
127 1 1. toTraversalCharacters : replaced return value with null for org/egothor/stemmer/WordTraversalDirection::toTraversalCharacters → KILLED
            return characters;
128
        }
129
130 5 1. toTraversalCharacters : changed conditional boundary → SURVIVED
2. toTraversalCharacters : Changed increment from -1 to 1 → KILLED
3. toTraversalCharacters : Replaced integer subtraction with addition → KILLED
4. toTraversalCharacters : negated conditional → KILLED
5. toTraversalCharacters : Changed increment from 1 to -1 → KILLED
        for (int left = 0, right = characters.length - 1; left < right; left++, right--) { // NOPMD
131
            final char swap = characters[left];
132
            characters[left] = characters[right];
133
            characters[right] = swap;
134
        }
135 1 1. toTraversalCharacters : replaced return value with null for org/egothor/stemmer/WordTraversalDirection::toTraversalCharacters → KILLED
        return characters;
136
    }
137
138
    /**
139
     * Converts a path represented in traversal order back to the logical key form.
140
     *
141
     * @param traversalPath key path in traversal order
142
     * @return logical key form
143
     * @throws NullPointerException if {@code traversalPath} is {@code null}
144
     */
145
    public String traversalPathToLogicalKey(final CharSequence traversalPath) {
146
        Objects.requireNonNull(traversalPath, "traversalPath");
147 1 1. traversalPathToLogicalKey : negated conditional → KILLED
        if (this == FORWARD) {
148 1 1. traversalPathToLogicalKey : replaced return value with "" for org/egothor/stemmer/WordTraversalDirection::traversalPathToLogicalKey → KILLED
            return traversalPath.toString();
149
        }
150 1 1. traversalPathToLogicalKey : replaced return value with "" for org/egothor/stemmer/WordTraversalDirection::traversalPathToLogicalKey → KILLED
        return new StringBuilder(traversalPath).reverse().toString();
151
    }
152
}

Mutations

83

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

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

86

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

87

1.1
Location : startIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:startIndexFollowsDirectionAndValidatesNegatives()]
replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::startIndex → KILLED

89

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

2.2
Location : startIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:startIndexFollowsDirectionAndValidatesNegatives()]
replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::startIndex → KILLED

3.3
Location : startIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:startIndexFollowsDirectionAndValidatesNegatives()]
Replaced integer subtraction with addition → KILLED

107

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

2.2
Location : logicalIndex
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

110

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

2.2
Location : logicalIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:logicalIndexMapsOffsetsInBothDirections()]
changed conditional boundary → KILLED

3.3
Location : logicalIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:logicalIndexMapsOffsetsInBothDirections()]
negated conditional → KILLED

4.4
Location : logicalIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:logicalIndexMapsOffsetsInBothDirections()]
changed conditional boundary → KILLED

113

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

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

3.3
Location : logicalIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:logicalIndexMapsOffsetsInBothDirections()]
Replaced integer subtraction with addition → KILLED

4.4
Location : logicalIndex
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:logicalIndexMapsOffsetsInBothDirections()]
replaced int return with 0 for org/egothor/stemmer/WordTraversalDirection::logicalIndex → KILLED

126

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

127

1.1
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
replaced return value with null for org/egothor/stemmer/WordTraversalDirection::toTraversalCharacters → KILLED

130

1.1
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
Changed increment from -1 to 1 → KILLED

2.2
Location : toTraversalCharacters
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

3.3
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
Replaced integer subtraction with addition → KILLED

4.4
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
negated conditional → KILLED

5.5
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
Changed increment from 1 to -1 → KILLED

135

1.1
Location : toTraversalCharacters
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
replaced return value with null for org/egothor/stemmer/WordTraversalDirection::toTraversalCharacters → KILLED

147

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

148

1.1
Location : traversalPathToLogicalKey
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
replaced return value with "" for org/egothor/stemmer/WordTraversalDirection::traversalPathToLogicalKey → KILLED

150

1.1
Location : traversalPathToLogicalKey
Killed by : org.egothor.stemmer.WordTraversalDirectionTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.WordTraversalDirectionTest]/[method:traversalCharacterConversionPreservesAndReversesAsExpected()]
replaced return value with "" for org/egothor/stemmer/WordTraversalDirection::traversalPathToLogicalKey → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1