Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/main/java/org/apache/commons/text/WordUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,21 @@ public static String abbreviate(final String str, int lower, int upper, final St
final StringBuilder result = new StringBuilder();
final int index = Strings.CS.indexOf(str, " ", lower);
if (index == -1) {
result.append(str, 0, upper);
int end = upper;
if (splitsSurrogatePair(str, end)) {
end--;
}
result.append(str, 0, end);
// only if abbreviation has occurred do we append the appendToEnd value
if (upper != str.length()) {
result.append(StringUtils.defaultString(appendToEnd));
}
} else {
result.append(str, 0, Math.min(index, upper));
int end = Math.min(index, upper);
if (splitsSurrogatePair(str, end)) {
end--;
}
result.append(str, 0, end);
result.append(StringUtils.defaultString(appendToEnd));
}
return result.toString();
Expand Down Expand Up @@ -426,6 +434,17 @@ public static boolean isDelimiter(final int codePoint, final char[] delimiters)
return false;
}

/**
* Tests whether the given index splits a surrogate pair, that is, whether it falls between a high surrogate and its trailing low surrogate.
*
* @param str The String to check.
* @param index The index to test.
* @return Whether cutting {@code str} 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));
}

/**
* Swaps the case of a String using a word based algorithm.
* <ul>
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/org/apache/commons/text/WordUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ void testAbbreviateForUpperLimitAndAppendedString() {
assertEquals("0123456789", WordUtils.abbreviate("0123456789", 0, -1, ""));
}

@Test
void testAbbreviateSurrogatePairs() {
// an upper limit landing inside a surrogate pair must not leave a lone surrogate behind
assertEquals("\uD83D\uDE00", WordUtils.abbreviate("\uD83D\uDE00\uD83D\uDE00\uD83D\uDE00", 0, 3, ""));
assertEquals("\uD83D\uDE00-", WordUtils.abbreviate("\uD83D\uDE00\uD83D\uDE00\uD83D\uDE00", 0, 3, "-"));
// the same applies when the abbreviation stops short of a space
assertEquals("\uD83D\uDE00-", WordUtils.abbreviate("\uD83D\uDE00\uD83D\uDE00 0", 0, 3, "-"));
// an upper limit falling between two pairs is already whole and must be left alone
assertEquals("\uD83D\uDE00\uD83D\uDE00", WordUtils.abbreviate("\uD83D\uDE00\uD83D\uDE00\uD83D\uDE00", 0, 4, ""));
}

@Test
void testAbbreviateUpperLessThanLowerValues() {
assertThrows(IllegalArgumentException.class, () -> WordUtils.abbreviate("0123456789", 5, 2, ""));
Expand Down
Loading