diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e4841227c..960c04e9d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@ + Add ClassUtils.getAbbreviatedName() FastDateParser does not set error indication in ParsePosition FastDateParser does not handle excess hours as per SimpleDateFormat FastDateParser error - timezones not handled correctly diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java b/src/main/java/org/apache/commons/lang3/ClassUtils.java index c58a608b4..e247cfbd1 100644 --- a/src/main/java/org/apache/commons/lang3/ClassUtils.java +++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java @@ -314,6 +314,87 @@ public class ClassUtils { return className.substring(0, i); } + // Abbreviated name + // ---------------------------------------------------------------------- + /** + *

Gets the abbreviated name of a {@code Class}.

+ * + * @param cls the class to get the abbreviated name for, may be {@code null} + * @param len the desired length of the abbreviated name + * @return the abbreviated name or an empty string + * @throws IllegalArgumentException if len <= 0 + * @see getAbbreviatedName(String, int) + * @since 3.4 + */ + public static String getAbbreviatedName(final Class cls, int len) { + if (cls == null) { + return StringUtils.EMPTY; + } + return getAbbreviatedName(cls.getName(), len); + } + + /** + *

Gets the abbreviated class name from a {@code String}.

+ * + *

The string passed in is assumed to be a class name - it is not checked.

+ * + *

The abbreviation algorithm will shorten the class name, usually without + * significant loss of meaning.

+ *

The abbreviated class name will always include the complete package hierarchy. + * If enough space is available, rightmost sub-packages will be displayed in full + * length.

+ * + *

The following table illustrates the algorithm:

+ * + * + * + * + * + * + *
classNamelenreturn
null 1""
"java.lang.String" 5"j.l.String"
"java.lang.String"15"j.lang.String"
"java.lang.String"30"java.lang.String"
+ * @param className the className to get the abbreviated name for, may be {@code null} + * @param len the desired length of the abbreviated name + * @return the abbreviated name or an empty string + * @throws IllegalArgumentException if len <= 0 + * @since 3.4 + */ + public static String getAbbreviatedName(String className, int len) { + if (len <= 0) { + throw new IllegalArgumentException("len must be > 0"); + } + if (className == null) { + return StringUtils.EMPTY; + } + + int availableSpace = len; + int packageLevels = StringUtils.countMatches(className, '.'); + String[] output = new String[packageLevels + 1]; + int endIndex = className.length() - 1; + for (int level = packageLevels; level >= 0; level--) { + int startIndex = className.lastIndexOf('.', endIndex); + String part = className.substring(startIndex + 1, endIndex + 1); + availableSpace -= part.length(); + if (level > 0) { + // all elements except top level require an additional char space + availableSpace--; + } + if (level == packageLevels) { + // ClassName is always complete + output[level] = part; + } else { + if (availableSpace > 0) { + output[level] = part; + } else { + // if no space is left still the first char is used + output[level] = part.substring(0, 1); + } + } + endIndex = startIndex - 1; + } + + return StringUtils.join(output, '.'); + } + // Superclasses/Superinterfaces // ---------------------------------------------------------------------- /** diff --git a/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java index fea5bba1a..93db1a0e4 100644 --- a/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java @@ -230,6 +230,42 @@ public class ClassUtilsTest { assertEquals("", ClassUtils.getPackageName("")); } + // ------------------------------------------------------------------------- + @Test + public void test_getAbbreviatedName_Class() { + assertEquals("", ClassUtils.getAbbreviatedName((Class)null, 1)); + assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 1)); + assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 5)); + assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 13)); + assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 15)); + assertEquals("java.lang.String", ClassUtils.getAbbreviatedName(String.class, 20)); + } + + @Test + public void test_getAbbreviatedName_Class_Exceptions() { + try { + ClassUtils.getAbbreviatedName(String.class, 0); + fail("ClassUtils.getAbbreviatedName() should fail with an " + + "IllegalArgumentException for a len value of 0."); + } catch (final Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + try { + ClassUtils.getAbbreviatedName(String.class, -10); + fail("ClassUtils.getAbbreviatedName() should fail with an " + + "IllegalArgumentException for negative values of len."); + } catch (final Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void test_getAbbreviatedName_String() { + assertEquals("", ClassUtils.getAbbreviatedName((String)null, 1)); + assertEquals("WithoutPackage", ClassUtils.getAbbreviatedName("WithoutPackage", 1)); + assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", 1)); + } + // ------------------------------------------------------------------------- @Test public void test_getAllSuperclasses_Class() {