LANG-1480 getAbbreviatedName refactored to create appropriate length … (#446)

* LANG-1480 getAbbreviatedName refactored to create appropriate length short class names

* LANG-1480 code fixed for special extreme case ".." abbreviated to 1 length should result ".." it was throwing exception. Tests are added

* import changed to avoid wild cards
apache master merged into current branch

* Mutable object import was moved to it's original place

* some accidental formatting reverted

* some accidental formatting reverted

* some accidental formatting reverted

* some accidental formatting reverted

* some accidental formatting reverted

* some accidental formatting reverted

* some accidental formatting reverted

* added another test case

* LANG-1480 fixing JavaDoc documentation as per requested by garydgregory

* LANG-1480 shortcut implemented, argument renamed, more tests

* LANG-1480 checkstyle update

* LANG-1492 tests methods modified to be public

* LANG-1480 imports rearranged

* LANG-1480 imports rearranged

* LANG-1480 imports rearranged

* imports were added that were accidentally removed during merging master
This commit is contained in:
Peter Verhas 2019-10-12 21:16:54 +02:00 committed by Gary Gregory
parent a32c188c32
commit bedae6950d
2 changed files with 123 additions and 40 deletions

View File

@ -428,17 +428,17 @@ public class ClassUtils {
* <p>Gets the abbreviated name of a {@code Class}.</p> * <p>Gets the abbreviated name of a {@code Class}.</p>
* *
* @param cls the class to get the abbreviated name for, may be {@code null} * @param cls the class to get the abbreviated name for, may be {@code null}
* @param len the desired length of the abbreviated name * @param lengthHint the desired length of the abbreviated name
* @return the abbreviated name or an empty string * @return the abbreviated name or an empty string
* @throws IllegalArgumentException if len &lt;= 0 * @throws IllegalArgumentException if len &lt;= 0
* @see #getAbbreviatedName(String, int) * @see #getAbbreviatedName(String, int)
* @since 3.4 * @since 3.4
*/ */
public static String getAbbreviatedName(final Class<?> cls, final int len) { public static String getAbbreviatedName(final Class<?> cls, final int lengthHint) {
if (cls == null) { if (cls == null) {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
return getAbbreviatedName(cls.getName(), len); return getAbbreviatedName(cls.getName(), lengthHint);
} }
/** /**
@ -448,9 +448,20 @@ public class ClassUtils {
* *
* <p>The abbreviation algorithm will shorten the class name, usually without * <p>The abbreviation algorithm will shorten the class name, usually without
* significant loss of meaning.</p> * significant loss of meaning.</p>
*
* <p>The abbreviated class name will always include the complete package hierarchy. * <p>The abbreviated class name will always include the complete package hierarchy.
* If enough space is available, rightmost sub-packages will be displayed in full * If enough space is available, rightmost sub-packages will be displayed in full
* length.</p> * length. The abbreviated package names will be shortened to a single character.</p>
* <p>Only package names are shortened, the class simple name remains untouched. (See examples.)</p>
* <p>The result will be longer than the desired length only if all the package names
* shortened to a single character plus the class simple name with the separating dots
* together are longer than the desired length. In other words, when the class name
* cannot be shortened to the desired length.</p>
* <p>If the class name can be shortened then
* the final length will be at most {@code lengthHint} characters.</p>
* <p>If the {@code lengthHint} is zero or negative then the method
* throws exception. If you want to achieve the shortest possible version then
* use {@code 1} as a {@code lengthHint}.</p>
* *
* <table> * <table>
* <caption>Examples</caption> * <caption>Examples</caption>
@ -459,48 +470,85 @@ public class ClassUtils {
* <tr><td>"java.lang.String"</td><td> 5</td><td>"j.l.String"</td></tr> * <tr><td>"java.lang.String"</td><td> 5</td><td>"j.l.String"</td></tr>
* <tr><td>"java.lang.String"</td><td>15</td><td>"j.lang.String"</td></tr> * <tr><td>"java.lang.String"</td><td>15</td><td>"j.lang.String"</td></tr>
* <tr><td>"java.lang.String"</td><td>30</td><td>"java.lang.String"</td></tr> * <tr><td>"java.lang.String"</td><td>30</td><td>"java.lang.String"</td></tr>
* <tr><td>"org.apache.commons.lang3.ClassUtils"</td><td>18</td><td>"o.a.c.l.ClassUtils"</td></tr>
* </table> * </table>
*
* @param className the className to get the abbreviated name for, may be {@code null} * @param className the className to get the abbreviated name for, may be {@code null}
* @param len the desired length of the abbreviated name * @param lengthHint the desired length of the abbreviated name
* @return the abbreviated name or an empty string * @return the abbreviated name or an empty string if the specified
* @throws IllegalArgumentException if len &lt;= 0 * class name is {@code null} or empty string. The abbreviated name may be
* longer than the desired length if it cannot be abbreviated to the desired length.
* @throws IllegalArgumentException if {@code len <= 0}
* @since 3.4 * @since 3.4
*/ */
public static String getAbbreviatedName(final String className, final int len) { public static String getAbbreviatedName(final String className, final int lengthHint) {
if (len <= 0) { if (lengthHint <= 0) {
throw new IllegalArgumentException("len must be > 0"); throw new IllegalArgumentException("len must be > 0");
} }
if (className == null) { if (className == null) {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
if (className.length() <= lengthHint) {
int availableSpace = len; return className;
final int packageLevels = StringUtils.countMatches(className, '.');
final String[] output = new String[packageLevels + 1];
int endIndex = className.length() - 1;
for (int level = packageLevels; level >= 0; level--) {
final int startIndex = className.lastIndexOf('.', endIndex);
final 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) { final char[] abbreviated = className.toCharArray();
// ClassName is always complete int target = 0;
output[level] = part; int source = 0;
} else { while (source < abbreviated.length) {
if (availableSpace > 0) { // copy the next part
output[level] = part; int runAheadTarget = target;
} else { while (source < abbreviated.length && abbreviated[source] != '.') {
// if no space is left still the first char is used abbreviated[runAheadTarget++] = abbreviated[source++];
output[level] = part.substring(0, 1);
}
}
endIndex = startIndex - 1;
} }
return StringUtils.join(output, '.'); ++target;
if (useFull(runAheadTarget, source, abbreviated.length, lengthHint)
|| target > runAheadTarget) {
target = runAheadTarget;
}
// copy the '.' unless it was the last part
if (source < abbreviated.length) {
abbreviated[target++] = abbreviated[source++];
}
}
return new String(abbreviated, 0, target);
}
/**
* <p>Decides if the part that was just copied to its destination
* location in the work array can be kept as it was copied or must be
* abbreviated. It must be kept when the part is the last one, which
* is the simple name of the class. In this case the {@code source}
* index, from where the characters are copied points one position
* after the last character, a.k.a. {@code source ==
* originalLength}</p>
*
* <p>If the part is not the last one then it can be kept
* unabridged if the number of the characters copied so far plus
* the character that are to be copied is less than or equal to the
* desired length.</p>
*
* @param runAheadTarget the target index (where the characters were
* copied to) pointing after the last character
* copied when the current part was copied
* @param source the source index (where the characters were
* copied from) pointing after the last
* character copied when the current part was
* copied
* @param originalLength the original length of the class full name,
* which is abbreviated
* @param desiredLength the desired length of the abbreviated class
* name
* @return {@code true} if it can be kept in its original length
* {@code false} if the current part has to be abbreviated and
*/
private static boolean useFull(final int runAheadTarget,
final int source,
final int originalLength,
final int desiredLength) {
return source >= originalLength ||
runAheadTarget + originalLength - source <= desiredLength;
} }
// Superclasses/Superinterfaces // Superclasses/Superinterfaces

View File

@ -20,6 +20,8 @@ import org.apache.commons.lang3.ClassUtils.Interfaces;
import org.apache.commons.lang3.reflect.testbed.GenericConsumer; import org.apache.commons.lang3.reflect.testbed.GenericConsumer;
import org.apache.commons.lang3.reflect.testbed.GenericParent; import org.apache.commons.lang3.reflect.testbed.GenericParent;
import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild; import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -160,17 +162,33 @@ public class ClassUtilsTest {
assertEquals("", ClassUtils.getAbbreviatedName((Class<?>) null, 1)); assertEquals("", ClassUtils.getAbbreviatedName((Class<?>) null, 1));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 1)); assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 1));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 5)); assertEquals("j.l.String", ClassUtils.getAbbreviatedName(String.class, 5));
assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName(ClassUtils.class, 18));
assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 13)); assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 13));
assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 15)); assertEquals("j.lang.String", ClassUtils.getAbbreviatedName(String.class, 15));
assertEquals("java.lang.String", ClassUtils.getAbbreviatedName(String.class, 20)); assertEquals("java.lang.String", ClassUtils.getAbbreviatedName(String.class, 20));
} }
/**
* Test that in case the required length is larger than the name and thus there is no need for any shortening
* then the returned string object is the same as the one passed as argument. Note, however, that this is
* tested as an internal implementation detail, but it is not a guaranteed feature of the implementation.
*/
@Test @Test
@DisplayName("When the length hint is longer than the actual length then the same String object is returned")
public void test_getAbbreviatedName_TooLongHint(){
final String className = "java.lang.String";
Assertions.assertSame(className, ClassUtils.getAbbreviatedName(className, className.length()+1));
Assertions.assertSame(className, ClassUtils.getAbbreviatedName(className, className.length()));
}
@Test
@DisplayName("When the desired length is negative then exception is thrown")
public void test_getAbbreviatedName_Class_NegativeLen() { public void test_getAbbreviatedName_Class_NegativeLen() {
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, -10)); assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, -10));
} }
@Test @Test
@DisplayName("When the desired length is zero then exception is thrown")
public void test_getAbbreviatedName_Class_ZeroLen() { public void test_getAbbreviatedName_Class_ZeroLen() {
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, 0)); assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, 0));
} }
@ -178,8 +196,25 @@ public class ClassUtilsTest {
@Test @Test
public void test_getAbbreviatedName_String() { public void test_getAbbreviatedName_String() {
assertEquals("", ClassUtils.getAbbreviatedName((String) null, 1)); assertEquals("", ClassUtils.getAbbreviatedName((String) null, 1));
assertEquals("", ClassUtils.getAbbreviatedName("", 1));
assertEquals("WithoutPackage", ClassUtils.getAbbreviatedName("WithoutPackage", 1)); assertEquals("WithoutPackage", ClassUtils.getAbbreviatedName("WithoutPackage", 1));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", 1)); assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", 1));
assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName("org.apache.commons.lang3.ClassUtils", 18));
assertEquals("org.apache.commons.lang3.ClassUtils",
ClassUtils.getAbbreviatedName("org.apache.commons.lang3.ClassUtils",
"org.apache.commons.lang3.ClassUtils".length()));
assertEquals("o.a.c.l.ClassUtils", ClassUtils.getAbbreviatedName("o.a.c.l.ClassUtils", 18));
assertEquals("o..c.l.ClassUtils", ClassUtils.getAbbreviatedName("o..c.l.ClassUtils", 18));
assertEquals(".", ClassUtils.getAbbreviatedName(".", 18));
assertEquals(".", ClassUtils.getAbbreviatedName(".", 1));
assertEquals("..", ClassUtils.getAbbreviatedName("..", 1));
assertEquals("...", ClassUtils.getAbbreviatedName("...", 2));
assertEquals("...", ClassUtils.getAbbreviatedName("...", 3));
assertEquals("java.lang.String", ClassUtils.getAbbreviatedName("java.lang.String", Integer.MAX_VALUE));
assertEquals("j.lang.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.lang.String".length()));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.lang.String".length() - 1));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.l.String".length()));
assertEquals("j.l.String", ClassUtils.getAbbreviatedName("java.lang.String", "j.l.String".length() - 1));
} }
@Test @Test