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>
*
* @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
* @throws IllegalArgumentException if len &lt;= 0
* @see #getAbbreviatedName(String, int)
* @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) {
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
* significant loss of meaning.</p>
*
* <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
* 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>
* <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>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>"org.apache.commons.lang3.ClassUtils"</td><td>18</td><td>"o.a.c.l.ClassUtils"</td></tr>
* </table>
* @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 &lt;= 0
*
* @param className the className to get the abbreviated name for, may be {@code null}
* @param lengthHint the desired length of the abbreviated name
* @return the abbreviated name or an empty string if the specified
* 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
*/
public static String getAbbreviatedName(final String className, final int len) {
if (len <= 0) {
throw new IllegalArgumentException("len must be > 0");
}
if (className == null) {
return StringUtils.EMPTY;
}
int availableSpace = len;
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--;
public static String getAbbreviatedName(final String className, final int lengthHint) {
if (lengthHint <= 0) {
throw new IllegalArgumentException("len must be > 0");
}
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);
}
if (className == null) {
return StringUtils.EMPTY;
}
endIndex = startIndex - 1;
}
if (className.length() <= lengthHint) {
return className;
}
final char[] abbreviated = className.toCharArray();
int target = 0;
int source = 0;
while (source < abbreviated.length) {
// copy the next part
int runAheadTarget = target;
while (source < abbreviated.length && abbreviated[source] != '.') {
abbreviated[runAheadTarget++] = abbreviated[source++];
}
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

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.GenericParent;
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 java.lang.reflect.Constructor;
@ -160,17 +162,33 @@ public class ClassUtilsTest {
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("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, 15));
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
@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() {
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, -10));
}
@Test
@DisplayName("When the desired length is zero then exception is thrown")
public void test_getAbbreviatedName_Class_ZeroLen() {
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getAbbreviatedName(String.class, 0));
}
@ -178,8 +196,25 @@ public class ClassUtilsTest {
@Test
public void test_getAbbreviatedName_String() {
assertEquals("", ClassUtils.getAbbreviatedName((String) null, 1));
assertEquals("", ClassUtils.getAbbreviatedName("", 1));
assertEquals("WithoutPackage", ClassUtils.getAbbreviatedName("WithoutPackage", 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