diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 437d59bf9..4b8f2b406 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -53,6 +53,7 @@ The type attribute can be add,update,fix,remove. BooleanUtils javadoc issues ArrayUtils#add confusing example in javadoc StringUtils#isAnyEmpty and #isAnyBlank should return false for an empty array + StringUtils#abbreviate should support 'custom ellipses' parameter Add StringUtils#isAnyNotEmpty and #isAnyNotBlank StringUtils#getLevenshteinDistance reduce memory consumption Update Java requirement from Java 6 to 7. diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index a6c7c5625..dbd7ff47b 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -79,7 +79,7 @@ *
  • Reverse/ReverseDelimited * - reverses a String
  • *
  • Abbreviate - * - abbreviates a string using ellipsis
  • + * - abbreviates a string using ellipsis or another given String *
  • Difference * - compares Strings and reports on their differences
  • *
  • LevenshteinDistance @@ -7377,7 +7377,8 @@ public static String reverseDelimited(final String str, final char separatorChar * @since 2.0 */ 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 */ public static String abbreviate(final String str, int offset, final int maxWidth) { - if (str == null) { - return null; + final String defaultAbbrevMarker = "..."; + return abbreviate(str, defaultAbbrevMarker, offset, maxWidth); + } + + /** + *

    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.

    + * + *

    Specifically:

    + * + * + *
    +     * 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
    +     * 
    + * + * @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); + } + + /** + *

    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.

    + * + *

    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. + * + *

    In no case will it return a String of length greater than {@code maxWidth}.

    + * + *
    +     * 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
    +     * 
    + * + * @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) { return str; @@ -7428,20 +7516,19 @@ public static String abbreviate(final String str, int offset, final int maxWidth if (offset > str.length()) { offset = str.length(); } - if (str.length() - offset < maxWidth - 3) { - offset = str.length() - (maxWidth - 3); + if (str.length() - offset < maxWidth - abbrevMarkerLength) { + offset = str.length() - (maxWidth - abbrevMarkerLength); } - final String abrevMarker = "..."; - if (offset <= 4) { - return str.substring(0, maxWidth - 3) + abrevMarker; + if (offset <= abbrevMarkerLength+1) { + return str.substring(0, maxWidth - abbrevMarkerLength) + abbrevMarker; } - if (maxWidth < 7) { - throw new IllegalArgumentException("Minimum abbreviation width with offset is 7"); + if (maxWidth < minAbbrevWidthOffset) { + throw new IllegalArgumentException(String.format("Minimum abbreviation width with offset is %d", minAbbrevWidthOffset)); } - if (offset + maxWidth - 3 < str.length()) { - return abrevMarker + abbreviate(str.substring(offset), maxWidth - 3); + if (offset + maxWidth - abbrevMarkerLength < str.length()) { + 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)); } /** diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java index 524bd8ddb..e8b4f6e0c 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java @@ -1933,10 +1933,37 @@ public void testAbbreviate_StringInt() { assertEquals("a...", StringUtils.abbreviate("abcdefg", 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 { @SuppressWarnings("unused") final - String res = StringUtils.abbreviate("abc", 3); + String res = StringUtils.abbreviate("abcdefghij", "...", 3); fail("StringUtils.abbreviate expecting IllegalArgumentException"); } catch (final IllegalArgumentException ex) { // empty @@ -1950,19 +1977,15 @@ public void testAbbreviate_StringIntInt() { assertEquals("", StringUtils.abbreviate("", 2, 10)); try { - @SuppressWarnings("unused") - final - String res = StringUtils.abbreviate("abcdefghij", 0, 3); + StringUtils.abbreviate("abcdefghij", 0, 3); fail("StringUtils.abbreviate expecting IllegalArgumentException"); - } catch (final IllegalArgumentException ex) { + } catch (final IllegalArgumentException expected) { // empty } try { - @SuppressWarnings("unused") - final - String res = StringUtils.abbreviate("abcdefghij", 5, 6); + StringUtils.abbreviate("abcdefghij", 5, 6); fail("StringUtils.abbreviate expecting IllegalArgumentException"); - } catch (final IllegalArgumentException ex) { + } catch (final IllegalArgumentException expected) { // empty } @@ -2006,6 +2029,65 @@ private void assertAbbreviateWithOffset(final String expected, final int offset, 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 public void testAbbreviateMiddle() { // javadoc examples @@ -2047,6 +2129,7 @@ public void testAbbreviateMiddle() { assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5)); } + //----------------------------------------------------------------------- @Test public void testTruncate_StringInt() { assertNull(StringUtils.truncate(null, 12));