View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  // (FYI: Formatted and sorted with Eclipse)
19  
20  package org.apache.commons.codec.language;
21  
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  
26  import org.apache.commons.codec.AbstractStringEncoderTest;
27  import org.apache.commons.codec.EncoderException;
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests {@link Soundex}.
32   *
33   * <p>
34   * Keep this file in UTF-8 encoding for proper Javadoc processing.
35   * </p>
36   */
37  class SoundexTest extends AbstractStringEncoderTest<Soundex> {
38  
39      @Override
40      protected Soundex createStringEncoder() {
41          return new Soundex();
42      }
43  
44      @Test
45      void testB650() throws EncoderException {
46          // @formatter:off
47          checkEncodingVariations("B650",
48              "BARHAM",
49              "BARONE",
50              "BARRON",
51              "BERNA",
52              "BIRNEY",
53              "BIRNIE",
54              "BOOROM",
55              "BOREN",
56              "BORN",
57              "BOURN",
58              "BOURNE",
59              "BOWRON",
60              "BRAIN",
61              "BRAME",
62              "BRANN",
63              "BRAUN",
64              "BREEN",
65              "BRIEN",
66              "BRIM",
67              "BRIMM",
68              "BRINN",
69              "BRION",
70              "BROOM",
71              "BROOME",
72              "BROWN",
73              "BROWNE",
74              "BRUEN",
75              "BRUHN",
76              "BRUIN",
77              "BRUMM",
78              "BRUN",
79              "BRUNO",
80              "BRYAN",
81              "BURIAN",
82              "BURN",
83              "BURNEY",
84              "BYRAM",
85              "BYRNE",
86              "BYRON",
87              "BYRUM");
88          // @formatter:on
89      }
90  
91      @Test
92      void testBadCharacters() {
93          assertEquals("H452", getStringEncoder().encode("HOL>MES"));
94      }
95  
96      @Test
97      void testDifference() throws EncoderException {
98          // Edge cases
99          assertEquals(0, getStringEncoder().difference(null, null));
100         assertEquals(0, getStringEncoder().difference("", ""));
101         assertEquals(0, getStringEncoder().difference(" ", " "));
102         // Normal cases
103         assertEquals(4, getStringEncoder().difference("Smith", "Smythe"));
104         assertEquals(2, getStringEncoder().difference("Ann", "Andrew"));
105         assertEquals(1, getStringEncoder().difference("Margaret", "Andrew"));
106         assertEquals(0, getStringEncoder().difference("Janet", "Margaret"));
107         // Examples from https://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp
108         assertEquals(4, getStringEncoder().difference("Green", "Greene"));
109         assertEquals(0, getStringEncoder().difference("Blotchet-Halls", "Greene"));
110         // Examples from https://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_setu-sus_3o6w.asp
111         assertEquals(4, getStringEncoder().difference("Smith", "Smythe"));
112         assertEquals(4, getStringEncoder().difference("Smithers", "Smythers"));
113         assertEquals(2, getStringEncoder().difference("Anothers", "Brothers"));
114     }
115 
116     @Test
117     void testEncodeBasic() {
118         assertEquals("T235", getStringEncoder().encode("testing"));
119         assertEquals("T000", getStringEncoder().encode("The"));
120         assertEquals("Q200", getStringEncoder().encode("quick"));
121         assertEquals("B650", getStringEncoder().encode("brown"));
122         assertEquals("F200", getStringEncoder().encode("fox"));
123         assertEquals("J513", getStringEncoder().encode("jumped"));
124         assertEquals("O160", getStringEncoder().encode("over"));
125         assertEquals("T000", getStringEncoder().encode("the"));
126         assertEquals("L200", getStringEncoder().encode("lazy"));
127         assertEquals("D200", getStringEncoder().encode("dogs"));
128     }
129 
130     /**
131      * Examples from http://www.bradandkathy.com/genealogy/overviewofsoundex.html
132      */
133     @Test
134     void testEncodeBatch2() {
135         assertEquals("A462", getStringEncoder().encode("Allricht"));
136         assertEquals("E166", getStringEncoder().encode("Eberhard"));
137         assertEquals("E521", getStringEncoder().encode("Engebrethson"));
138         assertEquals("H512", getStringEncoder().encode("Heimbach"));
139         assertEquals("H524", getStringEncoder().encode("Hanselmann"));
140         assertEquals("H431", getStringEncoder().encode("Hildebrand"));
141         assertEquals("K152", getStringEncoder().encode("Kavanagh"));
142         assertEquals("L530", getStringEncoder().encode("Lind"));
143         assertEquals("L222", getStringEncoder().encode("Lukaschowsky"));
144         assertEquals("M235", getStringEncoder().encode("McDonnell"));
145         assertEquals("M200", getStringEncoder().encode("McGee"));
146         assertEquals("O155", getStringEncoder().encode("Opnian"));
147         assertEquals("O155", getStringEncoder().encode("Oppenheimer"));
148         assertEquals("R355", getStringEncoder().encode("Riedemanas"));
149         assertEquals("Z300", getStringEncoder().encode("Zita"));
150         assertEquals("Z325", getStringEncoder().encode("Zitzmeinn"));
151     }
152 
153     /**
154      * Examples from http://www.archives.gov/research_room/genealogy/census/soundex.html
155      */
156     @Test
157     void testEncodeBatch3() {
158         assertEquals("W252", getStringEncoder().encode("Washington"));
159         assertEquals("L000", getStringEncoder().encode("Lee"));
160         assertEquals("G362", getStringEncoder().encode("Gutierrez"));
161         assertEquals("P236", getStringEncoder().encode("Pfister"));
162         assertEquals("J250", getStringEncoder().encode("Jackson"));
163         assertEquals("T522", getStringEncoder().encode("Tymczak"));
164         // For VanDeusen: D-250 (D, 2 for the S, 5 for the N, 0 added) is also
165         // possible.
166         assertEquals("V532", getStringEncoder().encode("VanDeusen"));
167     }
168 
169     /**
170      * Examples from: http://www.myatt.demon.co.uk/sxalg.htm
171      */
172     @Test
173     void testEncodeBatch4() {
174         assertEquals("H452", getStringEncoder().encode("HOLMES"));
175         assertEquals("A355", getStringEncoder().encode("ADOMOMI"));
176         assertEquals("V536", getStringEncoder().encode("VONDERLEHR"));
177         assertEquals("B400", getStringEncoder().encode("BALL"));
178         assertEquals("S000", getStringEncoder().encode("SHAW"));
179         assertEquals("J250", getStringEncoder().encode("JACKSON"));
180         assertEquals("S545", getStringEncoder().encode("SCANLON"));
181         assertEquals("S532", getStringEncoder().encode("SAINTJOHN"));
182 
183     }
184 
185     @Test
186     void testEncodeIgnoreApostrophes() throws EncoderException {
187         // @formatter:off
188         checkEncodingVariations("O165",
189             "OBrien",
190             "'OBrien",
191             "O'Brien",
192             "OB'rien",
193             "OBr'ien",
194             "OBri'en",
195             "OBrie'n",
196             "OBrien'");
197         // @formatter:on
198     }
199 
200     /**
201      * Test data from http://www.myatt.demon.co.uk/sxalg.htm
202      *
203      * @throws EncoderException Thrown for some failure scenarios.
204      */
205     @Test
206     void testEncodeIgnoreHyphens() throws EncoderException {
207         // @formatter:off
208         checkEncodingVariations("K525",
209             "KINGSMITH",
210             "-KINGSMITH",
211             "K-INGSMITH",
212             "KI-NGSMITH",
213             "KIN-GSMITH",
214             "KING-SMITH",
215             "KINGS-MITH",
216             "KINGSM-ITH",
217             "KINGSMI-TH",
218             "KINGSMIT-H",
219             "KINGSMITH-");
220         // @formatter:on
221     }
222 
223     @Test
224     void testEncodeIgnoreTrimmable() {
225         assertEquals("W252", getStringEncoder().encode(" \t\n\r Washington \t\n\r "));
226     }
227 
228     @Test
229 // examples and algorithm rules from:  http://www.genealogy.com/articles/research/00000060.html
230     void testGenealogy() { // treat vowels and HW as silent
231         final Soundex s = Soundex.US_ENGLISH_GENEALOGY;
232         assertEquals("H251", s.encode("Heggenburger"));
233         assertEquals("B425", s.encode("Blackman"));
234         assertEquals("S530", s.encode("Schmidt"));
235         assertEquals("L150", s.encode("Lippmann"));
236         // Additional local example
237         assertEquals("D200", s.encode("Dodds")); // 'o' is not a separator here - it is silent
238         assertEquals("D200", s.encode("Dhdds")); // 'h' is silent
239         assertEquals("D200", s.encode("Dwdds")); // 'w' is silent
240     }
241 
242     /**
243      * Consonants from the same code group separated by W or H are treated as one.
244      */
245     @Test
246     void testHWRuleEx1() {
247         // From
248         // http://www.archives.gov/research_room/genealogy/census/soundex.html:
249         // Ashcraft is coded A-261 (A, 2 for the S, C ignored, 6 for the R, 1
250         // for the F). It is not coded A-226.
251         assertEquals("A261", getStringEncoder().encode("Ashcraft"));
252         assertEquals("A261", getStringEncoder().encode("Ashcroft"));
253         assertEquals("Y330", getStringEncoder().encode("yehudit"));
254         assertEquals("Y330", getStringEncoder().encode("yhwdyt"));
255     }
256 
257     /**
258      * Consonants from the same code group separated by W or H are treated as one.
259      *
260      * Test data from http://www.myatt.demon.co.uk/sxalg.htm
261      */
262     @Test
263     void testHWRuleEx2() {
264         assertEquals("B312", getStringEncoder().encode("BOOTHDAVIS"));
265         assertEquals("B312", getStringEncoder().encode("BOOTH-DAVIS"));
266     }
267 
268     /**
269      * Consonants from the same code group separated by W or H are treated as one.
270      *
271      * @throws EncoderException Thrown for some failure scenarios.
272      */
273     @Test
274     void testHWRuleEx3() throws EncoderException {
275         assertEquals("S460", getStringEncoder().encode("Sgler"));
276         assertEquals("S460", getStringEncoder().encode("Swhgler"));
277         // Also S460:
278         // @formatter:off
279         checkEncodingVariations("S460",
280             "SAILOR",
281             "SALYER",
282             "SAYLOR",
283             "SCHALLER",
284             "SCHELLER",
285             "SCHILLER",
286             "SCHOOLER",
287             "SCHULER",
288             "SCHUYLER",
289             "SEILER",
290             "SEYLER",
291             "SHOLAR",
292             "SHULER",
293             "SILAR",
294             "SILER",
295             "SILLER");
296         // @formatter:on
297     }
298 
299     /**
300      * Examples for MS SQLServer from
301      * https://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_setu-sus_3o6w.asp
302      */
303     @Test
304     void testMsSqlServer1() {
305         assertEquals("S530", getStringEncoder().encode("Smith"));
306         assertEquals("S530", getStringEncoder().encode("Smythe"));
307     }
308 
309     /**
310      * Examples for MS SQLServer from
311      * https://support.microsoft.com/default.aspx?scid=https://support.microsoft.com:80/support
312      * /kb/articles/Q100/3/65.asp&NoWebContent=1
313      *
314      * @throws EncoderException Thrown for some failure scenarios.
315      */
316     @Test
317     void testMsSqlServer2() throws EncoderException {
318         checkEncodingVariations("E625", "Erickson", "Erickson", "Erikson", "Ericson", "Ericksen", "Ericsen");
319     }
320 
321     /**
322      * Examples for MS SQLServer from https://databases.about.com/library/weekly/aa042901a.htm
323      */
324     @Test
325     void testMsSqlServer3() {
326         assertEquals("A500", getStringEncoder().encode("Ann"));
327         assertEquals("A536", getStringEncoder().encode("Andrew"));
328         assertEquals("J530", getStringEncoder().encode("Janet"));
329         assertEquals("M626", getStringEncoder().encode("Margaret"));
330         assertEquals("S315", getStringEncoder().encode("Steven"));
331         assertEquals("M240", getStringEncoder().encode("Michael"));
332         assertEquals("R163", getStringEncoder().encode("Robert"));
333         assertEquals("L600", getStringEncoder().encode("Laura"));
334         assertEquals("A500", getStringEncoder().encode("Anne"));
335     }
336 
337     /**
338      * https://issues.apache.org/jira/browse/CODEC-54 https://issues.apache.org/jira/browse/CODEC-56
339      */
340     @Test
341     void testNewInstance() {
342         assertEquals("W452", new Soundex().soundex("Williams"));
343     }
344 
345     @Test
346     void testNewInstance2() {
347         assertEquals("W452", new Soundex(Soundex.US_ENGLISH_MAPPING_STRING.toCharArray()).soundex("Williams"));
348     }
349 
350     @Test
351     void testNewInstance3() {
352         assertEquals("W452", new Soundex(Soundex.US_ENGLISH_MAPPING_STRING).soundex("Williams"));
353     }
354 
355     @Test
356 // examples and algorithm rules from:  http://west-penwith.org.uk/misc/soundex.htm
357     void testSimplifiedSoundex() { // treat vowels and HW as separators
358         final Soundex s = Soundex.US_ENGLISH_SIMPLIFIED;
359         assertEquals("W452", s.encode("WILLIAMS"));
360         assertEquals("B625", s.encode("BARAGWANATH"));
361         assertEquals("D540", s.encode("DONNELL"));
362         assertEquals("L300", s.encode("LLOYD"));
363         assertEquals("W422", s.encode("WOOLCOCK"));
364         // Additional local examples
365         assertEquals("D320", s.encode("Dodds"));
366         assertEquals("D320", s.encode("Dwdds")); // w is a separator
367         assertEquals("D320", s.encode("Dhdds")); // h is a separator
368     }
369 
370     @Test
371     void testSoundexUtilsConstructable() {
372         new SoundexUtils();
373     }
374 
375     @Test
376     void testSoundexUtilsNullBehaviour() {
377         assertNull(SoundexUtils.clean(null));
378         assertEquals("", SoundexUtils.clean(""));
379         assertEquals(0, SoundexUtils.differenceEncoded(null, ""));
380         assertEquals(0, SoundexUtils.differenceEncoded("", null));
381     }
382 
383     /**
384      * https://issues.apache.org/jira/browse/CODEC-54 https://issues.apache.org/jira/browse/CODEC-56
385      */
386     @Test
387     void testUsEnglishStatic() {
388         assertEquals("W452", Soundex.US_ENGLISH.soundex("Williams"));
389     }
390 
391     /**
392      * Fancy characters are not mapped by the default US mapping.
393      *
394      * https://issues.apache.org/jira/browse/CODEC-30
395      */
396     @Test
397     void testUsMappingEWithAcute() {
398         assertEquals("E000", getStringEncoder().encode("e"));
399         if (Character.isLetter('\u00e9')) { // e-acute
400             //         uppercase E-acute
401             assertThrows(IllegalArgumentException.class, () -> getStringEncoder().encode("\u00e9"));
402         } else {
403             assertEquals("", getStringEncoder().encode("\u00e9"));
404         }
405     }
406 
407     /**
408      * Fancy characters are not mapped by the default US mapping.
409      *
410      * https://issues.apache.org/jira/browse/CODEC-30
411      */
412     @Test
413     void testUsMappingOWithDiaeresis() {
414         assertEquals("O000", getStringEncoder().encode("o"));
415         if (Character.isLetter('\u00f6')) { // o-umlaut
416             //         uppercase O-umlaut
417             assertThrows(IllegalArgumentException.class, () -> getStringEncoder().encode("\u00f6"));
418         } else {
419             assertEquals("", getStringEncoder().encode("\u00f6"));
420         }
421     }
422 
423     /**
424      * Tests example from https://en.wikipedia.org/wiki/Soundex#American_Soundex as of 2015-03-22.
425      */
426     @Test
427     void testWikipediaAmericanSoundex() {
428         assertEquals("R163", getStringEncoder().encode("Robert"));
429         assertEquals("R163", getStringEncoder().encode("Rupert"));
430         assertEquals("A261", getStringEncoder().encode("Ashcraft"));
431         assertEquals("A261", getStringEncoder().encode("Ashcroft"));
432         assertEquals("T522", getStringEncoder().encode("Tymczak"));
433         assertEquals("P236", getStringEncoder().encode("Pfister"));
434     }
435 }