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));
+    }
 }

Reply via email to