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-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d325f69d Handle supplementary code points in WordUtils case methods 
(#1728)
8d325f69d is described below

commit 8d325f69d97a194659f054ba389420d3800cdd82
Author: alhuda <[email protected]>
AuthorDate: Fri Jun 26 00:42:52 2026 +0530

    Handle supplementary code points in WordUtils case methods (#1728)
    
    * handle supplementary code points in WordUtils case methods
    
    * Document null delimiters case in isDelimiter(int, char[])
    
    Signed-off-by: Alhuda Khan <[email protected]>
    
    ---------
    
    Signed-off-by: Alhuda Khan <[email protected]>
---
 .../org/apache/commons/lang3/text/WordUtils.java   | 57 ++++++++++++++++------
 .../apache/commons/lang3/text/WordUtilsTest.java   | 19 ++++++++
 2 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/text/WordUtils.java 
b/src/main/java/org/apache/commons/lang3/text/WordUtils.java
index 9c14ad609..81039bf69 100644
--- a/src/main/java/org/apache/commons/lang3/text/WordUtils.java
+++ b/src/main/java/org/apache/commons/lang3/text/WordUtils.java
@@ -99,14 +99,15 @@ public static String capitalize(final String str, final 
char... delimiters) {
         }
         final char[] buffer = str.toCharArray();
         boolean capitalizeNext = true;
-        for (int i = 0; i < buffer.length; i++) {
-            final char ch = buffer[i];
-            if (isDelimiter(ch, delimiters)) {
+        for (int i = 0; i < buffer.length;) {
+            final int codePoint = Character.codePointAt(buffer, i);
+            if (isDelimiter(codePoint, delimiters)) {
                 capitalizeNext = true;
             } else if (capitalizeNext) {
-                buffer[i] = Character.toTitleCase(ch);
+                Character.toChars(Character.toTitleCase(codePoint), buffer, i);
                 capitalizeNext = false;
             }
+            i += Character.charCount(codePoint);
         }
         return new String(buffer);
     }
@@ -297,6 +298,28 @@ private static boolean isDelimiter(final char ch, final 
char[] delimiters) {
         return delimiters == null ? Character.isWhitespace(ch) : 
ArrayUtils.contains(delimiters, ch);
     }
 
+    /**
+     * Tests if the code point is a delimiter.
+     *
+     * <p>A {@code null} {@code delimiters} array treats any whitespace code 
point, as defined by
+     * {@link Character#isWhitespace(int)}, as a delimiter.</p>
+     *
+     * @param codePoint  the code point to check.
+     * @param delimiters  the delimiters, {@code null} matches whitespace.
+     * @return true if it is a delimiter.
+     */
+    private static boolean isDelimiter(final int codePoint, final char[] 
delimiters) {
+        if (delimiters == null) {
+            return Character.isWhitespace(codePoint);
+        }
+        for (final char delimiter : delimiters) {
+            if (codePoint == delimiter) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Swaps the case of a String using a word based algorithm.
      *
@@ -327,21 +350,22 @@ public static String swapCase(final String str) {
 
         boolean whitespace = true;
 
-        for (int i = 0; i < buffer.length; i++) {
-            final char ch = buffer[i];
-            if (Character.isUpperCase(ch) || Character.isTitleCase(ch)) {
-                buffer[i] = Character.toLowerCase(ch);
+        for (int i = 0; i < buffer.length;) {
+            final int codePoint = Character.codePointAt(buffer, i);
+            if (Character.isUpperCase(codePoint) || 
Character.isTitleCase(codePoint)) {
+                Character.toChars(Character.toLowerCase(codePoint), buffer, i);
                 whitespace = false;
-            } else if (Character.isLowerCase(ch)) {
+            } else if (Character.isLowerCase(codePoint)) {
                 if (whitespace) {
-                    buffer[i] = Character.toTitleCase(ch);
+                    Character.toChars(Character.toTitleCase(codePoint), 
buffer, i);
                     whitespace = false;
                 } else {
-                    buffer[i] = Character.toUpperCase(ch);
+                    Character.toChars(Character.toUpperCase(codePoint), 
buffer, i);
                 }
             } else {
-                whitespace = Character.isWhitespace(ch);
+                whitespace = Character.isWhitespace(codePoint);
             }
+            i += Character.charCount(codePoint);
         }
         return new String(buffer);
     }
@@ -399,14 +423,15 @@ public static String uncapitalize(final String str, final 
char... delimiters) {
         }
         final char[] buffer = str.toCharArray();
         boolean uncapitalizeNext = true;
-        for (int i = 0; i < buffer.length; i++) {
-            final char ch = buffer[i];
-            if (isDelimiter(ch, delimiters)) {
+        for (int i = 0; i < buffer.length;) {
+            final int codePoint = Character.codePointAt(buffer, i);
+            if (isDelimiter(codePoint, delimiters)) {
                 uncapitalizeNext = true;
             } else if (uncapitalizeNext) {
-                buffer[i] = Character.toLowerCase(ch);
+                Character.toChars(Character.toLowerCase(codePoint), buffer, i);
                 uncapitalizeNext = false;
             }
+            i += Character.charCount(codePoint);
         }
         return new String(buffer);
     }
diff --git a/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java
index 1f43e783f..ab34e7ea8 100644
--- a/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java
@@ -285,6 +285,25 @@ void testInitials_SupplementaryCodePoint() {
         assertEquals("B" + emoji + "L", WordUtils.initials("Ben." + emoji + 
"mile.Lee", '.'));
     }
 
+    @Test
+    void testCase_SupplementaryCodePoint() {
+        // Deseret long-i: capital U+10400, small U+10428 (each a surrogate 
pair)
+        final String cap = new String(Character.toChars(0x10400));
+        final String small = new String(Character.toChars(0x10428));
+
+        assertEquals(cap + "bc", WordUtils.capitalize(small + "bc"));
+        assertEquals("Ben " + cap + "ee", WordUtils.capitalize("ben " + small 
+ "ee"));
+        assertEquals("Ben." + cap + "ee", WordUtils.capitalize("ben." + small 
+ "ee", '.'));
+        assertEquals(cap + "bc", WordUtils.capitalizeFully(cap + "BC"));
+
+        assertEquals(small + "BC", WordUtils.uncapitalize(cap + "BC"));
+        assertEquals("a." + small + "BC", WordUtils.uncapitalize("a." + cap + 
"BC", '.'));
+
+        assertEquals(small, WordUtils.swapCase(cap));
+        assertEquals(cap, WordUtils.swapCase(small));
+        assertEquals("A" + small, WordUtils.swapCase("a" + cap));
+    }
+
     @Test
     void testLANG1292() {
         // Prior to fix, this was throwing StringIndexOutOfBoundsException

Reply via email to