Skip to content

Commit 4052e98

Browse files
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 <garydgregory@users.noreply.github.com>
1 parent 7bcb03a commit 4052e98

2 files changed

Lines changed: 96 additions & 11 deletions

File tree

src/main/java/org/apache/commons/lang3/ClassUtils.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.lang.reflect.Modifier;
21+
import java.util.ArrayDeque;
2122
import java.util.ArrayList;
2223
import java.util.Collections;
2324
import java.util.Comparator;
25+
import java.util.Deque;
2426
import java.util.HashMap;
2527
import java.util.HashSet;
2628
import java.util.Iterator;
@@ -1011,11 +1013,6 @@ public static String getShortCanonicalName(final String canonicalName) {
10111013
/**
10121014
* Gets the class name minus the package name from a {@link Class}.
10131015
*
1014-
* <p>
1015-
* This method simply gets the name using {@code Class.getName()} and then calls {@link #getShortClassName(String)}. See
1016-
* relevant notes there.
1017-
* </p>
1018-
*
10191016
* @param cls the class to get the short name for.
10201017
* @return the class name without the package name or an empty string. If the class is an inner class then the returned
10211018
* value will contain the outer class or classes separated with {@code .} (dot) character.
@@ -1024,17 +1021,31 @@ public static String getShortClassName(final Class<?> cls) {
10241021
if (cls == null) {
10251022
return StringUtils.EMPTY;
10261023
}
1027-
return getShortClassName(cls.getName());
1024+
int dim = 0;
1025+
Class<?> c = cls;
1026+
while (c.isArray()) {
1027+
dim++;
1028+
c = c.getComponentType();
1029+
}
1030+
final String base;
1031+
// Preserve legacy behavior for anonymous/local classes (keeps compiler ordinals: $13, $10Named, etc.)
1032+
if (c.isAnonymousClass() || c.isLocalClass()) {
1033+
base = getShortClassName(c.getName());
1034+
} else {
1035+
final Deque<String> parts = new ArrayDeque<>();
1036+
Class<?> x = c;
1037+
while (x != null) {
1038+
parts.push(x.getSimpleName());
1039+
x = x.getDeclaringClass();
1040+
}
1041+
base = String.join(".", parts);
1042+
}
1043+
return base + StringUtils.repeat("[]", dim);
10281044
}
10291045

10301046
/**
10311047
* Gets the class name of the {@code object} without the package name or names.
10321048
*
1033-
* <p>
1034-
* The method looks up the class of the object and then converts the name of the class invoking
1035-
* {@link #getShortClassName(Class)} (see relevant notes there).
1036-
* </p>
1037-
*
10381049
* @param object the class to get the short name for, may be {@code null}.
10391050
* @param valueIfNull the value to return if the object is {@code null}.
10401051
* @return the class name of the object without the package name, or {@code valueIfNull} if the argument {@code object}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.lang3;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
class $trange {
25+
26+
}
27+
28+
class Pa$$word {
29+
30+
}
31+
32+
/**
33+
* Tests for <a href="https://issues.apache.org/jira/browse/LANG-1818">LANG-1818</a>
34+
*/
35+
public class ClassUtilsShortClassNameTest {
36+
37+
class $Inner {
38+
39+
}
40+
41+
class Inner {
42+
class Ne$ted {
43+
44+
}
45+
}
46+
47+
@Test
48+
void testDollarSignImmediatelyAfterPackage() {
49+
assertEquals("$trange", ClassUtils.getShortClassName($trange.class));
50+
}
51+
52+
@Test
53+
void testDollarSignWithinName() {
54+
assertEquals("Pa$$word", ClassUtils.getShortClassName(Pa$$word.class));
55+
}
56+
57+
@Test
58+
void testMultipleDollarSigns() {
59+
assertEquals(getClass().getSimpleName() + ".$Inner",
60+
ClassUtils.getShortClassName($Inner.class));
61+
}
62+
63+
@Test
64+
void testInnerClassName() {
65+
assertEquals(getClass().getSimpleName() + ".Inner",
66+
ClassUtils.getShortClassName(Inner.class));
67+
}
68+
69+
@Test
70+
void testNe$tedClassName() {
71+
assertEquals(getClass().getSimpleName() + ".Inner.Ne$ted",
72+
ClassUtils.getShortClassName(Inner.Ne$ted.class));
73+
}
74+
}

0 commit comments

Comments
 (0)