LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter (closes #195)

This commit is contained in:
Bruno P. Kinoshita 2016-10-13 22:55:17 +13:00 committed by pascalschumacher
parent a40b2a907a
commit c37a911d3a
3 changed files with 196 additions and 25 deletions

View File

@ -53,6 +53,7 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="LANG-1278" type="fix" dev="pschumacher" due-to="Duke Yin">BooleanUtils javadoc issues</action> <action issue="LANG-1278" type="fix" dev="pschumacher" due-to="Duke Yin">BooleanUtils javadoc issues</action>
<action issue="LANG-1070" type="fix" dev="pschumacher" due-to="Paul Pogonyshev">ArrayUtils#add confusing example in javadoc</action> <action issue="LANG-1070" type="fix" dev="pschumacher" due-to="Paul Pogonyshev">ArrayUtils#add confusing example in javadoc</action>
<action issue="LANG-1271" type="fix" dev="pschumacher" due-to="Pierre Templier">StringUtils#isAnyEmpty and #isAnyBlank should return false for an empty array</action> <action issue="LANG-1271" type="fix" dev="pschumacher" due-to="Pierre Templier">StringUtils#isAnyEmpty and #isAnyBlank should return false for an empty array</action>
<action issue="LANG-1160" type="add" dev="kinow">StringUtils#abbreviate should support 'custom ellipses' parameter</action>
<action issue="LANG-1270" type="add" dev="pschumacher" due-to="Pierre Templier">Add StringUtils#isAnyNotEmpty and #isAnyNotBlank</action> <action issue="LANG-1270" type="add" dev="pschumacher" due-to="Pierre Templier">Add StringUtils#isAnyNotEmpty and #isAnyNotBlank</action>
<action issue="LANG-1277" type="update" dev="pschumacher" due-to="yufcuy">StringUtils#getLevenshteinDistance reduce memory consumption</action> <action issue="LANG-1277" type="update" dev="pschumacher" due-to="yufcuy">StringUtils#getLevenshteinDistance reduce memory consumption</action>
<action issue="LANG-1279" type="update" dev="ggregory">Update Java requirement from Java 6 to 7.</action> <action issue="LANG-1279" type="update" dev="ggregory">Update Java requirement from Java 6 to 7.</action>

View File

@ -79,7 +79,7 @@
* <li><b>Reverse/ReverseDelimited</b> * <li><b>Reverse/ReverseDelimited</b>
* - reverses a String</li> * - reverses a String</li>
* <li><b>Abbreviate</b> * <li><b>Abbreviate</b>
* - abbreviates a string using ellipsis</li> * - abbreviates a string using ellipsis or another given String</li>
* <li><b>Difference</b> * <li><b>Difference</b>
* - compares Strings and reports on their differences</li> * - compares Strings and reports on their differences</li>
* <li><b>LevenshteinDistance</b> * <li><b>LevenshteinDistance</b>
@ -7377,7 +7377,8 @@ public static String reverseDelimited(final String str, final char separatorChar
* @since 2.0 * @since 2.0
*/ */
public static String abbreviate(final String str, final int maxWidth) { public static String abbreviate(final String str, final int maxWidth) {
return abbreviate(str, 0, maxWidth); final String defaultAbbrevMarker = "...";
return abbreviate(str, defaultAbbrevMarker, 0, maxWidth);
} }
/** /**
@ -7416,11 +7417,98 @@ public static String abbreviate(final String str, final int maxWidth) {
* @since 2.0 * @since 2.0
*/ */
public static String abbreviate(final String str, int offset, final int maxWidth) { public static String abbreviate(final String str, int offset, final int maxWidth) {
if (str == null) { final String defaultAbbrevMarker = "...";
return null; return abbreviate(str, defaultAbbrevMarker, offset, maxWidth);
}
/**
* <p>Abbreviates a String using another given String as replacement marker. This will turn
* "Now is the time for all good men" into "Now is the time for..." if "..." was defined
* as the replacement marker.</p>
*
* <p>Specifically:</p>
* <ul>
* <li>If the number of characters in {@code str} is less than or equal to
* {@code maxWidth}, return {@code str}.</li>
* <li>Else abbreviate it to {@code (substring(str, 0, max-abbrevMarker.length) + abbrevMarker)}.</li>
* <li>If {@code maxWidth} is less than {@code abbrevMarker.length + 1}, throw an
* {@code IllegalArgumentException}.</li>
* <li>In no case will it return a String of length greater than
* {@code maxWidth}.</li>
* </ul>
*
* <pre>
* StringUtils.abbreviate(null, "...", *) = null
* StringUtils.abbreviate("abcdefg", null, *) = "abcdefg"
* StringUtils.abbreviate("", "...", 4) = ""
* StringUtils.abbreviate("abcdefg", ".", 5) = "abcd."
* StringUtils.abbreviate("abcdefg", ".", 7) = "abcdefg"
* StringUtils.abbreviate("abcdefg", ".", 8) = "abcdefg"
* StringUtils.abbreviate("abcdefg", "..", 4) = "ab.."
* StringUtils.abbreviate("abcdefg", "..", 3) = "a.."
* StringUtils.abbreviate("abcdefg", "..", 2) = IllegalArgumentException
* StringUtils.abbreviate("abcdefg", "...", 3) = IllegalArgumentException
* </pre>
*
* @param str the String to check, may be null
* @param abbrevMarker the String used as replacement marker
* @param maxWidth maximum length of result String, must be at least {@code abbrevMarker.length + 1}
* @return abbreviated String, {@code null} if null String input
* @throws IllegalArgumentException if the width is too small
* @since 3.5
*/
public static String abbreviate(final String str, final String abbrevMarker, final int maxWidth) {
return abbreviate(str, abbrevMarker, 0, maxWidth);
}
/**
* <p>Abbreviates a String using a given replacement marker. This will turn
* "Now is the time for all good men" into "...is the time for..." if "..." was defined
* as the replacement marker.</p>
*
* <p>Works like {@code abbreviate(String, String, int)}, but allows you to specify
* a "left edge" offset. Note that this left edge is not necessarily going to
* be the leftmost character in the result, or the first character following the
* replacement marker, but it will appear somewhere in the result.
*
* <p>In no case will it return a String of length greater than {@code maxWidth}.</p>
*
* <pre>
* StringUtils.abbreviate(null, null, *, *) = null
* StringUtils.abbreviate("abcdefghijklmno", null, *, *) = "abcdefghijklmno"
* StringUtils.abbreviate("", "...", 0, 4) = ""
* StringUtils.abbreviate("abcdefghijklmno", "---", -1, 10) = "abcdefg---"
* StringUtils.abbreviate("abcdefghijklmno", ",", 0, 10) = "abcdefghi,"
* StringUtils.abbreviate("abcdefghijklmno", ",", 1, 10) = "abcdefghi,"
* StringUtils.abbreviate("abcdefghijklmno", ",", 2, 10) = "abcdefghi,"
* StringUtils.abbreviate("abcdefghijklmno", "::", 4, 10) = "::efghij::"
* StringUtils.abbreviate("abcdefghijklmno", "...", 6, 10) = "...ghij..."
* StringUtils.abbreviate("abcdefghijklmno", "*", 9, 10) = "*ghijklmno"
* StringUtils.abbreviate("abcdefghijklmno", "'", 10, 10) = "'ghijklmno"
* StringUtils.abbreviate("abcdefghijklmno", "!", 12, 10) = "!ghijklmno"
* StringUtils.abbreviate("abcdefghij", "abra", 0, 4) = IllegalArgumentException
* StringUtils.abbreviate("abcdefghij", "...", 5, 6) = IllegalArgumentException
* </pre>
*
* @param str the String to check, may be null
* @param abbrevMarker the String used as replacement marker
* @param offset left edge of source String
* @param maxWidth maximum length of result String, must be at least 4
* @return abbreviated String, {@code null} if null String input
* @throws IllegalArgumentException if the width is too small
* @since 3.5
*/
public static String abbreviate(final String str, final String abbrevMarker, int offset, final int maxWidth) {
if (isEmpty(str) || isEmpty(abbrevMarker)) {
return str;
} }
if (maxWidth < 4) {
throw new IllegalArgumentException("Minimum abbreviation width is 4"); final int abbrevMarkerLength = abbrevMarker.length();
final int minAbbrevWidth = abbrevMarkerLength + 1;
final int minAbbrevWidthOffset = abbrevMarkerLength + abbrevMarkerLength + 1;
if (maxWidth < minAbbrevWidth) {
throw new IllegalArgumentException(String.format("Minimum abbreviation width is %d", minAbbrevWidth));
} }
if (str.length() <= maxWidth) { if (str.length() <= maxWidth) {
return str; return str;
@ -7428,20 +7516,19 @@ public static String abbreviate(final String str, int offset, final int maxWidth
if (offset > str.length()) { if (offset > str.length()) {
offset = str.length(); offset = str.length();
} }
if (str.length() - offset < maxWidth - 3) { if (str.length() - offset < maxWidth - abbrevMarkerLength) {
offset = str.length() - (maxWidth - 3); offset = str.length() - (maxWidth - abbrevMarkerLength);
} }
final String abrevMarker = "..."; if (offset <= abbrevMarkerLength+1) {
if (offset <= 4) { return str.substring(0, maxWidth - abbrevMarkerLength) + abbrevMarker;
return str.substring(0, maxWidth - 3) + abrevMarker;
} }
if (maxWidth < 7) { if (maxWidth < minAbbrevWidthOffset) {
throw new IllegalArgumentException("Minimum abbreviation width with offset is 7"); throw new IllegalArgumentException(String.format("Minimum abbreviation width with offset is %d", minAbbrevWidthOffset));
} }
if (offset + maxWidth - 3 < str.length()) { if (offset + maxWidth - abbrevMarkerLength < str.length()) {
return abrevMarker + abbreviate(str.substring(offset), maxWidth - 3); return abbrevMarker + abbreviate(str.substring(offset), abbrevMarker, maxWidth - abbrevMarkerLength);
} }
return abrevMarker + str.substring(str.length() - (maxWidth - 3)); return abbrevMarker + str.substring(str.length() - (maxWidth - abbrevMarkerLength));
} }
/** /**

View File

@ -1933,10 +1933,37 @@ public void testAbbreviate_StringInt() {
assertEquals("a...", StringUtils.abbreviate("abcdefg", 4)); assertEquals("a...", StringUtils.abbreviate("abcdefg", 4));
assertEquals("", StringUtils.abbreviate("", 4)); assertEquals("", StringUtils.abbreviate("", 4));
try {
StringUtils.abbreviate("abc", 3);
fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException expected) {
// empty
}
}
@Test
public void testAbbreviate_StringStringInt() {
assertNull(StringUtils.abbreviate(null, null, 10));
assertNull(StringUtils.abbreviate(null, "...", 10));
assertEquals("paranaguacu", StringUtils.abbreviate("paranaguacu", null, 10));
assertEquals("", StringUtils.abbreviate("", "...", 2));
assertEquals("wai**", StringUtils.abbreviate("waiheke", "**", 5));
assertEquals("And af,,,,", StringUtils.abbreviate("And after a long time, he finally met his son.", ",,,,", 10));
final String raspberry = "raspberry peach";
assertEquals("raspberry pe..", StringUtils.abbreviate(raspberry, "..", 14));
assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", "---*---", 15));
assertEquals("raspberry peach", StringUtils.abbreviate("raspberry peach", ".", 16));
assertEquals("abc()(", StringUtils.abbreviate("abcdefg", "()(", 6));
assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", ";", 7));
assertEquals("abcdefg", StringUtils.abbreviate("abcdefg", "_-", 8));
assertEquals("abc.", StringUtils.abbreviate("abcdefg", ".", 4));
assertEquals("", StringUtils.abbreviate("", 4));
try { try {
@SuppressWarnings("unused") @SuppressWarnings("unused")
final final
String res = StringUtils.abbreviate("abc", 3); String res = StringUtils.abbreviate("abcdefghij", "...", 3);
fail("StringUtils.abbreviate expecting IllegalArgumentException"); fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException ex) { } catch (final IllegalArgumentException ex) {
// empty // empty
@ -1950,19 +1977,15 @@ public void testAbbreviate_StringIntInt() {
assertEquals("", StringUtils.abbreviate("", 2, 10)); assertEquals("", StringUtils.abbreviate("", 2, 10));
try { try {
@SuppressWarnings("unused") StringUtils.abbreviate("abcdefghij", 0, 3);
final
String res = StringUtils.abbreviate("abcdefghij", 0, 3);
fail("StringUtils.abbreviate expecting IllegalArgumentException"); fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException ex) { } catch (final IllegalArgumentException expected) {
// empty // empty
} }
try { try {
@SuppressWarnings("unused") StringUtils.abbreviate("abcdefghij", 5, 6);
final
String res = StringUtils.abbreviate("abcdefghij", 5, 6);
fail("StringUtils.abbreviate expecting IllegalArgumentException"); fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException ex) { } catch (final IllegalArgumentException expected) {
// empty // empty
} }
@ -2006,6 +2029,65 @@ private void assertAbbreviateWithOffset(final String expected, final int offset,
assertEquals(message, expected, actual); assertEquals(message, expected, actual);
} }
@Test
public void testAbbreviate_StringStringIntInt() {
assertNull(StringUtils.abbreviate(null, null, 10, 12));
assertNull(StringUtils.abbreviate(null, "...", 10, 12));
assertEquals("", StringUtils.abbreviate("", null, 0, 10));
assertEquals("", StringUtils.abbreviate("", "...", 2, 10));
try {
StringUtils.abbreviate("abcdefghij", "::", 0, 2);
fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException expected) {
// empty
}
try {
StringUtils.abbreviate("abcdefghij", "!!!", 5, 6);
fail("StringUtils.abbreviate expecting IllegalArgumentException");
} catch (final IllegalArgumentException expected) {
// empty
}
final String raspberry = "raspberry peach";
assertEquals("raspberry peach", StringUtils.abbreviate(raspberry, "--", 12, 15));
assertNull(StringUtils.abbreviate(null, ";", 7, 14));
assertAbbreviateWithAbbrevMarkerAndOffset("abcdefgh;;", ";;", -1, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("abcdefghi.", ".", 0, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("abcdefgh++", "++", 1, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("abcdefghi*", "*", 2, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("abcdef{{{{", "{{{{", 4, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("abcdef____", "____", 5, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("==fghijk==", "==", 5, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("___ghij___", "___", 6, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 7, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 8, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 9, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("///ijklmno", "///", 10, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("//hijklmno", "//", 10, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("//hijklmno", "//", 11, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("...ijklmno", "...", 12, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 13, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("/ghijklmno", "/", 14, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("999ijklmno", "999", 15, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("_ghijklmno", "_", 16, 10);
assertAbbreviateWithAbbrevMarkerAndOffset("+ghijklmno", "+", Integer.MAX_VALUE, 10);
}
private void assertAbbreviateWithAbbrevMarkerAndOffset(final String expected, final String abbrevMarker, final int offset, final int maxWidth) {
final String abcdefghijklmno = "abcdefghijklmno";
final String message = "abbreviate(String,String,int,int) failed";
final String actual = StringUtils.abbreviate(abcdefghijklmno, abbrevMarker, offset, maxWidth);
if (offset >= 0 && offset < abcdefghijklmno.length()) {
assertTrue(message + " -- should contain offset character",
actual.indexOf((char) ('a' + offset)) != -1);
}
assertTrue(message + " -- should not be greater than maxWidth",
actual.length() <= maxWidth);
assertEquals(message, expected, actual);
}
@Test @Test
public void testAbbreviateMiddle() { public void testAbbreviateMiddle() {
// javadoc examples // javadoc examples
@ -2047,6 +2129,7 @@ public void testAbbreviateMiddle() {
assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5)); assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5));
} }
//-----------------------------------------------------------------------
@Test @Test
public void testTruncate_StringInt() { public void testTruncate_StringInt() {
assertNull(StringUtils.truncate(null, 12)); assertNull(StringUtils.truncate(null, 12));