You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "Gary D. Gregory (Jira)" <ji...@apache.org> on 2026/01/28 20:57:00 UTC
[jira] [Resolved] (LANG-1818) ClassUtils.getShortClassName(Class) misinterprets '$' in legitimate class names
[ https://issues.apache.org/jira/browse/LANG-1818?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Gary D. Gregory resolved LANG-1818.
-----------------------------------
Fix Version/s: 3.21.0
Resolution: Fixed
Thank you [~iponomarev]
PR merged, 3.21.0-SNAPSHOT build deployed.
> ClassUtils.getShortClassName(Class) misinterprets '$' in legitimate class names
> -------------------------------------------------------------------------------
>
> Key: LANG-1818
> URL: https://issues.apache.org/jira/browse/LANG-1818
> Project: Commons Lang
> Issue Type: Improvement
> Reporter: Ivan Ponomarev
> Priority: Minor
> Fix For: 3.21.0
>
>
> h3.Description
> {{ClassUtils.getShortClassName(String)}} operates on JVM binary names and necessarily relies on heuristics. As documented in its Javadoc, it cannot reliably distinguish package names, outer classes, and inner classes in all cases when given only a {{String}}.
> However, these limitations should not apply to {{getShortClassName(Class)}} (and {{getShortClassName(Object))}}, which currently delegate to the string-based method:
> {code:java}
> public static String getShortClassName(final Class<?> cls) {
> if (cls == null) {
> return StringUtils.EMPTY;
> }
> return getShortClassName(cls.getName());
> }
> {code}
> When a {{Class<?>}} instance is available, we have unambiguous metadata. Delegating to the string-based implementation causes legitimate $ characters in class identifiers to be incorrectly interpreted as inner-class separators.
> h3.Motivation
> Although uncommon, {{$}} is a valid character in Java identifiers and is used in practice (e.g. generated code, DSL-heavy libraries, test fixtures, Selenide library exports {{$}} and {{$$}} names). Treating every $ as an inner-class separator leads to incorrect results even for top-level and member classes.
> While this ambiguity is unavoidable for {{getShortClassName(String)}}, it is avoidable for {{getShortClassName(Class)}}.
> h3.Reproducer
> {code:java}
> class $trange {}
> class Pa$$word {}
> class ClassUtilsShortClassNameTest {
> class $Inner {}
> class Inner {
> class Ne$ted {}
> }
> @Test
> void testDollarSignImmediatelyAfterPackage() {
> assertEquals("$trange", ClassUtils.getShortClassName($trange.class));
> // Actual (before fix): ".trange"
> }
> @Test
> void testDollarSignWithinName() {
> assertEquals("Pa$$word", ClassUtils.getShortClassName(Pa$$word.class));
> // Actual (before fix): "Pa..word"
> }
> @Test
> void testMultipleDollarSigns() {
> assertEquals(
> getClass().getSimpleName() + ".$Inner",
> ClassUtils.getShortClassName($Inner.class)
> );
> // Actual (before fix): "ClassUtilsShortClassNameTest..Inner"
> }
> @Test
> void testNe$tedClassName() {
> assertEquals(
> getClass().getSimpleName() + ".Inner.Ne$ted",
> ClassUtils.getShortClassName(Inner.Ne$ted.class)
> );
> // Actual (before fix): "ClassUtilsShortClassNameTest.Inner.Ne.ted"
> }
> }
> {code}
> h3.Proposed fix
> * Reimplement {{getShortClassName(Class)}} using {{Class}} metadata ({{getSimpleName()}}, {{getDeclaringClass()}}, {{getEnclosingClass()}}, array component handling) instead of delegating to the string-based method.
> * Preserve existing behavior for local and anonymous classes (compiler-generated ordinal names) by falling back to the legacy string-based logic, ensuring backward compatibility with existing tests.
> * Leave {{getShortClassName(String)}} unchanged and continue to document its inherent ambiguity.
> As a result: {{getShortCanonicalName(String)}} continues to rely on the string-based implementation and remains subject to the documented limitations; this is expected and unchanged. This change improves correctness for the Class-based APIs without altering behavior for purely string-based usage.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)