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:
+ *
+ * className | len | return |
+ * 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() {