This is an automated email from the ASF dual-hosted git repository.
ggregory 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 4052e9862 LANG-1818: Fix ClassUtils.getShortClassName(Class) to
correctly handle $ in valid class names (#1591)
4052e9862 is described below
commit 4052e9862bf540f0b294bcf58aad5b5e85eae4ad
Author: Ivan Ponomarev <[email protected]>
AuthorDate: Wed Jan 28 20:29:46 2026 +0000
LANG-1818: Fix ClassUtils.getShortClassName(Class) to correctly handle $ in
valid class names (#1591)
* fix lang-1818
* fix review comments
* Checkstyle
---------
Co-authored-by: Gary Gregory <[email protected]>
---
.../java/org/apache/commons/lang3/ClassUtils.java | 33 ++++++----
.../lang3/ClassUtilsShortClassNameTest.java | 74 ++++++++++++++++++++++
2 files changed, 96 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 5c2cf099d..3032caee6 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}.
*
- * <p>
- * This method simply gets the name using {@code Class.getName()} and then
calls {@link #getShortClassName(String)}. See
- * relevant notes there.
- * </p>
- *
* @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<String> 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.
*
- * <p>
- * 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).
- * </p>
- *
* @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 000000000..d19bc0393
--- /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 <a
href="https://issues.apache.org/jira/browse/LANG-1818">LANG-1818</a>
+ */
+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));
+ }
+}