1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.language;
19
20 import java.util.regex.Pattern;
21
22 import org.apache.commons.codec.EncoderException;
23 import org.apache.commons.codec.StringEncoder;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class Nysiis implements StringEncoder {
71
72 private static final char[] CHARS_A = new char[] { 'A' };
73 private static final char[] CHARS_AF = new char[] { 'A', 'F' };
74 private static final char[] CHARS_C = new char[] { 'C' };
75 private static final char[] CHARS_FF = new char[] { 'F', 'F' };
76 private static final char[] CHARS_G = new char[] { 'G' };
77 private static final char[] CHARS_N = new char[] { 'N' };
78 private static final char[] CHARS_NN = new char[] { 'N', 'N' };
79 private static final char[] CHARS_S = new char[] { 'S' };
80 private static final char[] CHARS_SSS = new char[] { 'S', 'S', 'S' };
81
82 private static final Pattern PAT_MAC = Pattern.compile("^MAC");
83 private static final Pattern PAT_KN = Pattern.compile("^KN");
84 private static final Pattern PAT_K = Pattern.compile("^K");
85 private static final Pattern PAT_PH_PF = Pattern.compile("^(PH|PF)");
86 private static final Pattern PAT_SCH = Pattern.compile("^SCH");
87 private static final Pattern PAT_EE_IE = Pattern.compile("(EE|IE)$");
88 private static final Pattern PAT_DT_ETC = Pattern.compile("(DT|RT|RD|NT|ND)$");
89
90 private static final char SPACE = ' ';
91 private static final int TRUE_LENGTH = 6;
92
93
94
95
96
97
98
99
100 private static boolean isVowel(final char c) {
101 return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 private static char[] transcodeRemaining(final char prev, final char curr, final char next, final char aNext) {
119
120 if (curr == 'E' && next == 'V') {
121 return CHARS_AF;
122 }
123
124
125 if (isVowel(curr)) {
126 return CHARS_A;
127 }
128
129
130 if (curr == 'Q') {
131 return CHARS_G;
132 } else if (curr == 'Z') {
133 return CHARS_S;
134 } else if (curr == 'M') {
135 return CHARS_N;
136 }
137
138
139 if (curr == 'K') {
140 if (next == 'N') {
141 return CHARS_NN;
142 } else {
143 return CHARS_C;
144 }
145 }
146
147
148 if (curr == 'S' && next == 'C' && aNext == 'H') {
149 return CHARS_SSS;
150 }
151
152
153 if (curr == 'P' && next == 'H') {
154 return CHARS_FF;
155 }
156
157
158 if (curr == 'H' && (!isVowel(prev) || !isVowel(next))) {
159 return new char[] { prev };
160 }
161
162
163 if (curr == 'W' && isVowel(prev)) {
164 return new char[] { prev };
165 }
166
167 return new char[] { curr };
168 }
169
170
171 private final boolean strict;
172
173
174
175
176
177 public Nysiis() {
178 this(true);
179 }
180
181
182
183
184
185
186
187
188
189
190
191
192 public Nysiis(final boolean strict) {
193 this.strict = strict;
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 @Override
210 public Object encode(Object obj) throws EncoderException {
211 if (!(obj instanceof String)) {
212 throw new EncoderException("Parameter supplied to Nysiis encode is not of type java.lang.String");
213 }
214 return this.nysiis((String) obj);
215 }
216
217
218
219
220
221
222
223
224
225
226 @Override
227 public String encode(String str) {
228 return this.nysiis(str);
229 }
230
231
232
233
234
235
236 public boolean isStrict() {
237 return this.strict;
238 }
239
240
241
242
243
244
245
246
247 public String nysiis(String str) {
248 if (str == null) {
249 return null;
250 }
251
252
253 str = SoundexUtils.clean(str);
254
255 if (str.length() == 0) {
256 return str;
257 }
258
259
260
261 str = PAT_MAC.matcher(str).replaceFirst("MCC");
262 str = PAT_KN.matcher(str).replaceFirst("NN");
263 str = PAT_K.matcher(str).replaceFirst("C");
264 str = PAT_PH_PF.matcher(str).replaceFirst("FF");
265 str = PAT_SCH.matcher(str).replaceFirst("SSS");
266
267
268
269 str = PAT_EE_IE.matcher(str).replaceFirst("Y");
270 str = PAT_DT_ETC.matcher(str).replaceFirst("D");
271
272
273 StringBuilder key = new StringBuilder(str.length());
274 key.append(str.charAt(0));
275
276
277 final char[] chars = str.toCharArray();
278 final int len = chars.length;
279
280 for (int i = 1; i < len; i++) {
281 final char next = i < len - 1 ? chars[i + 1] : SPACE;
282 final char aNext = i < len - 2 ? chars[i + 2] : SPACE;
283 final char[] transcoded = transcodeRemaining(chars[i - 1], chars[i], next, aNext);
284 System.arraycopy(transcoded, 0, chars, i, transcoded.length);
285
286
287 if (chars[i] != chars[i - 1]) {
288 key.append(chars[i]);
289 }
290 }
291
292 if (key.length() > 1) {
293 char lastChar = key.charAt(key.length() - 1);
294
295
296 if (lastChar == 'S') {
297 key.deleteCharAt(key.length() - 1);
298 lastChar = key.charAt(key.length() - 1);
299 }
300
301 if (key.length() > 2) {
302 final char last2Char = key.charAt(key.length() - 2);
303
304 if (last2Char == 'A' && lastChar == 'Y') {
305 key.deleteCharAt(key.length() - 2);
306 }
307 }
308
309
310 if (lastChar == 'A') {
311 key.deleteCharAt(key.length() - 1);
312 }
313 }
314
315 final String string = key.toString();
316 return this.isStrict() ? string.substring(0, Math.min(TRUE_LENGTH, string.length())) : string;
317 }
318
319 }