diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java b/src/main/java/org/apache/commons/lang3/ClassUtils.java index 5c2cf099d19..3032caee6d0 100644 --- a/src/main/java/org/apache/commons/lang3/ClassUtils.java +++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java @@ -18,9 +18,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -1011,11 +1013,6 @@ public static String getShortCanonicalName(final String canonicalName) { /** * Gets the class name minus the package name from a {@link Class}. * - *

- * This method simply gets the name using {@code Class.getName()} and then calls {@link #getShortClassName(String)}. See - * relevant notes there. - *

- * * @param cls the class to get the short name for. * @return the class name without the package name or an empty string. If the class is an inner class then the returned * value will contain the outer class or classes separated with {@code .} (dot) character. @@ -1024,17 +1021,31 @@ public static String getShortClassName(final Class cls) { if (cls == null) { return StringUtils.EMPTY; } - return getShortClassName(cls.getName()); + int dim = 0; + Class c = cls; + while (c.isArray()) { + dim++; + c = c.getComponentType(); + } + final String base; + // Preserve legacy behavior for anonymous/local classes (keeps compiler ordinals: $13, $10Named, etc.) + if (c.isAnonymousClass() || c.isLocalClass()) { + base = getShortClassName(c.getName()); + } else { + final Deque parts = new ArrayDeque<>(); + Class x = c; + while (x != null) { + parts.push(x.getSimpleName()); + x = x.getDeclaringClass(); + } + base = String.join(".", parts); + } + return base + StringUtils.repeat("[]", dim); } /** * Gets the class name of the {@code object} without the package name or names. * - *

- * The method looks up the class of the object and then converts the name of the class invoking - * {@link #getShortClassName(Class)} (see relevant notes there). - *

- * * @param object the class to get the short name for, may be {@code null}. * @param valueIfNull the value to return if the object is {@code null}. * @return the class name of the object without the package name, or {@code valueIfNull} if the argument {@code object} diff --git a/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java b/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java new file mode 100644 index 00000000000..d19bc039306 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.lang3; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class $trange { + +} + +class Pa$$word { + +} + +/** + * Tests for LANG-1818 + */ +public class ClassUtilsShortClassNameTest { + + class $Inner { + + } + + class Inner { + class Ne$ted { + + } + } + + @Test + void testDollarSignImmediatelyAfterPackage() { + assertEquals("$trange", ClassUtils.getShortClassName($trange.class)); + } + + @Test + void testDollarSignWithinName() { + assertEquals("Pa$$word", ClassUtils.getShortClassName(Pa$$word.class)); + } + + @Test + void testMultipleDollarSigns() { + assertEquals(getClass().getSimpleName() + ".$Inner", + ClassUtils.getShortClassName($Inner.class)); + } + + @Test + void testInnerClassName() { + assertEquals(getClass().getSimpleName() + ".Inner", + ClassUtils.getShortClassName(Inner.class)); + } + + @Test + void testNe$tedClassName() { + assertEquals(getClass().getSimpleName() + ".Inner.Ne$ted", + ClassUtils.getShortClassName(Inner.Ne$ted.class)); + } +}