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  package org.apache.commons.codec.language.bm;
19  
20  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNotSame;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests {@link PhoneticEngine.Builder}.
32   *
33   * @since 1.23.0
34   */
35  class PhoneticEngineBuilderTest {
36  
37      /**
38       * Returns a string of {@code 'a'} characters with the given length, compatible with Java 8.
39       *
40       * @param length the desired length.
41       * @return a string of the specified length filled with {@code 'a'}.
42       */
43      private static String repeat(final int length) {
44          final char[] chars = new char[length];
45          java.util.Arrays.fill(chars, 'a');
46          return new String(chars);
47      }
48  
49      /**
50       * Tests that the builder produces a non-null {@link PhoneticEngine}.
51       */
52      @Test
53      void testBuilderGet() {
54          final PhoneticEngine engine = PhoneticEngine.builder().get();
55          assertNotNull(engine);
56      }
57  
58      /**
59       * Tests that {@link PhoneticEngine#builder()} returns a non-null builder.
60       */
61      @Test
62      void testBuilderNotNull() {
63          assertNotNull(PhoneticEngine.builder());
64      }
65  
66      /**
67       * Tests that successive calls to {@link PhoneticEngine#builder()} return distinct builder instances.
68       */
69      @Test
70      void testBuilderReturnsNewInstance() {
71          assertNotSame(PhoneticEngine.builder(), PhoneticEngine.builder());
72      }
73  
74      /**
75       * Tests that the default maximum input length (666) allows input of exactly that length to be encoded without throwing.
76       */
77      @Test
78      void testDefaultMaxInputLengthAllowsInputAtLimit() {
79          // Default max input length is 666 (Hubert Blaine Wolfeschlegelsteinhausenbergerdorff Sr.)
80          final PhoneticEngine engine = PhoneticEngine.builder().get();
81          final String input = repeat(666);
82          assertDoesNotThrow(() -> engine.encode(input));
83      }
84  
85      /**
86       * Tests that the default maximum input length (666) causes an {@link IllegalArgumentException}
87       * when input exceeds that limit.
88       */
89      @Test
90      void testDefaultMaxInputLengthRejectsInputBeyondLimit() {
91          final PhoneticEngine engine = PhoneticEngine.builder().get();
92          final String input = repeat(667);
93          assertThrows(IllegalArgumentException.class, () -> engine.encode(input));
94      }
95  
96      /**
97       * Tests the default values of the builder.
98       */
99      @Test
100     void testDefaultValues() {
101         final PhoneticEngine engine = PhoneticEngine.builder().get();
102         assertEquals(NameType.GENERIC, engine.getNameType());
103         assertEquals(RuleType.APPROX, engine.getRuleType());
104         assertTrue(engine.isConcat());
105         assertEquals(20, engine.getMaxPhonemes());
106     }
107 
108     /**
109      * Tests that the builder produces engines that encode correctly (ASHKENAZI/APPROX/concat).
110      */
111     @Test
112     void testEngineEncodeAshkenaziApprox() {
113         // @formatter:off
114         final PhoneticEngine engine = PhoneticEngine.builder()
115                 .setNameType(NameType.ASHKENAZI)
116                 .setRuleType(RuleType.APPROX)
117                 .setConcat(true)
118                 .setMaxPhonemes(10)
119                 .get();
120         // @formatter:on
121         final String result = engine.encode("Renault");
122         assertEquals("rYnDlt|rYnalt|rYnult|rinDlt|rinalt|rinolt|rinult", result);
123     }
124 
125     /**
126      * Tests that the builder produces engines that encode correctly (GENERIC/APPROX/concat).
127      */
128     @Test
129     void testEngineEncodeGenericApprox() {
130         // @formatter:off
131         final PhoneticEngine engine = PhoneticEngine.builder()
132                 .setNameType(NameType.GENERIC)
133                 .setRuleType(RuleType.APPROX)
134                 .setConcat(true)
135                 .setMaxPhonemes(10)
136                 .get();
137         // @formatter:on
138         final String result = engine.encode("Renault");
139         assertEquals("rinD|rinDlt|rina|rinalt|rino|rinolt|rinu|rinult", result);
140     }
141 
142     /**
143      * Tests that the builder produces engines that encode correctly (GENERIC/EXACT/concat).
144      */
145     @Test
146     void testEngineEncodeGenericExact() {
147         // @formatter:off
148         final PhoneticEngine engine = PhoneticEngine.builder()
149                 .setNameType(NameType.GENERIC)
150                 .setRuleType(RuleType.EXACT)
151                 .setConcat(true)
152                 .setMaxPhonemes(10)
153                 .get();
154         // @formatter:on
155         final String result = engine.encode("SntJohn-Smith");
156         assertEquals("sntjonsmit", result);
157     }
158 
159     /**
160      * Tests that a builder-created engine is not the same instance when built twice.
161      */
162     @Test
163     void testGetReturnsDifferentInstances() {
164         final PhoneticEngine.Builder builder = PhoneticEngine.builder();
165         final PhoneticEngine engine1 = builder.get();
166         final PhoneticEngine engine2 = builder.get();
167         assertNotSame(engine1, engine2);
168     }
169 
170     /**
171      * Tests method chaining: all setters return the same builder instance.
172      */
173     @Test
174     void testMethodChaining() {
175         // @formatter:off
176         final PhoneticEngine.Builder builder = PhoneticEngine.builder();
177         assertNotNull(builder
178                 .setNameType(NameType.SEPHARDIC)
179                 .setRuleType(RuleType.APPROX)
180                 .setConcat(true)
181                 .setMaxPhonemes(15)
182                 .get());
183         // @formatter:on
184     }
185 
186     /**
187      * Tests {@link PhoneticEngine.Builder#setAll(PhoneticEngine)} copies all properties.
188      */
189     @Test
190     void testSetAll() {
191         // @formatter:off
192         final PhoneticEngine original = PhoneticEngine.builder()
193                 .setNameType(NameType.ASHKENAZI)
194                 .setRuleType(RuleType.EXACT)
195                 .setConcat(false)
196                 .setMaxPhonemes(5)
197                 .get();
198         // @formatter:on
199         final PhoneticEngine copy = PhoneticEngine.builder().setAll(original).get();
200         assertEquals(original.getNameType(), copy.getNameType());
201         assertEquals(original.getRuleType(), copy.getRuleType());
202         assertEquals(original.isConcat(), copy.isConcat());
203         assertEquals(original.getMaxPhonemes(), copy.getMaxPhonemes());
204     }
205 
206     /**
207      * Tests {@link PhoneticEngine.Builder#setConcat(boolean)} with {@code false}.
208      */
209     @Test
210     void testSetConcatFalse() {
211         final PhoneticEngine engine = PhoneticEngine.builder().setConcat(false).get();
212         assertFalse(engine.isConcat());
213     }
214 
215     /**
216      * Tests {@link PhoneticEngine.Builder#setConcat(boolean)} with {@code true}.
217      */
218     @Test
219     void testSetConcatTrue() {
220         final PhoneticEngine engine = PhoneticEngine.builder().setConcat(true).get();
221         assertTrue(engine.isConcat());
222     }
223 
224     /**
225      * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a custom value:
226      * input exactly at the limit is accepted.
227      */
228     @Test
229     void testSetMaxInputLengthAllowsInputAtCustomLimit() {
230         final int customLimit = 10;
231         final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(customLimit).get();
232         final String input = repeat(customLimit);
233         assertDoesNotThrow(() -> engine.encode(input));
234     }
235 
236     /**
237      * Tests that {@link PhoneticEngine.Builder#setMaxInputLength(int)} with {@link Integer#MAX_VALUE}
238      * allows very long input.
239      */
240     @Test
241     void testSetMaxInputLengthMaxValue() {
242         final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(Integer.MAX_VALUE).get();
243         // A very long string should not trigger the length check
244         final String input = repeat(10_000);
245         assertDoesNotThrow(() -> engine.encode(input));
246     }
247 
248     /**
249      * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a custom value:
250      * input one character beyond the limit is rejected with {@link IllegalArgumentException}.
251      */
252     @Test
253     void testSetMaxInputLengthRejectsInputBeyondCustomLimit() {
254         final int customLimit = 10;
255         final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(customLimit).get();
256         final String input = repeat(customLimit + 1);
257         assertThrows(IllegalArgumentException.class, () -> engine.encode(input));
258     }
259 
260     /**
261      * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} that the setter returns
262      * the same builder instance to enable method chaining.
263      */
264     @Test
265     void testSetMaxInputLengthReturnsBuilder() {
266         final PhoneticEngine.Builder builder = PhoneticEngine.builder();
267         assertNotNull(builder.setMaxInputLength(100));
268     }
269 
270     /**
271      * Tests that {@link PhoneticEngine.Builder#setMaxInputLength(int)} can be combined
272      * with other builder settings and still produces a valid engine.
273      */
274     @Test
275     void testSetMaxInputLengthWithOtherSettings() {
276         // @formatter:off
277         final PhoneticEngine engine = PhoneticEngine.builder()
278                 .setNameType(NameType.ASHKENAZI)
279                 .setRuleType(RuleType.APPROX)
280                 .setConcat(true)
281                 .setMaxPhonemes(10)
282                 .setMaxInputLength(5)
283                 .get();
284         // @formatter:on
285         assertNotNull(engine);
286         assertDoesNotThrow(() -> engine.encode("abcde"));
287         assertThrows(IllegalArgumentException.class, () -> engine.encode("abcdef"));
288     }
289 
290     /**
291      * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a limit of 0:
292      * null input causes a {@link NullPointerException}.
293      */
294     @Test
295     void testSetMaxInputLengthZeroNullInputThrowsNpe() {
296         final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(0).get();
297         assertThrows(NullPointerException.class, () -> engine.encode(null));
298     }
299 
300     /**
301      * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a limit of 0:
302      * any non-empty input is rejected.
303      */
304     @Test
305     void testSetMaxInputLengthZeroRejectsAnyInput() {
306         final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(0).get();
307         assertThrows(IllegalArgumentException.class, () -> engine.encode("a"));
308     }
309 
310     /**
311      * Tests {@link PhoneticEngine.Builder#setMaxPhonemes(int)}.
312      */
313     @Test
314     void testSetMaxPhonemes() {
315         final int maxPhonemes = 10;
316         final PhoneticEngine engine = PhoneticEngine.builder().setMaxPhonemes(maxPhonemes).get();
317         assertEquals(maxPhonemes, engine.getMaxPhonemes());
318     }
319 
320     /**
321      * Tests {@link PhoneticEngine.Builder#setMaxPhonemes(int)} with {@link Integer#MAX_VALUE}.
322      */
323     @Test
324     void testSetMaxPhonemesMaxValue() {
325         final PhoneticEngine engine = PhoneticEngine.builder().setMaxPhonemes(Integer.MAX_VALUE).get();
326         assertEquals(Integer.MAX_VALUE, engine.getMaxPhonemes());
327     }
328 
329     /**
330      * Tests {@link PhoneticEngine.Builder#setNameType(NameType)} with ASHKENAZI.
331      */
332     @Test
333     void testSetNameTypeAshkenazi() {
334         final PhoneticEngine engine = PhoneticEngine.builder().setNameType(NameType.ASHKENAZI).get();
335         assertEquals(NameType.ASHKENAZI, engine.getNameType());
336     }
337 
338     /**
339      * Tests {@link PhoneticEngine.Builder#setNameType(NameType)}.
340      */
341     @Test
342     void testSetNameTypeGeneric() {
343         final PhoneticEngine engine = PhoneticEngine.builder().setNameType(NameType.GENERIC).get();
344         assertEquals(NameType.GENERIC, engine.getNameType());
345     }
346 
347     /**
348      * Tests {@link PhoneticEngine.Builder#setNameType(NameType)} with SEPHARDIC.
349      */
350     @Test
351     void testSetNameTypeSephardic() {
352         final PhoneticEngine engine = PhoneticEngine.builder().setNameType(NameType.SEPHARDIC).get();
353         assertEquals(NameType.SEPHARDIC, engine.getNameType());
354     }
355 
356     /**
357      * Tests {@link PhoneticEngine.Builder#setRuleType(RuleType)} with APPROX.
358      */
359     @Test
360     void testSetRuleTypeApprox() {
361         final PhoneticEngine engine = PhoneticEngine.builder().setRuleType(RuleType.APPROX).get();
362         assertEquals(RuleType.APPROX, engine.getRuleType());
363     }
364 
365     /**
366      * Tests {@link PhoneticEngine.Builder#setRuleType(RuleType)} with EXACT.
367      */
368     @Test
369     void testSetRuleTypeExact() {
370         final PhoneticEngine engine = PhoneticEngine.builder().setRuleType(RuleType.EXACT).get();
371         assertEquals(RuleType.EXACT, engine.getRuleType());
372     }
373 }