This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-codec.git
commit 6ceaebcd87ba415953620d31f2c35eed38271e42 Author: Gary Gregory <[email protected]> AuthorDate: Fri Aug 7 17:45:12 2026 -0400 Add PhoneticEngine.Builder.setMaxInputLength(int). --- src/changes/changes.xml | 3 +- .../codec/language/bm/BeiderMorseEncoder.java | 9 + .../commons/codec/language/bm/PhoneticEngine.java | 112 ++++++++---- .../language/bm/BeiderMorseEncoderBuilderTest.java | 196 +++++++++++++++++++++ .../codec/language/bm/BeiderMorseEncoderTest.java | 16 +- .../language/bm/PhoneticEngineBuilderTest.java | 125 ++++++++++++- 6 files changed, 420 insertions(+), 41 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3326a5b5..b57ea63a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -48,10 +48,11 @@ The <action> type attribute can be add,update,fix,remove. <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Optimize PhoneticEngine.encode(String, LanguageSet) for speed.</action> <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">RFC1522Codec.decodeText(String) now throws a DecoderException instead of a StringIndexOutOfBoundsException when a separator is missing.</action> <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Optimize Base58.convertFromBase58(byte[], Context) for speed and temp object allocation.</action> - <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Allocate a single MessageDigest and use it in Sha2Crypt.sha2Crypt(byte[], String, String, int, String)..</action> + <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Allocate a single MessageDigest and use it in Sha2Crypt.sha2Crypt(byte[], String, String, int, String).</action> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use PhoneticEngine.Builder and deprecate old constructors.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add BeiderMorseEncoder.Builder and deprecate old constructor.</action> + <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Add PhoneticEngine.Builder.setMaxInputLength(int).</action> <!-- UPDATE --> </release> <release version="1.22.1" date="2026-07-27" description="This is a feature and maintenance release. Java 8 or later is required."> diff --git a/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java b/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java index 40a9548a..15f9c1c0 100644 --- a/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java +++ b/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java @@ -183,6 +183,9 @@ public class BeiderMorseEncoder implements StringEncoder { /** * Sets the number of maximum of phonemes that shall be considered by the engine. + * <p> + * A value less than 0 will reset the maximum number of phonemes to the default {@value PhoneticEngine.Builder#MAX_PHONEMES}. + * </p> * * @param maxPhonemes the maximum number of phonemes returned by the engine. * @since 1.7 @@ -194,6 +197,9 @@ public class BeiderMorseEncoder implements StringEncoder { /** * Sets the type of name. Use {@link NameType#GENERIC} unless you specifically want phonetic encodings optimized for Ashkenazi or Sephardic Jewish family * names. + * <p> + * A null value will reset the name type to the default of {@link NameType#GENERIC}. + * </p> * * @param nameType the NameType in use. */ @@ -203,6 +209,9 @@ public class BeiderMorseEncoder implements StringEncoder { /** * Sets the rule type to apply. This will widen or narrow the range of phonetic encodings considered. + * <p> + * A null value will reset the rule type to the default of {@link RuleType#APPROX}. + * </p> * * @param ruleType {@link RuleType#APPROX} or {@link RuleType#EXACT} for approximate or exact phonetic matches. */ diff --git a/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java b/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java index a92771ec..e4ca8a75 100644 --- a/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java +++ b/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java @@ -62,13 +62,20 @@ public class PhoneticEngine { */ public static class Builder implements Supplier<PhoneticEngine> { - private NameType nameType = NameType.GENERIC; + /** See https://en.wikipedia.org/wiki/Hubert_Blaine_Wolfeschlegelsteinhausenbergerdorff_Sr. */ + private static final int MAX_INPUT_LENGTH = 666; - private RuleType ruleType = RuleType.APPROX; + private static final int MAX_PHONEMES = 20; private boolean concat = true; - private int maxPhonemes = DEFAULT_MAX_PHONEMES; + private int maxInputLength = MAX_INPUT_LENGTH; + + private int maxPhonemes = MAX_PHONEMES; + + private NameType nameType = NameType.GENERIC; + + private RuleType ruleType = RuleType.APPROX; private Builder() { // empty @@ -104,36 +111,61 @@ public class PhoneticEngine { return this; } + /** + * Sets the maximum input length allowed. + * <p> + * A value less than 0 will reset the maximum input length to the default value of {@value #MAX_INPUT_LENGTH}, see + * <a href="https://en.wikipedia.org/wiki/Hubert_Blaine_Wolfeschlegelsteinhausenbergerdorff_Sr.">Hubert Blaine Wolfeschlegelsteinhausenbergerdorff + * Sr.</a>. + * </p> + * + * @param maxInputLength the maximum input length allowed. + * @return This builder. + */ + public Builder setMaxInputLength(final int maxInputLength) { + this.maxInputLength = maxInputLength < 0 ? MAX_INPUT_LENGTH : maxInputLength; + return this; + } + /** * Sets maximum number of phonemes the engine will handle. + * <p> + * A value less than 0 will reset the maximum number of phonemes to the default {@value #MAX_PHONEMES}. + * </p> * * @param maxPhonemes The maximum number of phonemes the engine will handle. * @return This builder. */ public Builder setMaxPhonemes(final int maxPhonemes) { - this.maxPhonemes = maxPhonemes; + this.maxPhonemes = maxPhonemes < 0 ? MAX_PHONEMES : maxPhonemes; return this; } /** * Sets the name type for the engine to be built. + * <p> + * A null value will reset the name type to the default of {@link NameType#GENERIC}. + * </p> * * @param nameType The type of names the engine will use. * @return This builder. */ public Builder setNameType(final NameType nameType) { - this.nameType = nameType; + this.nameType = nameType != null ? nameType : NameType.GENERIC; return this; } /** * Sets the rule type for the engine to be built. + * <p> + * A null value will reset the rule type to the default of {@link RuleType#APPROX}. + * </p> * * @param ruleType The type of rules the engine will use. * @return This builder. */ public Builder setRuleType(final RuleType ruleType) { - this.ruleType = ruleType; + this.ruleType = ruleType != null ? ruleType : RuleType.APPROX; return this; } } @@ -240,15 +272,15 @@ public class PhoneticEngine { private final Map<String, List<Rule>> finalRules; - private final CharSequence input; - - private final PhonemeBuilder phonemeBuilder; + private boolean found; private int i; + private final CharSequence input; + private final int maxPhonemes; - private boolean found; + private final PhonemeBuilder phonemeBuilder; RulesApplication(final Map<String, List<Rule>> finalRules, final CharSequence input, final PhonemeBuilder phonemeBuilder, final int i, final int maxPhonemes) { @@ -301,8 +333,6 @@ public class PhoneticEngine { } } - private static final int DEFAULT_MAX_PHONEMES = 20; - private static final Map<NameType, Set<String>> NAME_PREFIXES = new EnumMap<>(NameType.class); private static final Pattern QUOTE = Pattern.compile("'"); @@ -335,53 +365,64 @@ public class PhoneticEngine { return strings.stream().collect(Collectors.joining(sep)); } + private final boolean concat; + private final Lang lang; - private final NameType nameType; + private final int maxInputLength; - private final RuleType ruleType; + private final int maxPhonemes; - private final boolean concat; + private final NameType nameType; - private final int maxPhonemes; + private final RuleType ruleType; + /** + * Creates a new, fully-configured phonetic engine. + * + * @param builder The builder to use for configuration. + * @throws IllegalArgumentException if ruleType is RULES. + */ private PhoneticEngine(final Builder builder) { - this(builder.nameType, builder.ruleType, builder.concat, builder.maxPhonemes); + if (builder.ruleType == RuleType.RULES) { + throw new IllegalArgumentException("ruleType must not be " + RuleType.RULES); + } + this.nameType = builder.nameType; + this.ruleType = builder.ruleType; + this.concat = builder.concat; + this.lang = Lang.instance(builder.nameType); + this.maxPhonemes = builder.maxPhonemes; + this.maxInputLength = builder.maxInputLength; } /** * Generates a new, fully-configured phonetic engine. * - * @param nameType the type of names it will use. - * @param ruleType the type of rules it will apply. + * @param nameType the type of names it will use, null is treated as {@link NameType#GENERIC}. + * @param ruleType the type of rules it will apply, null is treated as {@link RuleType#APPROX}. * @param concatenate if it will concatenate multiple encodings. * @deprecated Use {@link #builder()} instead. */ @Deprecated public PhoneticEngine(final NameType nameType, final RuleType ruleType, final boolean concatenate) { - this(nameType, ruleType, concatenate, DEFAULT_MAX_PHONEMES); + this(nameType, ruleType, concatenate, Builder.MAX_PHONEMES); } /** * Generates a new, fully-configured phonetic engine. * - * @param nameType the type of names it will use. - * @param ruleType the type of rules it will apply. + * @param nameType the type of names it will use, null is treated as {@link NameType#GENERIC}. + * @param ruleType the type of rules it will apply, null is treated as {@link RuleType#APPROX}. * @param concatenate if it will concatenate multiple encodings. - * @param maxPhonemes the maximum number of phonemes that will be handled. + * @param maxPhonemes the maximum number of phonemes that will be handled, less than 0 will reset to the default of {@value Builder#MAX_PHONEMES}. + * @throws IllegalArgumentException if ruleType is RULES. * @since 1.7 * @deprecated Use {@link #builder()} instead. */ @Deprecated public PhoneticEngine(final NameType nameType, final RuleType ruleType, final boolean concatenate, final int maxPhonemes) { - if (ruleType == RuleType.RULES) { - throw new IllegalArgumentException("ruleType must not be " + RuleType.RULES); - } - this.nameType = nameType; - this.ruleType = ruleType; - this.concat = concatenate; - this.lang = Lang.instance(nameType); - this.maxPhonemes = maxPhonemes; + this(builder().setNameType(nameType).setRuleType(ruleType).setConcat(concatenate).setMaxPhonemes(maxPhonemes) + .setMaxInputLength(Builder.MAX_INPUT_LENGTH)); } /** @@ -430,8 +471,9 @@ public class PhoneticEngine { /** * Encodes a string to its phonetic representation. * - * @param input the String to encode. + * @param input the String to encode, not null. * @return The encoding of the input. + * @throws IllegalArgumentException if the input is longer than the maximum allowed length. */ public String encode(final String input) { return encode(input, lang.guessLanguages(input)); @@ -440,11 +482,15 @@ public class PhoneticEngine { /** * Encodes an input string into an output phonetic representation, given a set of possible origin languages. * - * @param input String to phoneticise; a String with dashes or spaces separating each word. + * @param input String to phoneticise; a String with dashes or spaces separating each word, not null. * @param languageSet set of possible origin languages. * @return A phonetic representation of the input; a String containing '-'-separated phonetic representations of the input. + * @throws IllegalArgumentException if the input is longer than the maximum allowed length. */ public String encode(String input, final Languages.LanguageSet languageSet) { + if (input.length() > maxInputLength) { + throw new IllegalArgumentException("Input is greater than maxInputLength (" + maxInputLength + ")."); + } final Map<String, List<Rule>> rules = Rule.getInstanceMap(this.nameType, RuleType.RULES, languageSet); // rules common across many (all) languages final Map<String, List<Rule>> finalRules1 = Rule.getInstanceMap(this.nameType, this.ruleType, "common"); diff --git a/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderBuilderTest.java b/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderBuilderTest.java new file mode 100644 index 00000000..af6754f6 --- /dev/null +++ b/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderBuilderTest.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language.bm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.commons.codec.EncoderException; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link BeiderMorseEncoder.Builder} and {@link BeiderMorseEncoder#builder()}. + */ +class BeiderMorseEncoderBuilderTest { + + /** + * Tests that each call to {@code get()} on the same builder returns a distinct encoder instance. + */ + @Test + void testBuilderGetReturnsDifferentInstances() { + final BeiderMorseEncoder.Builder builder = BeiderMorseEncoder.builder(); + assertNotSame(builder.get(), builder.get()); + } + + /** + * Tests that the builder's {@code get()} method returns a non-null encoder with default settings. + */ + @Test + void testBuilderGetReturnsNonNull() { + assertNotNull(BeiderMorseEncoder.builder().get()); + } + + /** + * Tests that {@link BeiderMorseEncoder#builder()} returns a non-null builder. + */ + @Test + void testBuilderIsNotNull() { + assertNotNull(BeiderMorseEncoder.builder()); + } + + /** + * Tests that each call to {@link BeiderMorseEncoder#builder()} returns a distinct builder instance. + */ + @Test + void testBuilderReturnsDifferentInstances() { + assertNotSame(BeiderMorseEncoder.builder(), BeiderMorseEncoder.builder()); + } + + /** + * Tests that an encoder built with concat disabled produces the expected behavior. + */ + @Test + void testBuilderWithConcatDisabled() { + final PhoneticEngine engine = PhoneticEngine.builder().setConcat(false).get(); + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().setPhoneticEngine(engine).get(); + assertFalse(encoder.isConcat()); + } + + /** + * Tests that the builder with EXACT rule type produces a non-empty encoding for a known name. + */ + @Test + void testBuilderWithExactRuleType() throws EncoderException { + final PhoneticEngine engine = PhoneticEngine.builder().setRuleType(RuleType.EXACT).get(); + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().setPhoneticEngine(engine).get(); + assertEquals(RuleType.EXACT, encoder.getRuleType()); + final String result = encoder.encode("Cohen"); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + /** + * Tests that the default encoder has concat enabled. + */ + @Test + void testDefaultConcat() { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().get(); + assertTrue(encoder.isConcat()); + } + + /** + * Tests that the default encoder built with no configuration produces a non-empty encoding. + */ + @Test + void testDefaultEncoderEncodes() throws EncoderException { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().get(); + final String result = encoder.encode("Smith"); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + /** + * Tests that the default encoder has the default name type (GENERIC). + */ + @Test + void testDefaultNameType() { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().get(); + assertEquals(NameType.GENERIC, encoder.getNameType()); + } + + /** + * Tests that the default encoder has the default rule type (APPROX). + */ + @Test + void testDefaultRuleType() { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().get(); + assertEquals(RuleType.APPROX, encoder.getRuleType()); + } + + /** + * Tests that an encoder built via the builder encodes {@code null} to {@code null}. + */ + @Test + void testEncodeNullReturnsNull() throws EncoderException { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().get(); + assertNotNull(encoder); + // encode(null) should return null per BeiderMorseEncoder implementation. + assertEquals(null, encoder.encode((String) null)); + } + + /** + * Tests that passing {@code null} to {@link BeiderMorseEncoder.Builder#setPhoneticEngine(PhoneticEngine)} falls back to the default engine. + */ + @Test + void testSetPhoneticEngineNullFallsBackToDefault() { + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().setPhoneticEngine(null).get(); + assertNotNull(encoder); + assertEquals(NameType.GENERIC, encoder.getNameType()); + assertEquals(RuleType.APPROX, encoder.getRuleType()); + assertTrue(encoder.isConcat()); + } + + /** + * Tests that {@link BeiderMorseEncoder.Builder#setPhoneticEngine(PhoneticEngine)} supports chaining. + */ + @Test + void testSetPhoneticEngineReturnsBuilder() { + final PhoneticEngine engine = PhoneticEngine.builder().get(); + final BeiderMorseEncoder.Builder builder = BeiderMorseEncoder.builder(); + assertNotNull(builder.setPhoneticEngine(engine)); + } + + /** + * Tests building an encoder with a custom {@link PhoneticEngine} using the ASHKENAZI name type. + */ + @Test + void testSetPhoneticEngineWithAshkenazi() { + // @formatter:off + final PhoneticEngine engine = PhoneticEngine.builder() + .setNameType(NameType.ASHKENAZI) + .setRuleType(RuleType.EXACT) + .get(); + // @formatter:on + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().setPhoneticEngine(engine).get(); + assertEquals(NameType.ASHKENAZI, encoder.getNameType()); + assertEquals(RuleType.EXACT, encoder.getRuleType()); + } + + /** + * Tests building an encoder with a custom {@link PhoneticEngine} using the SEPHARDIC name type. + */ + @Test + void testSetPhoneticEngineWithSephardic() { + final PhoneticEngine engine = PhoneticEngine.builder().setNameType(NameType.SEPHARDIC).get(); + final BeiderMorseEncoder encoder = BeiderMorseEncoder.builder().setPhoneticEngine(engine).get(); + assertEquals(NameType.SEPHARDIC, encoder.getNameType()); + } + + /** + * Tests that two encoders built independently with identical configurations produce the same encoding. + */ + @Test + void testTwoDefaultBuildersProduceSameEncoding() throws EncoderException { + final BeiderMorseEncoder enc1 = BeiderMorseEncoder.builder().get(); + final BeiderMorseEncoder enc2 = BeiderMorseEncoder.builder().get(); + assertEquals(enc1.encode("Levy"), enc2.encode("Levy")); + } +} diff --git a/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderTest.java b/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderTest.java index e42bb27c..128a1d0a 100644 --- a/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderTest.java +++ b/src/test/java/org/apache/commons/codec/language/bm/BeiderMorseEncoderTest.java @@ -98,9 +98,12 @@ class BeiderMorseEncoderTest extends AbstractStringEncoderTest<StringEncoder> { } } - @Test - void testDQuoteRepeat() throws Exception { - assertEquals("(D|a|i|o)-(dD|da|di|do)", new BeiderMorseEncoder().encode(StringUtils.repeat("d'", 20000) + "aaa")); + @ParameterizedTest + @ValueSource(ints = { 20_000, 100_000, 200_000, 400_000 }) + void testDQuoteRepeatLarge(final int repeat) throws Exception { + final String source = StringUtils.repeat("d'", repeat) + "aaa"; + assertEquals("(D|a|i|o)-(dD|da|di|do)", + BeiderMorseEncoder.builder().setPhoneticEngine(PhoneticEngine.builder().setMaxInputLength(source.length()).get()).get().encode(source)); } @Test @@ -127,11 +130,12 @@ class BeiderMorseEncoderTest extends AbstractStringEncoderTest<StringEncoder> { @Disabled("For performance testing.") @ParameterizedTest - @ValueSource(ints = { 2000, 8000, 16000, 32000 }) - void testEncodeLarge(final int target) throws EncoderException { + @ValueSource(ints = { 2_000, 8_000, 16_000, 32_000 }) + void testEncodeLargePerf(final int target) throws EncoderException { final String[] units = { "a", "e", "i", "o", "u", "ai", "ei", "ou", "au", "ie", "tsch", "sch", "zh", "kh", "ye", "yo" }; final Random r = new Random(1); - final BeiderMorseEncoder enc = new BeiderMorseEncoder(); // default GENERIC/APPROX, maxPhonemes=20 + // default GENERIC/APPROX, maxPhonemes=20 + final BeiderMorseEncoder enc = new BeiderMorseEncoder(); final StringBuilder sb = new StringBuilder(); while (sb.length() < target) { sb.append(units[r.nextInt(units.length)]); // one long token, no spaces diff --git a/src/test/java/org/apache/commons/codec/language/bm/PhoneticEngineBuilderTest.java b/src/test/java/org/apache/commons/codec/language/bm/PhoneticEngineBuilderTest.java index 23cafb67..d4643b46 100644 --- a/src/test/java/org/apache/commons/codec/language/bm/PhoneticEngineBuilderTest.java +++ b/src/test/java/org/apache/commons/codec/language/bm/PhoneticEngineBuilderTest.java @@ -17,11 +17,13 @@ package org.apache.commons.codec.language.bm; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; import org.junit.jupiter.api.Test; @@ -248,4 +250,125 @@ class PhoneticEngineBuilderTest { final PhoneticEngine engine = PhoneticEngine.builder().setRuleType(RuleType.EXACT).get(); assertEquals(RuleType.EXACT, engine.getRuleType()); } + + /** + * Returns a string of {@code 'a'} characters with the given length, compatible with Java 8. + * + * @param length the desired length. + * @return a string of the specified length filled with {@code 'a'}. + */ + private static String repeat(final int length) { + final char[] chars = new char[length]; + java.util.Arrays.fill(chars, 'a'); + return new String(chars); + } + + /** + * Tests that the default maximum input length (666) allows input of exactly that length to be encoded without throwing. + */ + @Test + void testDefaultMaxInputLengthAllowsInputAtLimit() { + // Default max input length is 666 (Hubert Blaine Wolfeschlegelsteinhausenbergerdorff Sr.) + final PhoneticEngine engine = PhoneticEngine.builder().get(); + final String input = repeat(666); + assertDoesNotThrow(() -> engine.encode(input)); + } + + /** + * Tests that the default maximum input length (666) causes an {@link IllegalArgumentException} + * when input exceeds that limit. + */ + @Test + void testDefaultMaxInputLengthRejectsInputBeyondLimit() { + final PhoneticEngine engine = PhoneticEngine.builder().get(); + final String input = repeat(667); + assertThrows(IllegalArgumentException.class, () -> engine.encode(input)); + } + + /** + * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a custom value: + * input exactly at the limit is accepted. + */ + @Test + void testSetMaxInputLengthAllowsInputAtCustomLimit() { + final int customLimit = 10; + final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(customLimit).get(); + final String input = repeat(customLimit); + assertDoesNotThrow(() -> engine.encode(input)); + } + + /** + * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a custom value: + * input one character beyond the limit is rejected with {@link IllegalArgumentException}. + */ + @Test + void testSetMaxInputLengthRejectsInputBeyondCustomLimit() { + final int customLimit = 10; + final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(customLimit).get(); + final String input = repeat(customLimit + 1); + assertThrows(IllegalArgumentException.class, () -> engine.encode(input)); + } + + /** + * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a limit of 0: + * any non-empty input is rejected. + */ + @Test + void testSetMaxInputLengthZeroRejectsAnyInput() { + final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(0).get(); + assertThrows(IllegalArgumentException.class, () -> engine.encode("a")); + } + + /** + * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} with a limit of 0: + * null input bypasses the length check but causes a {@link NullPointerException} downstream. + */ + @Test + void testSetMaxInputLengthZeroNullInputThrowsNpe() { + final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(0).get(); + // null is not blocked by the length check, but encoding null causes NPE + assertThrows(NullPointerException.class, () -> engine.encode(null)); + } + + /** + * Tests {@link PhoneticEngine.Builder#setMaxInputLength(int)} that the setter returns + * the same builder instance to enable method chaining. + */ + @Test + void testSetMaxInputLengthReturnsBuilder() { + final PhoneticEngine.Builder builder = PhoneticEngine.builder(); + assertNotNull(builder.setMaxInputLength(100)); + } + + /** + * Tests that {@link PhoneticEngine.Builder#setMaxInputLength(int)} can be combined + * with other builder settings and still produces a valid engine. + */ + @Test + void testSetMaxInputLengthWithOtherSettings() { + // @formatter:off + final PhoneticEngine engine = PhoneticEngine.builder() + .setNameType(NameType.ASHKENAZI) + .setRuleType(RuleType.APPROX) + .setConcat(true) + .setMaxPhonemes(10) + .setMaxInputLength(5) + .get(); + // @formatter:on + assertNotNull(engine); + assertDoesNotThrow(() -> engine.encode("abcde")); + assertThrows(IllegalArgumentException.class, () -> engine.encode("abcdef")); + } + + /** + * Tests that {@link PhoneticEngine.Builder#setMaxInputLength(int)} with {@link Integer#MAX_VALUE} + * allows very long input. + */ + @Test + void testSetMaxInputLengthMaxValue() { + final PhoneticEngine engine = PhoneticEngine.builder().setMaxInputLength(Integer.MAX_VALUE).get(); + // A very long string should not trigger the length check + final String input = repeat(10_000); + assertDoesNotThrow(() -> engine.encode(input)); + } }
