Repository: commons-lang Updated Branches: refs/heads/master a40b2a907 -> c37a911d3
LANG-1160: StringUtils#abbreviate should support 'custom ellipses' parameter (closes #195) Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/c37a911d Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/c37a911d Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/c37a911d Branch: refs/heads/master Commit: c37a911d3a1541adc25c8d76f717015b2266d123 Parents: a40b2a9 Author: Bruno P. Kinoshita <ki...@apache.org> Authored: Thu Oct 13 22:55:17 2016 +1300 Committer: pascalschumacher <pascalschumac...@gmx.net> Committed: Fri Oct 28 19:33:47 2016 +0200 ---------------------------------------------------------------------- src/changes/changes.xml | 1 + .../org/apache/commons/lang3/StringUtils.java | 119 ++++++++++++++++--- .../apache/commons/lang3/StringUtilsTest.java | 101 ++++++++++++++-- 3 files changed, 196 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-lang/blob/c37a911d/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 437d59b..4b8f2b4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -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-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-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-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> http://git-wip-us.apache.org/repos/asf/commons-lang/blob/c37a911d/src/main/java/org/apache/commons/lang3/StringUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index a6c7c56..dbd7ff4 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 @@ import java.util.regex.Pattern; * <li><b>Reverse/ReverseDelimited</b> * - reverses a String</li> * <li><b>Abbreviate</b> - * - abbreviates a string using ellipsis</li> + * - abbreviates a string using ellipsis or another given String</li> * <li><b>Difference</b> * - compares Strings and reports on their differences</li> * <li><b>LevenshteinDistance</b> @@ -7377,7 +7377,8 @@ public class StringUtils { * @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 class StringUtils { * @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); + } + + /** + * <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) { return str; @@ -7428,20 +7516,19 @@ public class StringUtils { 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)); } /** http://git-wip-us.apache.org/repos/asf/commons-lang/blob/c37a911d/src/test/java/org/apache/commons/lang3/StringUtilsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java index 524bd8d..e8b4f6e 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsTest.java @@ -1934,9 +1934,36 @@ public class StringUtilsTest { 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 class StringUtilsTest { 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 } @@ -2007,6 +2030,65 @@ public class StringUtilsTest { } @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 assertNull(StringUtils.abbreviateMiddle(null, null, 0)); @@ -2047,6 +2129,7 @@ public class StringUtilsTest { assertEquals("ab.ef", StringUtils.abbreviateMiddle("abcdef", ".", 5)); } + //----------------------------------------------------------------------- @Test public void testTruncate_StringInt() { assertNull(StringUtils.truncate(null, 12));