This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new e871e5b30 Preserve enclosing context and member names in
ClassUtils.getShortClassName(Class) (#1818)
e871e5b30 is described below
commit e871e5b3052df0e6047180ff9c5d2e8d17e06c85
Author: alhuda <[email protected]>
AuthorDate: Thu Oct 15 18:58:56 2026 +0530
Preserve enclosing context and member names in
ClassUtils.getShortClassName(Class) (#1818)
* fix getShortClassName(Class) for members of anonymous and local classes
* Rename test method to follow Java naming conventions
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
* Keep member simple names below an anonymous or local enclosure
Use the name-based form only for the anonymous or local class in the
declaring chain and append the simple names of the member classes
below it, so a literal '$' in a member name (Local.Inner$Name) is kept.
Move the tests to ClassUtilsShortClassNameTest.
---------
Co-authored-by: Gary Gregory <[email protected]>
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
---
.../java/org/apache/commons/lang3/ClassUtils.java | 24 ++++++-----
.../lang3/ClassUtilsShortClassNameTest.java | 48 ++++++++++++++++++++++
2 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java
b/src/main/java/org/apache/commons/lang3/ClassUtils.java
index 20e789939..5c6f1bf0d 100644
--- a/src/main/java/org/apache/commons/lang3/ClassUtils.java
+++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java
@@ -1143,18 +1143,20 @@ public static String getShortClassName(final Class<?>
cls) {
// can both throw NoClassDefFoundError when the enclosing class is
// missing from the classpath, so the try/catch wraps the whole block.
try {
- // 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<String> parts = new ArrayDeque<>();
- Class<?> x = c;
- while (x != null) {
- parts.push(x.getSimpleName());
- x = x.getDeclaringClass();
- }
- base = String.join(".", parts);
+ final Deque<String> parts = new ArrayDeque<>();
+ Class<?> x = c;
+ // An anonymous or local class anywhere in the chain stops the
walk: it has no declaring
+ // class, so the chain cannot recover its enclosing context. Use
the name-based form for
+ // that enclosure only, which keeps the context and the compiler
ordinals ($13, $10Named, etc.),
+ // and keep the simple names of the member classes below it, which
may contain '$'.
+ while (x != null && !x.isAnonymousClass() && !x.isLocalClass()) {
+ parts.push(x.getSimpleName());
+ x = x.getDeclaringClass();
+ }
+ if (x != null) {
+ parts.push(getShortClassName(x.getName()));
}
+ base = String.join(".", parts);
} catch (final NoClassDefFoundError ignored) {
base = getShortClassName(c.getName());
}
diff --git
a/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java
b/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java
index 6454c5a38..7f81184eb 100644
--- a/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java
+++ b/src/test/java/org/apache/commons/lang3/ClassUtilsShortClassNameTest.java
@@ -20,6 +20,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.lang.reflect.Array;
+import java.util.function.Supplier;
+
import org.junit.jupiter.api.Test;
class $trange {
@@ -44,6 +47,22 @@ void testDollarSignImmediatelyAfterPackage() {
assertEquals("$trange", ClassUtils.getShortClassName($trange.class));
}
+ @Test
+ void testDollarSignWithinMemberOfAnonymousClass() {
+ final Supplier<Class<?>> anonymous = new Supplier<Class<?>>() {
+
+ class Inner$Name {
+ }
+
+ @Override
+ public Class<?> get() {
+ return Inner$Name.class;
+ }
+ };
+ final String shortName = ClassUtils.getShortClassName(anonymous.get());
+ assertEquals(ClassUtils.getShortClassName(anonymous.getClass()) +
".Inner$Name", shortName);
+ }
+
@Test
void testDollarSignWithinMemberOfLocalClass() {
class Local {
@@ -65,6 +84,35 @@ void testInnerClassName() {
assertEquals(getClass().getSimpleName() + ".Inner",
ClassUtils.getShortClassName(Inner.class));
}
+ @Test
+ void testMemberOfAnonymousClass() {
+ final Supplier<Class<?>> anonymous = new Supplier<Class<?>>() {
+
+ class Inner {
+ }
+
+ @Override
+ public Class<?> get() {
+ return Inner.class;
+ }
+ };
+ final Class<?> inner = anonymous.get();
+ // The enclosing class and compiler-generated ordinal must be kept, as
in the name-based form.
+ assertEquals(ClassUtils.getShortClassName(inner.getName()),
ClassUtils.getShortClassName(inner));
+ assertEquals(ClassUtils.getShortClassName(inner.getName()) + "[]",
ClassUtils.getShortClassName(Array.newInstance(inner, 0).getClass()));
+ }
+
+ @Test
+ void testMemberOfLocalClass() {
+ class Local {
+ class Inner {
+ }
+ }
+ // The enclosing class and compiler-generated ordinal must be kept, as
in the name-based form.
+
assertEquals(ClassUtils.getShortClassName(Local.Inner.class.getName()),
ClassUtils.getShortClassName(Local.Inner.class));
+ assertEquals(ClassUtils.getShortClassName(Local.Inner.class.getName())
+ "[]", ClassUtils.getShortClassName(Local.Inner[].class));
+ }
+
@Test
void testMultipleDollarSigns() {
assertEquals(getClass().getSimpleName() + ".$Inner",
ClassUtils.getShortClassName($Inner.class));