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
commit 0cd35067e9e01462bda2fb371d9991ca34d7e7dd Author: Gary Gregory <[email protected]> AuthorDate: Sun Jun 21 11:35:31 2026 +0000 Sort members. --- .../java/org/apache/commons/lang3/StringUtils.java | 24 ++++---- .../org/apache/commons/lang3/ArrayUtilsTest.java | 36 ++++++------ .../commons/lang3/StringUtilsAbbreviateTest.java | 66 +++++++++++----------- .../apache/commons/lang3/text/WordUtilsTest.java | 16 +++--- .../lang3/time/DurationFormatUtilsTest.java | 64 ++++++++++----------- 5 files changed, 103 insertions(+), 103 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 3853771b5..d96ba2c91 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -393,18 +393,6 @@ public static String abbreviate(final String str, String abbrevMarker, final int return abbrevMarker + abbreviate(str.substring(from), abbrevMarker, maxWidth - abbrevMarkerLength); } - /** - * Tests whether a {@link String#substring} boundary at {@code index} would fall between the two halves of a surrogate pair, that is the char before - * {@code index} is a high surrogate and the char at {@code index} is its low surrogate. Slicing there leaves a lone surrogate in the result. - * - * @param str the String being sliced. - * @param index a candidate substring boundary, in {@code char} units. - * @return whether slicing at {@code index} would split a surrogate pair. - */ - private static boolean splitsSurrogatePair(final String str, final int index) { - return index > 0 && index < str.length() && Character.isHighSurrogate(str.charAt(index - 1)) && Character.isLowSurrogate(str.charAt(index)); - } - /** * Abbreviates a String to the length passed, replacing the middle characters with the supplied replacement String. * @@ -7637,6 +7625,18 @@ public static String[] splitPreserveAllTokens(final String str, final String sep return splitWorker(str, separatorChars, max, true); } + /** + * Tests whether a {@link String#substring} boundary at {@code index} would fall between the two halves of a surrogate pair, that is the char before + * {@code index} is a high surrogate and the char at {@code index} is its low surrogate. Slicing there leaves a lone surrogate in the result. + * + * @param str the String being sliced. + * @param index a candidate substring boundary, in {@code char} units. + * @return whether slicing at {@code index} would split a surrogate pair. + */ + private static boolean splitsSurrogatePair(final String str, final int index) { + return index > 0 && index < str.length() && Character.isHighSurrogate(str.charAt(index - 1)) && Character.isLowSurrogate(str.charAt(index)); + } + /** * Performs the logic for the {@code split} and {@code splitPreserveAllTokens} methods that do not return a maximum array length. * diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java index b9faef8c4..2f61f9eef 100644 --- a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java @@ -1824,16 +1824,6 @@ void testLastIndexOfDouble() { assertEquals(-1, ArrayUtils.lastIndexOf(array, 99)); } - @Test - void testLastIndexOfDoubleNaN() { - final double[] array = { Double.NaN, Double.NEGATIVE_INFINITY, Double.NaN, Double.POSITIVE_INFINITY }; - assertEquals(2, ArrayUtils.lastIndexOf(array, Double.NaN)); - assertEquals(2, ArrayUtils.lastIndexOf(array, Double.NaN, (double) 0)); - assertEquals(0, ArrayUtils.lastIndexOf(array, Double.NaN, 1)); - assertEquals(0, ArrayUtils.lastIndexOf(array, Double.NaN, 1, (double) 0)); - assertEquals(-1, ArrayUtils.lastIndexOf(array, Double.NaN, -1)); - } - @Test void testLastIndexOfDoubleInfinity() { final double[] array = { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }; @@ -1845,6 +1835,16 @@ void testLastIndexOfDoubleInfinity() { assertEquals(1, ArrayUtils.lastIndexOf(array, Double.NEGATIVE_INFINITY, 2, (double) 0)); } + @Test + void testLastIndexOfDoubleNaN() { + final double[] array = { Double.NaN, Double.NEGATIVE_INFINITY, Double.NaN, Double.POSITIVE_INFINITY }; + assertEquals(2, ArrayUtils.lastIndexOf(array, Double.NaN)); + assertEquals(2, ArrayUtils.lastIndexOf(array, Double.NaN, (double) 0)); + assertEquals(0, ArrayUtils.lastIndexOf(array, Double.NaN, 1)); + assertEquals(0, ArrayUtils.lastIndexOf(array, Double.NaN, 1, (double) 0)); + assertEquals(-1, ArrayUtils.lastIndexOf(array, Double.NaN, -1)); + } + @Test void testLastIndexOfDoubleTolerance() { double[] array = null; @@ -1905,14 +1905,6 @@ void testLastIndexOfFloat() { assertEquals(-1, ArrayUtils.lastIndexOf(array, 99)); } - @Test - void testLastIndexOfFloatNaN() { - final float[] array = { Float.NaN, Float.NEGATIVE_INFINITY, Float.NaN, Float.POSITIVE_INFINITY }; - assertEquals(2, ArrayUtils.lastIndexOf(array, Float.NaN)); - assertEquals(0, ArrayUtils.lastIndexOf(array, Float.NaN, 1)); - assertEquals(-1, ArrayUtils.lastIndexOf(array, Float.NaN, -1)); - } - @Test void testLastIndexOfFloatInfinity() { final float[] array = { Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY }; @@ -1922,6 +1914,14 @@ void testLastIndexOfFloatInfinity() { assertEquals(1, ArrayUtils.lastIndexOf(array, Float.NEGATIVE_INFINITY, 2)); } + @Test + void testLastIndexOfFloatNaN() { + final float[] array = { Float.NaN, Float.NEGATIVE_INFINITY, Float.NaN, Float.POSITIVE_INFINITY }; + assertEquals(2, ArrayUtils.lastIndexOf(array, Float.NaN)); + assertEquals(0, ArrayUtils.lastIndexOf(array, Float.NaN, 1)); + assertEquals(-1, ArrayUtils.lastIndexOf(array, Float.NaN, -1)); + } + @Test void testLastIndexOfFloatWithStartIndex() { float[] array = null; diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsAbbreviateTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsAbbreviateTest.java index 670e5fb00..771cf1955 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsAbbreviateTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsAbbreviateTest.java @@ -171,39 +171,6 @@ void testAbbreviate_StringStringIntInt() { assertEquals("....fg", StringUtils.abbreviate("abcdefg", "....", 5, 6)); } - @Test - void testAbbreviateSurrogatePair() { - // U+1F600 GRINNING FACE is a single supplementary code point stored as a surrogate pair - final String grin = "😀"; - // the head cut backs off the pair so the marker is never preceded by a lone high surrogate - assertEquals("...", StringUtils.abbreviate(grin + "abcdef", 4)); - assertEquals(grin + "...", StringUtils.abbreviate(grin + "abcdef", 5)); - // a trailing supplementary code point is kept whole rather than sliced into a lone low surrogate - assertEquals("..." + grin, StringUtils.abbreviate("abcdef" + grin, 6, 5)); - // an input that is only the pair is kept whole or dropped, never split into a lone surrogate - assertEquals(grin, StringUtils.abbreviate(grin, 4)); - assertEquals(grin, StringUtils.abbreviate(grin, 0, 5)); - assertEquals(grin, StringUtils.abbreviate(grin, 1, 4)); - assertEquals(grin, StringUtils.abbreviate(grin, "x", 0, 2)); - assertEquals(grin, StringUtils.abbreviate(grin, "x", 1, 2)); - assertEquals(grin, StringUtils.abbreviate(grin, "", 0, 2)); - assertEquals("", StringUtils.abbreviate(grin, "", 0, 1)); - // results stay within maxWidth and never contain an unpaired surrogate - for (int width = 4; width <= 8; width++) { - final String result = StringUtils.abbreviate("a" + grin + "b" + grin + "cd", width); - assertTrue(result.length() <= width, () -> "result longer than maxWidth: " + result); - for (int i = 0; i < result.length(); i++) { - final char ch = result.charAt(i); - if (Character.isHighSurrogate(ch)) { - assertTrue(i + 1 < result.length() && Character.isLowSurrogate(result.charAt(i + 1)), "lone high surrogate in: " + result); - i++; // skip the paired low surrogate - } else { - assertFalse(Character.isLowSurrogate(ch), "lone low surrogate in: " + result); - } - } - } - } - // Fixed LANG-1463 @Test void testAbbreviateMarkerWithEmptyString() { @@ -240,6 +207,39 @@ void testAbbreviateMiddle() { assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5)); } + @Test + void testAbbreviateSurrogatePair() { + // U+1F600 GRINNING FACE is a single supplementary code point stored as a surrogate pair + final String grin = "😀"; + // the head cut backs off the pair so the marker is never preceded by a lone high surrogate + assertEquals("...", StringUtils.abbreviate(grin + "abcdef", 4)); + assertEquals(grin + "...", StringUtils.abbreviate(grin + "abcdef", 5)); + // a trailing supplementary code point is kept whole rather than sliced into a lone low surrogate + assertEquals("..." + grin, StringUtils.abbreviate("abcdef" + grin, 6, 5)); + // an input that is only the pair is kept whole or dropped, never split into a lone surrogate + assertEquals(grin, StringUtils.abbreviate(grin, 4)); + assertEquals(grin, StringUtils.abbreviate(grin, 0, 5)); + assertEquals(grin, StringUtils.abbreviate(grin, 1, 4)); + assertEquals(grin, StringUtils.abbreviate(grin, "x", 0, 2)); + assertEquals(grin, StringUtils.abbreviate(grin, "x", 1, 2)); + assertEquals(grin, StringUtils.abbreviate(grin, "", 0, 2)); + assertEquals("", StringUtils.abbreviate(grin, "", 0, 1)); + // results stay within maxWidth and never contain an unpaired surrogate + for (int width = 4; width <= 8; width++) { + final String result = StringUtils.abbreviate("a" + grin + "b" + grin + "cd", width); + assertTrue(result.length() <= width, () -> "result longer than maxWidth: " + result); + for (int i = 0; i < result.length(); i++) { + final char ch = result.charAt(i); + if (Character.isHighSurrogate(ch)) { + assertTrue(i + 1 < result.length() && Character.isLowSurrogate(result.charAt(i + 1)), "lone high surrogate in: " + result); + i++; // skip the paired low surrogate + } else { + assertFalse(Character.isLowSurrogate(ch), "lone low surrogate in: " + result); + } + } + } + } + /** * Tests <a href="LANG-1770">https://issues.apache.org/jira/projects/LANG/issues/LANG-1770</a>. */ 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 305f01c2c..8554d05db 100644 --- a/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/text/WordUtilsTest.java @@ -185,14 +185,6 @@ void testInitials_String() { assertEquals("iah1", WordUtils.initials("i am here 123")); } - @Test - void testInitials_SupplementaryCodePoint() { - final String emoji = new String(Character.toChars(0x1F600)); - assertEquals("B" + emoji + "L", WordUtils.initials("Ben " + emoji + "mile Lee")); - assertEquals(emoji, WordUtils.initials(emoji + "abc")); - assertEquals("B" + emoji + "L", WordUtils.initials("Ben." + emoji + "mile.Lee", '.')); - } - @Test void testInitials_String_charArray() { char[] array = null; @@ -277,6 +269,14 @@ void testInitials_String_charArray() { assertEquals("i2", WordUtils.initials("i am here 123", array)); } + @Test + void testInitials_SupplementaryCodePoint() { + final String emoji = new String(Character.toChars(0x1F600)); + assertEquals("B" + emoji + "L", WordUtils.initials("Ben " + emoji + "mile Lee")); + assertEquals(emoji, WordUtils.initials(emoji + "abc")); + assertEquals("B" + emoji + "L", WordUtils.initials("Ben." + emoji + "mile.Lee", '.')); + } + @Test void testLANG1292() { // Prior to fix, this was throwing StringIndexOutOfBoundsException diff --git a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java index 1fe9821ba..b96bdb741 100644 --- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java @@ -67,6 +67,16 @@ private void assertEqualDuration(final String message, final String expected, fi } } + private void assertFormatPeriodOneMilli(final long startMillis, final long endMillis) { + final TimeZone gmt = TimeZones.getTimeZone("GMT"); + assertEquals("1", DurationFormatUtils.formatPeriod(startMillis, endMillis, "S", true, gmt)); + assertEquals("0/0/0/0/0/0.001", + DurationFormatUtils.formatPeriod(startMillis, endMillis, "y/M/d/H/m/s.S", true, gmt)); + // formatPeriod must agree with formatDuration over the full range, not just spans near the epoch. + assertEquals(DurationFormatUtils.formatDuration(endMillis - startMillis, "S"), + DurationFormatUtils.formatPeriod(startMillis, endMillis, "S", true, gmt)); + } + private void bruteForce(final int year, final int month, final int day, final String format, final int calendarType) { final String msg = year + "-" + month + "-" + day + " to "; final Calendar c = Calendar.getInstance(); @@ -478,38 +488,6 @@ void testFormatPeriodeStartGreaterEnd() { assertIllegalArgumentException(() -> DurationFormatUtils.formatPeriod(5000, 2500, "yy/MM")); } - @Test - void testFormatPeriodLargeFunnelledValue() { - final TimeZone gmt = TimeZones.getTimeZone("GMT"); - // ~69 years, chosen so the seconds count exceeds Integer.MAX_VALUE once days/hours/minutes are - // funnelled into the single requested field. - final long endMillis = 2_175_984_000_000L; - assertEquals("2175984000", DurationFormatUtils.formatPeriod(0, endMillis, "s", true, gmt)); - assertEquals("2175984000000", DurationFormatUtils.formatPeriod(0, endMillis, "S", true, gmt)); - // formatPeriod must agree with formatDuration, which reduces the same span in long arithmetic. - assertEquals(DurationFormatUtils.formatDuration(endMillis, "s"), - DurationFormatUtils.formatPeriod(0, endMillis, "s", true, gmt)); - } - - @Test - void testFormatPeriodLongRangeBounds() { - // A one-millisecond span sitting at the extremes of the long input range must still reduce - // correctly, confirming formatPeriod handles the whole range of millisecond inputs. - assertFormatPeriodOneMilli(Long.MAX_VALUE - 1, Long.MAX_VALUE); - assertFormatPeriodOneMilli(Long.MIN_VALUE, Long.MIN_VALUE + 1); - assertFormatPeriodOneMilli((long) Integer.MIN_VALUE - 1, Integer.MIN_VALUE); - } - - private void assertFormatPeriodOneMilli(final long startMillis, final long endMillis) { - final TimeZone gmt = TimeZones.getTimeZone("GMT"); - assertEquals("1", DurationFormatUtils.formatPeriod(startMillis, endMillis, "S", true, gmt)); - assertEquals("0/0/0/0/0/0.001", - DurationFormatUtils.formatPeriod(startMillis, endMillis, "y/M/d/H/m/s.S", true, gmt)); - // formatPeriod must agree with formatDuration over the full range, not just spans near the epoch. - assertEquals(DurationFormatUtils.formatDuration(endMillis - startMillis, "S"), - DurationFormatUtils.formatPeriod(startMillis, endMillis, "S", true, gmt)); - } - @SuppressWarnings("deprecation") @Test void testFormatPeriodISO() { @@ -581,6 +559,28 @@ void testFormatPeriodISOStartGreaterEnd() { assertIllegalArgumentException(() -> DurationFormatUtils.formatPeriodISO(5000, 2000)); } + @Test + void testFormatPeriodLargeFunnelledValue() { + final TimeZone gmt = TimeZones.getTimeZone("GMT"); + // ~69 years, chosen so the seconds count exceeds Integer.MAX_VALUE once days/hours/minutes are + // funnelled into the single requested field. + final long endMillis = 2_175_984_000_000L; + assertEquals("2175984000", DurationFormatUtils.formatPeriod(0, endMillis, "s", true, gmt)); + assertEquals("2175984000000", DurationFormatUtils.formatPeriod(0, endMillis, "S", true, gmt)); + // formatPeriod must agree with formatDuration, which reduces the same span in long arithmetic. + assertEquals(DurationFormatUtils.formatDuration(endMillis, "s"), + DurationFormatUtils.formatPeriod(0, endMillis, "s", true, gmt)); + } + + @Test + void testFormatPeriodLongRangeBounds() { + // A one-millisecond span sitting at the extremes of the long input range must still reduce + // correctly, confirming formatPeriod handles the whole range of millisecond inputs. + assertFormatPeriodOneMilli(Long.MAX_VALUE - 1, Long.MAX_VALUE); + assertFormatPeriodOneMilli(Long.MIN_VALUE, Long.MIN_VALUE + 1); + assertFormatPeriodOneMilli((long) Integer.MIN_VALUE - 1, Integer.MIN_VALUE); + } + /** * Takes 8 seconds to run. */
