1 /*
2 * Copyright 2012-2025 CodeLibs Project and the Others.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
15 */
16 package org.codelibs.fess.dict.mapping;
17
18 import java.util.Arrays;
19 import java.util.Objects;
20
21 import org.apache.commons.lang3.StringUtils;
22 import org.codelibs.core.lang.StringUtil;
23 import org.codelibs.fess.dict.DictionaryItem;
24
25 /**
26 * Represents a single character mapping rule that defines how input characters are mapped to output characters
27 * for text analysis and search processing. This class is used in character mapping dictionaries to transform
28 * text during indexing and search operations.
29 *
30 * <p>Each mapping item consists of one or more input character sequences that are mapped to a single output
31 * character sequence. The mapping supports both original values and new values for update operations.</p>
32 */
33 public class CharMappingItem extends DictionaryItem {
34 /**
35 * Array of input character sequences that will be mapped to the output sequence.
36 * These represent the original/current input values for this mapping rule.
37 */
38 private final String[] inputs;
39
40 /**
41 * The output character sequence that input characters will be mapped to.
42 * This represents the original/current output value for this mapping rule.
43 */
44 private final String output;
45
46 /**
47 * Array of new input character sequences for update operations.
48 * When not null, indicates this item has pending updates.
49 */
50 private String[] newInputs;
51
52 /**
53 * The new output character sequence for update operations.
54 * When not null, indicates this item has pending updates.
55 */
56 private String newOutput;
57
58 /**
59 * Constructs a new CharMappingItem with the specified ID, input sequences, and output sequence.
60 *
61 * @param id the unique identifier for this mapping item
62 * @param inputs array of input character sequences that will be mapped to the output
63 * @param output the output character sequence that inputs will be mapped to
64 */
65 public CharMappingItem(final long id, final String[] inputs, final String output) {
66 this.id = id;
67 this.inputs = inputs;
68 this.output = output == null ? null : output.replace("\n", " ");
69 Arrays.sort(inputs);
70
71 if (id == 0) {
72 // create
73 newInputs = inputs;
74 newOutput = output;
75 }
76 }
77
78 /**
79 * Returns the array of new input character sequences for update operations.
80 * Returns a defensive copy to prevent external modification.
81 *
82 * @return array of new input sequences (defensive copy), or null if no updates are pending
83 */
84 public String[] getNewInputs() {
85 return newInputs == null ? null : newInputs.clone();
86 }
87
88 /**
89 * Sets the array of new input character sequences for update operations.
90 *
91 * @param newInputs array of new input sequences to set
92 */
93 public void setNewInputs(final String[] newInputs) {
94 this.newInputs = newInputs;
95 }
96
97 /**
98 * Returns the new output character sequence for update operations.
99 *
100 * @return the new output sequence, or null if no updates are pending
101 */
102 public String getNewOutput() {
103 return newOutput;
104 }
105
106 /**
107 * Sets the new output character sequence for update operations.
108 * Newline characters in the output are automatically replaced with spaces.
109 *
110 * @param newOutput the new output sequence to set
111 */
112 public void setNewOutput(final String newOutput) {
113 this.newOutput = newOutput == null ? null : newOutput.replace("\n", " ");
114 }
115
116 /**
117 * Returns the array of input character sequences that are mapped to the output.
118 * Returns a defensive copy to prevent external modification.
119 *
120 * @return array of input sequences (defensive copy)
121 */
122 public String[] getInputs() {
123 return inputs == null ? null : inputs.clone();
124 }
125
126 /**
127 * Returns all input sequences joined with newline characters as a single string.
128 * This is useful for display purposes in forms and user interfaces.
129 *
130 * @return string representation of all inputs separated by newlines, or empty string if inputs is null
131 */
132 public String getInputsValue() {
133 if (inputs == null) {
134 return StringUtil.EMPTY;
135 }
136 return String.join("\n", inputs);
137 }
138
139 /**
140 * Returns the output character sequence that inputs are mapped to.
141 *
142 * @return the output sequence
143 */
144 public String getOutput() {
145 return output;
146 }
147
148 /**
149 * Checks whether this mapping item has pending updates.
150 *
151 * @return true if both newInputs and newOutput are not null, indicating pending updates
152 */
153 public boolean isUpdated() {
154 return newInputs != null && newOutput != null;
155 }
156
157 /**
158 * Checks whether this mapping item is marked for deletion.
159 * An item is considered deleted if it has updates pending and the new inputs array is empty.
160 *
161 * @return true if the item is marked for deletion
162 */
163 public boolean isDeleted() {
164 return isUpdated() && newInputs.length == 0;
165 }
166
167 /**
168 * Sorts both the current inputs and new inputs arrays in place.
169 * This ensures consistent ordering for comparison and equality operations.
170 */
171 public void sort() {
172 if (inputs != null) {
173 Arrays.sort(inputs);
174 }
175 if (newInputs != null) {
176 Arrays.sort(newInputs);
177 }
178 }
179
180 /**
181 * Calculates the hash code for this CharMappingItem based on inputs and output.
182 *
183 * @return the hash code value for this object
184 */
185 @Override
186 public int hashCode() {
187 return Objects.hash(Arrays.hashCode(inputs), output);
188 }
189
190 /**
191 * Compares this CharMappingItem with another object for equality.
192 * Two CharMappingItem objects are equal if they have the same inputs and output.
193 * Note: inputs arrays are sorted in the constructor, so no sorting is needed here.
194 *
195 * @param obj the object to compare with
196 * @return true if the objects are equal, false otherwise
197 */
198 @Override
199 public boolean equals(final Object obj) {
200 if (this == obj) {
201 return true;
202 }
203 if (obj == null || getClass() != obj.getClass()) {
204 return false;
205 }
206 final CharMappingItem other = (CharMappingItem) obj;
207 if (!Arrays.equals(inputs, other.inputs)) {
208 return false;
209 }
210 return Objects.equals(output, other.output);
211 }
212
213 /**
214 * Returns a string representation of this CharMappingItem including all fields.
215 *
216 * @return string representation of this object
217 */
218 @Override
219 public String toString() {
220 return "MappingItem [id=" + id + ", inputs=" + Arrays.toString(inputs) + ", output=" + output + ", newInputs="
221 + Arrays.toString(newInputs) + ", newOutput=" + newOutput + "]";
222 }
223
224 /**
225 * Returns a compact string representation of this mapping item in the format "input1,input2=>output".
226 * If the item has pending updates, the new values are used; otherwise, the current values are used.
227 *
228 * @return compact string representation of the mapping rule
229 */
230 public String toLineString() {
231 if (isUpdated()) {
232 return StringUtils.join(newInputs, ",") + "=>" + newOutput;
233 }
234 return StringUtils.join(inputs, ",") + "=>" + output;
235 }
236
237 }