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));

Reply via email to