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