001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021import java.util.ArrayDeque;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.Deque;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Objects;
033import java.util.Set;
034import java.util.concurrent.atomic.AtomicReference;
035import java.util.regex.Pattern;
036import java.util.stream.Collectors;
037
038/**
039 * Operates on classes without using reflection.
040 *
041 * <p>
042 * This class handles invalid {@code null} inputs as best it can. Each method documents its behavior in more detail.
043 * </p>
044 *
045 * <p>
046 * The notion of a {@code canonical name} includes the human-readable name for the type, for example {@code int[]}. The
047 * non-canonical method variants work with the JVM names, such as {@code [I}.
048 * </p>
049 *
050 * @since 2.0
051 */
052public class ClassUtils {
053
054    /**
055     * Enumerates inclusivity literals for {@link #hierarchy(Class, Interfaces)}.
056     *
057     * @since 3.2
058     */
059    public enum Interfaces {
060
061        /** Includes interfaces. */
062        INCLUDE,
063
064        /** Excludes interfaces. */
065        EXCLUDE
066    }
067
068    /**
069     * The JLS-specified maximum class name length {@value}.
070     *
071     * @see Class#forName(String, boolean, ClassLoader)
072     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
073     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
074     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
075     */
076    private static final int MAX_CLASS_NAME_LENGTH = 65535;
077
078    /**
079     * The JVM-specified {@code CONSTANT_Class_info} structure defines an array type descriptor is valid only if it represents {@value} or fewer dimensions.
080     *
081     * @see Class#forName(String, boolean, ClassLoader)
082     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
083     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
084     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
085     */
086    private static final int MAX_JVM_ARRAY_DIMENSION = 255;
087
088    /**
089     * The maximum number of array dimensions.
090     */
091    private static final int MAX_DIMENSIONS = 255;
092
093    private static final Pattern ARRAY_TAIL_PATTERN = Pattern.compile("(?:\\[\\])+");
094
095    private static final Comparator<Class<?>> COMPARATOR = (o1, o2) -> Objects.compare(getName(o1), getName(o2), String::compareTo);
096
097    /**
098     * The package separator character: {@code '&#x2e;' == {@value}}.
099     */
100    public static final char PACKAGE_SEPARATOR_CHAR = '.';
101
102    /**
103     * The package separator String: {@code "&#x2e;"}.
104     */
105    public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR);
106
107    /**
108     * The inner class separator character: {@code '$' == {@value}}.
109     */
110    public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
111
112    /**
113     * The inner class separator String: {@code "$"}.
114     */
115    public static final String INNER_CLASS_SEPARATOR = String.valueOf(INNER_CLASS_SEPARATOR_CHAR);
116
117    /**
118     * Maps names of primitives to their corresponding primitive {@link Class}es.
119     */
120    private static final Map<String, Class<?>> NAME_PRIMITIVE_MAP = new HashMap<>();
121
122    static {
123        NAME_PRIMITIVE_MAP.put(Boolean.TYPE.getName(), Boolean.TYPE);
124        NAME_PRIMITIVE_MAP.put(Byte.TYPE.getName(), Byte.TYPE);
125        NAME_PRIMITIVE_MAP.put(Character.TYPE.getName(), Character.TYPE);
126        NAME_PRIMITIVE_MAP.put(Double.TYPE.getName(), Double.TYPE);
127        NAME_PRIMITIVE_MAP.put(Float.TYPE.getName(), Float.TYPE);
128        NAME_PRIMITIVE_MAP.put(Integer.TYPE.getName(), Integer.TYPE);
129        NAME_PRIMITIVE_MAP.put(Long.TYPE.getName(), Long.TYPE);
130        NAME_PRIMITIVE_MAP.put(Short.TYPE.getName(), Short.TYPE);
131        NAME_PRIMITIVE_MAP.put(Void.TYPE.getName(), Void.TYPE);
132    }
133
134    /**
135     * Maps primitive {@link Class}es to their corresponding wrapper {@link Class}.
136     */
137    private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_MAP = new HashMap<>();
138
139    static {
140        PRIMITIVE_WRAPPER_MAP.put(Boolean.TYPE, Boolean.class);
141        PRIMITIVE_WRAPPER_MAP.put(Byte.TYPE, Byte.class);
142        PRIMITIVE_WRAPPER_MAP.put(Character.TYPE, Character.class);
143        PRIMITIVE_WRAPPER_MAP.put(Short.TYPE, Short.class);
144        PRIMITIVE_WRAPPER_MAP.put(Integer.TYPE, Integer.class);
145        PRIMITIVE_WRAPPER_MAP.put(Long.TYPE, Long.class);
146        PRIMITIVE_WRAPPER_MAP.put(Double.TYPE, Double.class);
147        PRIMITIVE_WRAPPER_MAP.put(Float.TYPE, Float.class);
148        PRIMITIVE_WRAPPER_MAP.put(Void.TYPE, Void.TYPE);
149    }
150
151    /**
152     * Maps wrapper {@link Class}es to their corresponding primitive types.
153     */
154    private static final Map<Class<?>, Class<?>> WRAPPER_PRIMITIVE_MAP = new HashMap<>();
155
156    static {
157        PRIMITIVE_WRAPPER_MAP.forEach((primitiveClass, wrapperClass) -> {
158            if (!primitiveClass.equals(wrapperClass)) {
159                WRAPPER_PRIMITIVE_MAP.put(wrapperClass, primitiveClass);
160            }
161        });
162    }
163
164    /**
165     * Maps a primitive class name to its corresponding abbreviation used in array class names.
166     */
167    private static final Map<String, String> ABBREVIATION_MAP;
168
169    /**
170     * Maps an abbreviation used in array class names to corresponding primitive class name.
171     */
172    private static final Map<String, String> REVERSE_ABBREVIATION_MAP;
173
174    /** Feed abbreviation maps. */
175    static {
176        final Map<String, String> map = new HashMap<>();
177        map.put(Integer.TYPE.getName(), "I");
178        map.put(Boolean.TYPE.getName(), "Z");
179        map.put(Float.TYPE.getName(), "F");
180        map.put(Long.TYPE.getName(), "J");
181        map.put(Short.TYPE.getName(), "S");
182        map.put(Byte.TYPE.getName(), "B");
183        map.put(Double.TYPE.getName(), "D");
184        map.put(Character.TYPE.getName(), "C");
185        ABBREVIATION_MAP = Collections.unmodifiableMap(map);
186        REVERSE_ABBREVIATION_MAP = Collections.unmodifiableMap(map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
187    }
188
189    /**
190     * Gets the class comparator, comparing by class name.
191     *
192     * @return The class comparator.
193     * @since 3.13.0
194     */
195    public static Comparator<Class<?>> comparator() {
196        return COMPARATOR;
197    }
198
199    /**
200     * Given a {@link List} of {@link Class} objects, this method converts them into class names.
201     *
202     * <p>
203     * A new {@link List} is returned. {@code null} objects will be copied into the returned list as {@code null}.
204     * </p>
205     *
206     * @param classes The classes to change.
207     * @return A {@link List} of class names corresponding to the Class objects, {@code null} if null input.
208     * @throws ClassCastException Thrown if {@code classes} contains a non-{@link Class} entry.
209     */
210    public static List<String> convertClassesToClassNames(final List<Class<?>> classes) {
211        return classes == null ? null : classes.stream().map(e -> getName(e, null)).collect(Collectors.toList());
212    }
213
214    /**
215     * Given a {@link List} of class names, this method converts them into classes.
216     *
217     * <p>
218     * A new {@link List} is returned. If the class name cannot be found, {@code null} is stored in the {@link List}. If the
219     * class name in the {@link List} is {@code null}, {@code null} is stored in the output {@link List}.
220     * </p>
221     *
222     * @param classNames The classNames to change.
223     * @return A {@link List} of Class objects corresponding to the class names, {@code null} if null input.
224     * @throws ClassCastException Thrown if classNames contains a non String entry.
225     */
226    public static List<Class<?>> convertClassNamesToClasses(final List<String> classNames) {
227        if (classNames == null) {
228            return null;
229        }
230        final List<Class<?>> classes = new ArrayList<>(classNames.size());
231        classNames.forEach(className -> {
232            try {
233                classes.add(Class.forName(className));
234            } catch (final Exception ex) {
235                classes.add(null);
236            }
237        });
238        return classes;
239    }
240
241    /**
242     * Gets the abbreviated name of a {@link Class}.
243     *
244     * @param cls The class to get the abbreviated name for, may be {@code null}.
245     * @param lengthHint The desired length of the abbreviated name.
246     * @return The abbreviated name or an empty string.
247     * @throws IllegalArgumentException Thrown if len &lt;= 0.
248     * @see #getAbbreviatedName(String, int)
249     * @since 3.4
250     */
251    public static String getAbbreviatedName(final Class<?> cls, final int lengthHint) {
252        if (cls == null) {
253            return StringUtils.EMPTY;
254        }
255        return getAbbreviatedName(cls.getName(), lengthHint);
256    }
257
258    /**
259     * Gets the abbreviated class name from a {@link String}.
260     *
261     * <p>
262     * The string passed in is assumed to be a class name - it is not checked.
263     * </p>
264     *
265     * <p>
266     * The abbreviation algorithm will shorten the class name, usually without significant loss of meaning.
267     * </p>
268     *
269     * <p>
270     * The abbreviated class name will always include the complete package hierarchy. If enough space is available,
271     * rightmost sub-packages will be displayed in full length. The abbreviated package names will be shortened to a single
272     * character.
273     * </p>
274     * <p>
275     * Only package names are shortened, the class simple name remains untouched. (See examples.)
276     * </p>
277     * <p>
278     * The result will be longer than the desired length only if all the package names shortened to a single character plus
279     * the class simple name with the separating dots together are longer than the desired length. In other words, when the
280     * class name cannot be shortened to the desired length.
281     * </p>
282     * <p>
283     * If the class name can be shortened then the final length will be at most {@code lengthHint} characters.
284     * </p>
285     * <p>
286     * If the {@code lengthHint} is zero or negative then the method throws exception. If you want to achieve the shortest
287     * possible version then use {@code 1} as a {@code lengthHint}.
288     * </p>
289     *
290     * <table>
291     * <caption>Examples</caption>
292     * <tr>
293     * <td>className</td>
294     * <td>len</td>
295     * <td>return</td>
296     * </tr>
297     * <tr>
298     * <td>null</td>
299     * <td>1</td>
300     * <td>""</td>
301     * </tr>
302     * <tr>
303     * <td>"java.lang.String"</td>
304     * <td>5</td>
305     * <td>"j.l.String"</td>
306     * </tr>
307     * <tr>
308     * <td>"java.lang.String"</td>
309     * <td>15</td>
310     * <td>"j.lang.String"</td>
311     * </tr>
312     * <tr>
313     * <td>"java.lang.String"</td>
314     * <td>30</td>
315     * <td>"java.lang.String"</td>
316     * </tr>
317     * <tr>
318     * <td>"org.apache.commons.lang3.ClassUtils"</td>
319     * <td>18</td>
320     * <td>"o.a.c.l.ClassUtils"</td>
321     * </tr>
322     * </table>
323     *
324     * @param className The className to get the abbreviated name for, may be {@code null}.
325     * @param lengthHint The desired length of the abbreviated name.
326     * @return The abbreviated name or an empty string if the specified class name is {@code null} or empty string. The
327     *         abbreviated name may be longer than the desired length if it cannot be abbreviated to the desired length.
328     * @throws IllegalArgumentException Thrown if {@code len <= 0}.
329     * @since 3.4
330     */
331    public static String getAbbreviatedName(final String className, final int lengthHint) {
332        if (lengthHint <= 0) {
333            throw new IllegalArgumentException("len must be > 0");
334        }
335        if (className == null) {
336            return StringUtils.EMPTY;
337        }
338        if (className.length() <= lengthHint) {
339            return className;
340        }
341        final char[] abbreviated = className.toCharArray();
342        int target = 0;
343        int source = 0;
344        while (source < abbreviated.length) {
345            // copy the next part
346            int runAheadTarget = target;
347            while (source < abbreviated.length && abbreviated[source] != '.') {
348                abbreviated[runAheadTarget++] = abbreviated[source++];
349            }
350
351            ++target;
352            if (useFull(runAheadTarget, source, abbreviated.length, lengthHint) || target > runAheadTarget) {
353                target = runAheadTarget;
354            }
355
356            // copy the '.' unless it was the last part
357            if (source < abbreviated.length) {
358                abbreviated[target++] = abbreviated[source++];
359            }
360        }
361        return new String(abbreviated, 0, target);
362    }
363
364    /**
365     * Gets a {@link List} of all interfaces implemented by the given class and its superclasses.
366     *
367     * <p>
368     * The order is determined by looking through each interface in turn as declared in the source file and following its
369     * hierarchy up. Then each superclass is considered in the same way. Later duplicates are ignored, so the order is
370     * maintained.
371     * </p>
372     *
373     * @param cls The class to look up, may be {@code null}.
374     * @return The {@link List} of interfaces in order, {@code null} if null input.
375     */
376    public static List<Class<?>> getAllInterfaces(final Class<?> cls) {
377        if (cls == null) {
378            return null;
379        }
380        final LinkedHashSet<Class<?>> interfacesFound = new LinkedHashSet<>();
381        getAllInterfaces(cls, interfacesFound);
382        return new ArrayList<>(interfacesFound);
383    }
384
385    /**
386     * Gets the interfaces for the specified class.
387     *
388     * @param cls The class to look up, may be {@code null}.
389     * @param interfacesFound The {@link Set} of interfaces for the class.
390     */
391    private static void getAllInterfaces(Class<?> cls, final Set<Class<?>> interfacesFound) {
392        while (cls != null) {
393            for (final Class<?> i : cls.getInterfaces()) {
394                if (interfacesFound.add(i)) {
395                    getAllInterfaces(i, interfacesFound);
396                }
397            }
398            cls = cls.getSuperclass();
399        }
400    }
401
402    /**
403     * Gets a {@link List} of superclasses for the given class.
404     *
405     * <ol>
406     * <li>The first entry is the superclass of the given class.</li>
407     * <li>The last entry is {@link Object}'s class.</li>
408     * </ol>
409     *
410     * @param cls The class to look up, may be {@code null}.
411     * @return The {@link List} of superclasses in order going up from this one {@code null} if null input.
412     */
413    public static List<Class<?>> getAllSuperclasses(final Class<?> cls) {
414        if (cls == null) {
415            return null;
416        }
417        final List<Class<?>> classes = new ArrayList<>();
418        Class<?> superclass = cls.getSuperclass();
419        while (superclass != null) {
420            classes.add(superclass);
421            superclass = superclass.getSuperclass();
422        }
423        return classes;
424    }
425
426    /**
427     * Gets the canonical class name for a {@link Class}.
428     *
429     * @param cls The class for which to get the canonical class name; may be null.
430     * @return The canonical name of the class, or the empty String.
431     * @since 3.7
432     * @see Class#getCanonicalName()
433     */
434    public static String getCanonicalName(final Class<?> cls) {
435        return getCanonicalName(cls, StringUtils.EMPTY);
436    }
437
438    /**
439     * Gets the canonical name for a {@link Class}.
440     *
441     * @param cls The class for which to get the canonical class name; may be null.
442     * @param valueIfNull The return value if null.
443     * @return The canonical name of the class, or {@code valueIfNull}.
444     * @since 3.7
445     * @see Class#getCanonicalName()
446     */
447    public static String getCanonicalName(final Class<?> cls, final String valueIfNull) {
448        if (cls == null) {
449            return valueIfNull;
450        }
451        final String canonicalName = cls.getCanonicalName();
452        return canonicalName == null ? valueIfNull : canonicalName;
453    }
454
455    /**
456     * Gets the canonical name for an {@link Object}.
457     *
458     * @param object The object for which to get the canonical class name; may be null.
459     * @return The canonical name of the object, or the empty String.
460     * @since 3.7
461     * @see Class#getCanonicalName()
462     */
463    public static String getCanonicalName(final Object object) {
464        return getCanonicalName(object, StringUtils.EMPTY);
465    }
466
467    /**
468     * Gets the canonical name for an {@link Object}.
469     *
470     * @param object The object for which to get the canonical class name; may be null.
471     * @param valueIfNull The return value if null.
472     * @return The canonical name of the object or {@code valueIfNull}.
473     * @since 3.7
474     * @see Class#getCanonicalName()
475     */
476    public static String getCanonicalName(final Object object, final String valueIfNull) {
477        if (object == null) {
478            return valueIfNull;
479        }
480        final String canonicalName = object.getClass().getCanonicalName();
481        return canonicalName == null ? valueIfNull : canonicalName;
482    }
483
484    /**
485     * Gets the canonical form of the given class name. Non-array class names are returned unchanged.
486     *
487     * <p>
488     * The method does not change the {@code $} separators if the class is an inner class.
489     * </p>
490     *
491     * <p>
492     * Example:
493     * <ul>
494     * <li>{@code getCanonicalName("[I") = "int[]"}</li>
495     * <li>{@code getCanonicalName("[Ljava.lang.String;") = "java.lang.String[]"}</li>
496     * <li>{@code getCanonicalName("java.lang.String") = "java.lang.String"}</li>
497     * </ul>
498     * </p>
499     *
500     * @param name The name of class.
501     * @return canonical form of class name.
502     * @throws IllegalArgumentException Thrown if the class name is invalid.
503     */
504    private static String getCanonicalName(final String name) {
505        String className = StringUtils.deleteWhitespace(name);
506        if (className == null) {
507            return null;
508        }
509        int dim = 0;
510        final int len = className.length();
511        while (dim < len && className.charAt(dim) == '[') {
512            dim++;
513            if (dim > MAX_DIMENSIONS) {
514                throw new IllegalArgumentException(String.format("Maximum array dimension %d exceeded", MAX_DIMENSIONS));
515            }
516        }
517        if (dim >= len) {
518            throw new IllegalArgumentException(String.format("Invalid class name %s", name));
519        }
520        if (dim < 1) {
521            return className;
522        }
523        className = className.substring(dim);
524        if (className.startsWith("L")) {
525            if (!className.endsWith(";") || className.length() < 3) {
526                throw new IllegalArgumentException(String.format("Invalid class name %s", name));
527            }
528            className = className.substring(1, className.length() - 1);
529        } else if (className.length() == 1) {
530            final String primitive = REVERSE_ABBREVIATION_MAP.get(className.substring(0, 1));
531            if (primitive == null) {
532                throw new IllegalArgumentException(String.format("Invalid class name %s", name));
533            }
534            className = primitive;
535        } else {
536            throw new IllegalArgumentException(String.format("Invalid class name %s", name));
537        }
538        final StringBuilder canonicalClassNameBuffer = new StringBuilder(className.length() + dim * 2);
539        canonicalClassNameBuffer.append(className);
540        for (int i = 0; i < dim; i++) {
541            canonicalClassNameBuffer.append("[]");
542        }
543        return canonicalClassNameBuffer.toString();
544    }
545
546    /**
547     * Gets the (initialized) class represented by {@code className} using the {@code classLoader}. This implementation
548     * supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
549     * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
550     * <p>
551     * The provided class name is normalized by removing all whitespace. This is especially helpful when handling XML element values in which whitespace has not
552     * been collapsed.
553     * </p>
554     * <p>
555     * <strong>Security note:</strong> because all whitespace is deleted before the class is resolved (and a failing {@code '.'} may be retried as {@code '$'}
556     * for inner classes), many distinct input strings resolve to the same class, while {@link Class#forName(String)} performs no such normalization. Validating
557     * an untrusted class name by string comparison <em>before</em> calling this method is therefore unsound: for example, {@code " java.lang.Runtime"} fails a
558     * naive {@code startsWith("java.")} denylist check on the raw string, yet loads {@code java.lang.Runtime}. Validate the class name <em>after</em>
559     * normalization, validate the resolved {@link Class} object itself, or use {@link #getClassStrict(ClassLoader, String, boolean)} which performs no
560     * whitespace normalization.
561     * </p>
562     *
563     * @param classLoader The class loader to use to load the class.
564     * @param className The class name.
565     * @return The class represented by {@code className} using the {@code classLoader}.
566     * @throws NullPointerException Thrown if the className is null.
567     * @throws ClassNotFoundException Thrown if the class is not found.
568     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
569     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
570     * @see Class#forName(String, boolean, ClassLoader)
571     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
572     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
573     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
574     */
575    public static Class<?> getClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException {
576        return getClass(classLoader, className, true);
577    }
578
579    /**
580     * Gets the class represented by {@code className} using the {@code classLoader}. This implementation supports the
581     * syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and
582     * "{@code [Ljava.util.Map$Entry;}".
583     * <p>
584     * The provided class name is normalized by removing all whitespace. This is especially helpful when handling XML element values in which whitespace has not
585     * been collapsed.
586     * </p>
587     * <p>
588     * <strong>Security note:</strong> because all whitespace is deleted before the class is resolved (and a failing {@code '.'} may be retried as {@code '$'}
589     * for inner classes), many distinct input strings resolve to the same class, while {@link Class#forName(String)} performs no such normalization. Validating
590     * an untrusted class name by string comparison <em>before</em> calling this method is therefore unsound: for example, {@code " java.lang.Runtime"} fails a
591     * naive {@code startsWith("java.")} denylist check on the raw string, yet loads {@code java.lang.Runtime}. Validate the class name <em>after</em>
592     * normalization, validate the resolved {@link Class} object itself, or use {@link #getClassStrict(ClassLoader, String, boolean)} which performs no
593     * whitespace normalization.
594     * </p>
595     *
596     * @param classLoader The class loader to use to load the class.
597     * @param className The class name.
598     * @param initialize whether the class must be initialized.
599     * @return The class represented by {@code className} using the {@code classLoader}.
600     * @throws NullPointerException Thrown if the className is null.
601     * @throws ClassNotFoundException Thrown if the class is not found.
602     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
603     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
604     * @see Class#forName(String, boolean, ClassLoader)
605     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
606     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
607     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
608     */
609    public static Class<?> getClass(final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException {
610        return getClass(classLoader, className, initialize, true);
611    }
612
613    /**
614     * Gets a class using the shared implementation of {@link #getClass(ClassLoader, String, boolean)} and
615     * {@link #getClassStrict(ClassLoader, String, boolean)}.
616     *
617     * @param classLoader The class loader to use to load the class.
618     * @param className The class name.
619     * @param initialize whether the class must be initialized.
620     * @param normalizeWhitespace whether to delete all whitespace from the class name before resolving it.
621     * @return The class represented by {@code className} using the {@code classLoader}.
622     * @throws NullPointerException Thrown if the className is null.
623     * @throws ClassNotFoundException Thrown if the class is not found.
624     */
625    private static Class<?> getClass(final ClassLoader classLoader, final String className, final boolean initialize, final boolean normalizeWhitespace)
626            throws ClassNotFoundException {
627        // This method was re-written to avoid recursion and stack overflows found by fuzz testing.
628        String next = className;
629        int lastDotIndex = -1;
630        do {
631            try {
632                final Class<?> clazz = getPrimitiveClass(next);
633                return clazz != null ? clazz : Class.forName(normalizeWhitespace ? toCleanName(next) : toEncodedName(next), initialize, classLoader);
634            } catch (final ClassNotFoundException ex) {
635                lastDotIndex = next.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
636                if (lastDotIndex != -1) {
637                    next = next.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR_CHAR + next.substring(lastDotIndex + 1);
638                }
639            }
640        } while (lastDotIndex != -1);
641        throw new ClassNotFoundException(className);
642    }
643
644    /**
645     * Gets the (initialized) class represented by {@code className} using the current thread's context class loader.
646     * This implementation supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
647     * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
648     * <p>
649     * The provided class name is normalized by removing all whitespace. This is especially helpful when handling XML element values in which whitespace has not
650     * been collapsed.
651     * </p>
652     * <p>
653     * <strong>Security note:</strong> because all whitespace is deleted before the class is resolved (and a failing {@code '.'} may be retried as {@code '$'}
654     * for inner classes), many distinct input strings resolve to the same class, while {@link Class#forName(String)} performs no such normalization. Validating
655     * an untrusted class name by string comparison <em>before</em> calling this method is therefore unsound: for example, {@code " java.lang.Runtime"} fails a
656     * naive {@code startsWith("java.")} denylist check on the raw string, yet loads {@code java.lang.Runtime}. Validate the class name <em>after</em>
657     * normalization, validate the resolved {@link Class} object itself, or use {@link #getClassStrict(ClassLoader, String, boolean)} which performs no
658     * whitespace normalization.
659     * </p>
660     *
661     * @param className The class name
662     * @return The class represented by {@code className} using the current thread's context class loader
663     * @throws NullPointerException Thrown if the className is null.
664     * @throws ClassNotFoundException Thrown if the class is not found.
665     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
666     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
667     * @see Class#forName(String, boolean, ClassLoader)
668     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
669     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
670     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
671     */
672    public static Class<?> getClass(final String className) throws ClassNotFoundException {
673        return getClass(className, true);
674    }
675
676    /**
677     * Gets the class represented by {@code className} using the current thread's context class loader. This
678     * implementation supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
679     * "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
680     * <p>
681     * The provided class name is normalized by removing all whitespace. This is especially helpful when handling XML element values in which whitespace has not
682     * been collapsed.
683     * </p>
684     * <p>
685     * <strong>Security note:</strong> because all whitespace is deleted before the class is resolved (and a failing {@code '.'} may be retried as {@code '$'}
686     * for inner classes), many distinct input strings resolve to the same class, while {@link Class#forName(String)} performs no such normalization. Validating
687     * an untrusted class name by string comparison <em>before</em> calling this method is therefore unsound: for example, {@code " java.lang.Runtime"} fails a
688     * naive {@code startsWith("java.")} denylist check on the raw string, yet loads {@code java.lang.Runtime}. Validate the class name <em>after</em>
689     * normalization, validate the resolved {@link Class} object itself, or use {@link #getClassStrict(ClassLoader, String, boolean)} which performs no
690     * whitespace normalization.
691     * </p>
692     *
693     * @param className The class name.
694     * @param initialize whether the class must be initialized.
695     * @return The class represented by {@code className} using the current thread's context class loader.
696     * @throws NullPointerException Thrown if the className is null.
697     * @throws ClassNotFoundException Thrown if the class is not found.
698     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
699     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
700     * @see Class#forName(String, boolean, ClassLoader)
701     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
702     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
703     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
704     */
705    public static Class<?> getClass(final String className, final boolean initialize) throws ClassNotFoundException {
706        final ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
707        final ClassLoader loader = contextCL == null ? ClassUtils.class.getClassLoader() : contextCL;
708        return getClass(loader, className, initialize);
709    }
710
711    /**
712     * Gets the class represented by {@code className} using the {@code classLoader}, without normalizing the class name.
713     * <p>
714     * Unlike {@link #getClass(ClassLoader, String, boolean)}, this method does <em>not</em> delete whitespace from the class name: a name that differs from
715     * the intended binary name only by whitespace throws {@link ClassNotFoundException}, matching {@link Class#forName(String, boolean, ClassLoader)}. Use
716     * this variant when the class name may come from an untrusted source, so that host-side string validation of the raw name cannot be bypassed through
717     * whitespace the loader would otherwise silently remove.
718     * </p>
719     * <p>
720     * The syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}"
721     * are still supported: a failing {@code '.'} is retried as {@code '$'} to find inner classes, so more than one dotted spelling can still resolve to the
722     * same inner class. When validating untrusted names, prefer validating the resolved {@link Class} object.
723     * </p>
724     *
725     * @param classLoader The class loader to use to load the class.
726     * @param className The class name.
727     * @param initialize whether the class must be initialized.
728     * @return The class represented by {@code className} using the {@code classLoader}.
729     * @throws NullPointerException Thrown if the className is null.
730     * @throws ClassNotFoundException Thrown if the class is not found.
731     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
732     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
733     * @see Class#forName(String, boolean, ClassLoader)
734     * @see #getClass(ClassLoader, String, boolean)
735     * @since 3.21.0
736     */
737    public static Class<?> getClassStrict(final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException {
738        return getClass(classLoader, className, initialize, false);
739    }
740
741    /**
742     * Gets the (initialized) class represented by {@code className} using the current thread's context class loader, without normalizing the class name.
743     * <p>
744     * Unlike {@link #getClass(String)}, this method does <em>not</em> delete whitespace from the class name: a name that differs from the intended binary
745     * name only by whitespace throws {@link ClassNotFoundException}, matching {@link Class#forName(String)}. Use this variant when the class name may come
746     * from an untrusted source, so that host-side string validation of the raw name cannot be bypassed through whitespace the loader would otherwise silently
747     * remove.
748     * </p>
749     * <p>
750     * The syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}"
751     * are still supported: a failing {@code '.'} is retried as {@code '$'} to find inner classes, so more than one dotted spelling can still resolve to the
752     * same inner class. When validating untrusted names, prefer validating the resolved {@link Class} object.
753     * </p>
754     *
755     * @param className The class name.
756     * @return The class represented by {@code className} using the current thread's context class loader.
757     * @throws NullPointerException Thrown if the className is null.
758     * @throws ClassNotFoundException Thrown if the class is not found.
759     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
760     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
761     * @see Class#forName(String)
762     * @see #getClass(String)
763     * @since 3.21.0
764     */
765    public static Class<?> getClassStrict(final String className) throws ClassNotFoundException {
766        final ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
767        final ClassLoader loader = contextCL == null ? ClassUtils.class.getClassLoader() : contextCL;
768        return getClassStrict(loader, className, true);
769    }
770
771    /**
772     * Gets the array component type using {@link Class#getComponentType()} with generics.
773     *
774     * @param <T> The array class type.
775     * @param cls A class or null.
776     * @return The array component type or null.
777     * @see Class#getComponentType()
778     * @since 3.13.0
779     */
780    @SuppressWarnings("unchecked")
781    public static <T> Class<T> getComponentType(final Class<T[]> cls) {
782        return cls == null ? null : (Class<T>) cls.getComponentType();
783    }
784
785    /**
786     * Gets the class name, handling {@code null} safely.
787     *
788     * @param cls The class for which to get the class name; may be null.
789     * @return The class name or the empty string in case the argument is {@code null}.
790     * @since 3.7
791     * @see Class#getSimpleName()
792     */
793    public static String getName(final Class<?> cls) {
794        return getName(cls, StringUtils.EMPTY);
795    }
796
797    /**
798     * Gets the class name, handling {@code null} safely.
799     *
800     * @param cls The class for which to get the class name; may be null.
801     * @param valueIfNull The return value if the argument {@code cls} is {@code null}.
802     * @return The class name or {@code valueIfNull}
803     * @since 3.7
804     * @see Class#getName()
805     */
806    public static String getName(final Class<?> cls, final String valueIfNull) {
807        return getName(cls, valueIfNull, false);
808    }
809
810    static String getName(final Class<?> cls, final String valueIfNull, final boolean simple) {
811        return cls == null ? valueIfNull : simple ? cls.getSimpleName() : cls.getName();
812    }
813
814    /**
815     * Gets the object's class name, handling {@code null} safely.
816     *
817     * @param object The object for which to get the class name; may be null.
818     * @return The class name or the empty String.
819     * @since 3.7
820     * @see Class#getSimpleName()
821     */
822    public static String getName(final Object object) {
823        return getName(object, StringUtils.EMPTY);
824    }
825
826    /**
827     * Gets the object's class name, handling {@code null} safely.
828     *
829     * @param object The object for which to get the class name; may be null.
830     * @param valueIfNull The value to return if {@code object} is {@code null}.
831     * @return The class name or {@code valueIfNull}.
832     * @since 3.0
833     * @see Class#getName()
834     */
835    public static String getName(final Object object, final String valueIfNull) {
836        return object == null ? valueIfNull : object.getClass().getName();
837    }
838
839    /**
840     * Gets the package name from the canonical name of a {@link Class}.
841     *
842     * @param cls The class to get the package name for, may be {@code null}.
843     * @return The package name or an empty string.
844     * @since 2.4
845     */
846    public static String getPackageCanonicalName(final Class<?> cls) {
847        if (cls == null) {
848            return StringUtils.EMPTY;
849        }
850        return getPackageCanonicalName(cls.getName());
851    }
852
853    /**
854     * Gets the package name from the class name of an {@link Object}.
855     *
856     * @param object The class to get the package name for, may be null.
857     * @param valueIfNull The value to return if null.
858     * @return The package name of the object, or the null value.
859     * @since 2.4
860     */
861    public static String getPackageCanonicalName(final Object object, final String valueIfNull) {
862        if (object == null) {
863            return valueIfNull;
864        }
865        return getPackageCanonicalName(object.getClass().getName());
866    }
867
868    /**
869     * Gets the package name from the class name.
870     *
871     * <p>
872     * The string passed in is assumed to be a class name - it is not checked.
873     * </p>
874     * <p>
875     * If the class is in the default package, return an empty string.
876     * </p>
877     *
878     * @param name The name to get the package name for, may be {@code null}.
879     * @return The package name or an empty string.
880     * @since 2.4
881     */
882    public static String getPackageCanonicalName(final String name) {
883        return getPackageName(getCanonicalName(name));
884    }
885
886    /**
887     * Gets the package name of a {@link Class}.
888     *
889     * @param cls The class to get the package name for, may be {@code null}.
890     * @return The package name or an empty string
891     */
892    public static String getPackageName(final Class<?> cls) {
893        if (cls == null) {
894            return StringUtils.EMPTY;
895        }
896        return getPackageName(cls.getName());
897    }
898
899    /**
900     * Gets the package name of an {@link Object}.
901     *
902     * @param object The class to get the package name for, may be null.
903     * @param valueIfNull The value to return if null.
904     * @return The package name of the object, or the null value.
905     */
906    public static String getPackageName(final Object object, final String valueIfNull) {
907        if (object == null) {
908            return valueIfNull;
909        }
910        return getPackageName(object.getClass());
911    }
912
913    /**
914     * Gets the package name from a {@link String}.
915     *
916     * <p>
917     * The string passed in is assumed to be a class name.
918     * </p>
919     * <p>
920     * If the class is unpackaged, return an empty string.
921     * </p>
922     *
923     * @param className The className to get the package name for, may be {@code null}.
924     * @return The package name or an empty string.
925     */
926    public static String getPackageName(String className) {
927        if (StringUtils.isEmpty(className)) {
928            return StringUtils.EMPTY;
929        }
930        int i = 0;
931        // Strip array encoding
932        while (className.charAt(i) == '[') {
933            i++;
934        }
935        className = className.substring(i);
936        // Strip Object type encoding
937        if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
938            className = className.substring(1);
939        }
940        i = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
941        if (i == -1) {
942            return StringUtils.EMPTY;
943        }
944        return className.substring(0, i);
945    }
946
947    /**
948     * Gets the primitive class for the given class name, for example "byte".
949     *
950     * @param className The primitive class for the given class name.
951     * @return The primitive class.
952     */
953    static Class<?> getPrimitiveClass(final String className) {
954        return NAME_PRIMITIVE_MAP.get(className);
955    }
956
957    /**
958     * Gets the desired Method much like {@code Class.getMethod}, however it ensures that the returned Method is from a
959     * public class or interface and not from an anonymous inner class. This means that the Method is invokable and doesn't
960     * fall foul of Java bug (<a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4071957">4071957</a>).
961     *
962     * <pre>
963     *  {@code Set set = Collections.unmodifiableSet(...);
964     *  Method method = ClassUtils.getPublicMethod(set.getClass(), "isEmpty",  new Class[0]);
965     *  Object result = method.invoke(set, new Object[]);}
966     * </pre>
967     *
968     * @param cls The class to check, not null.
969     * @param methodName The name of the method.
970     * @param parameterTypes The list of parameters.
971     * @return The method.
972     * @throws NullPointerException Thrown if the class is null.
973     * @throws SecurityException Thrown if a security violation occurred.
974     * @throws NoSuchMethodException Thrown if the method is not found in the given class or if the method doesn't conform with the
975     *         requirements.
976     */
977    public static Method getPublicMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) throws NoSuchMethodException {
978        final Method declaredMethod = cls.getMethod(methodName, parameterTypes);
979        if (isPublic(declaredMethod.getDeclaringClass())) {
980            return declaredMethod;
981        }
982        final List<Class<?>> candidateClasses = new ArrayList<>(getAllInterfaces(cls));
983        candidateClasses.addAll(getAllSuperclasses(cls));
984        for (final Class<?> candidateClass : candidateClasses) {
985            if (!isPublic(candidateClass)) {
986                continue;
987            }
988            final Method candidateMethod;
989            try {
990                candidateMethod = candidateClass.getMethod(methodName, parameterTypes);
991            } catch (final NoSuchMethodException ex) {
992                continue;
993            }
994            if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) {
995                return candidateMethod;
996            }
997        }
998        throw new NoSuchMethodException("Can't find a public method for " + methodName + " " + ArrayUtils.toString(parameterTypes));
999    }
1000
1001    /**
1002     * Gets the canonical name minus the package name from a {@link Class}.
1003     *
1004     * @param cls The class for which to get the short canonical class name; may be null.
1005     * @return The canonical name without the package name or an empty string.
1006     * @since 2.4
1007     * @see Class#getCanonicalName()
1008     */
1009    public static String getShortCanonicalName(final Class<?> cls) {
1010        return cls == null ? StringUtils.EMPTY : getShortCanonicalName(cls.getCanonicalName());
1011    }
1012
1013    /**
1014     * Gets the canonical name minus the package name for an {@link Object}.
1015     *
1016     * @param object The class to get the short name for, may be null.
1017     * @param valueIfNull The value to return if null.
1018     * @return The canonical name of the object without the package name, or the null value.
1019     * @since 2.4
1020     * @see Class#getCanonicalName()
1021     */
1022    public static String getShortCanonicalName(final Object object, final String valueIfNull) {
1023        return object == null ? valueIfNull : getShortCanonicalName(object.getClass());
1024    }
1025
1026    /**
1027     * Gets the canonical name minus the package name from a String.
1028     *
1029     * <p>
1030     * The string passed in is assumed to be a class name - it is not checked.
1031     * </p>
1032     *
1033     * <p>
1034     * Note that this method is mainly designed to handle the arrays and primitives properly. If the class is an inner class
1035     * then the result value will not contain the outer classes. This way the behavior of this method is different from
1036     * {@link #getShortClassName(String)}. The argument in that case is class name and not canonical name and the return
1037     * value retains the outer classes.
1038     * </p>
1039     *
1040     * <p>
1041     * Note that there is no way to reliably identify the part of the string representing the package hierarchy and the part
1042     * that is the outer class or classes in case of an inner class. Trying to find the class would require reflective call
1043     * and the class itself may not even be on the class path. Relying on the fact that class names start with capital
1044     * letter and packages with lower case is heuristic.
1045     * </p>
1046     *
1047     * <p>
1048     * It is recommended to use {@link #getShortClassName(String)} for cases when the class is an inner class and use this
1049     * method for cases it is designed for.
1050     * </p>
1051     *
1052     * <table>
1053     * <caption>Examples</caption>
1054     * <tr>
1055     * <td>return value</td>
1056     * <td>input</td>
1057     * </tr>
1058     * <tr>
1059     * <td>{@code ""}</td>
1060     * <td>{@code (String) null}</td>
1061     * </tr>
1062     * <tr>
1063     * <td>{@code "Map.Entry"}</td>
1064     * <td>{@code java.util.Map.Entry.class.getName()}</td>
1065     * </tr>
1066     * <tr>
1067     * <td>{@code "Entry"}</td>
1068     * <td>{@code java.util.Map.Entry.class.getCanonicalName()}</td>
1069     * </tr>
1070     * <tr>
1071     * <td>{@code "ClassUtils"}</td>
1072     * <td>{@code "org.apache.commons.lang3.ClassUtils"}</td>
1073     * </tr>
1074     * <tr>
1075     * <td>{@code "ClassUtils[]"}</td>
1076     * <td>{@code "[Lorg.apache.commons.lang3.ClassUtils;"}</td>
1077     * </tr>
1078     * <tr>
1079     * <td>{@code "ClassUtils[][]"}</td>
1080     * <td>{@code "[[Lorg.apache.commons.lang3.ClassUtils;"}</td>
1081     * </tr>
1082     * <tr>
1083     * <td>{@code "ClassUtils[]"}</td>
1084     * <td>{@code "org.apache.commons.lang3.ClassUtils[]"}</td>
1085     * </tr>
1086     * <tr>
1087     * <td>{@code "ClassUtils[][]"}</td>
1088     * <td>{@code "org.apache.commons.lang3.ClassUtils[][]"}</td>
1089     * </tr>
1090     * <tr>
1091     * <td>{@code "int[]"}</td>
1092     * <td>{@code "[I"}</td>
1093     * </tr>
1094     * <tr>
1095     * <td>{@code "int[]"}</td>
1096     * <td>{@code int[].class.getCanonicalName()}</td>
1097     * </tr>
1098     * <tr>
1099     * <td>{@code "int[]"}</td>
1100     * <td>{@code int[].class.getName()}</td>
1101     * </tr>
1102     * <tr>
1103     * <td>{@code "int[][]"}</td>
1104     * <td>{@code "[[I"}</td>
1105     * </tr>
1106     * <tr>
1107     * <td>{@code "int[]"}</td>
1108     * <td>{@code "int[]"}</td>
1109     * </tr>
1110     * <tr>
1111     * <td>{@code "int[][]"}</td>
1112     * <td>{@code "int[][]"}</td>
1113     * </tr>
1114     * </table>
1115     *
1116     * @param canonicalName The class name to get the short name for.
1117     * @return The canonical name of the class without the package name or an empty string.
1118     * @since 2.4
1119     */
1120    public static String getShortCanonicalName(final String canonicalName) {
1121        return getShortClassName(getCanonicalName(canonicalName));
1122    }
1123
1124    /**
1125     * Gets the class name minus the package name from a {@link Class}.
1126     *
1127     * @param cls The class to get the short name for.
1128     * @return The class name without the package name or an empty string. If the class is an inner class then the returned
1129     *         value will contain the outer class or classes separated with {@code .} (dot) character.
1130     */
1131    public static String getShortClassName(final Class<?> cls) {
1132        if (cls == null) {
1133            return StringUtils.EMPTY;
1134        }
1135        int dim = 0;
1136        Class<?> c = cls;
1137        while (c.isArray()) {
1138            dim++;
1139            c = c.getComponentType();
1140        }
1141        String base;
1142        // c.isAnonymousClass() / isLocalClass() and the getDeclaringClass() chain
1143        // can both throw NoClassDefFoundError when the enclosing class is
1144        // missing from the classpath, so the try/catch wraps the whole block.
1145        try {
1146            // Preserve legacy behavior for anonymous/local classes (keeps compiler ordinals: $13, $10Named, etc.)
1147            if (c.isAnonymousClass() || c.isLocalClass()) {
1148                base = getShortClassName(c.getName());
1149            } else {
1150                final Deque<String> parts = new ArrayDeque<>();
1151                Class<?> x = c;
1152                while (x != null) {
1153                    parts.push(x.getSimpleName());
1154                    x = x.getDeclaringClass();
1155                }
1156                base = String.join(".", parts);
1157            }
1158        } catch (final NoClassDefFoundError ignored) {
1159            base = getShortClassName(c.getName());
1160        }
1161        return base + StringUtils.repeat("[]", dim);
1162    }
1163
1164    /**
1165     * Gets the class name of the {@code object} without the package name or names.
1166     *
1167     * @param object The class to get the short name for, may be {@code null}.
1168     * @param valueIfNull The value to return if the object is {@code null}.
1169     * @return The class name of the object without the package name, or {@code valueIfNull} if the argument {@code object}
1170     *         is {@code null}.
1171     */
1172    public static String getShortClassName(final Object object, final String valueIfNull) {
1173        if (object == null) {
1174            return valueIfNull;
1175        }
1176        return getShortClassName(object.getClass());
1177    }
1178
1179    /**
1180     * Gets the class name minus the package name from a String.
1181     *
1182     * <p>
1183     * The string passed in is assumed to be a class name - it is not checked. The string has to be formatted the way as the
1184     * JDK method {@code Class.getName()} returns it, and not the usual way as we write it, for example in import
1185     * statements, or as it is formatted by {@code Class.getCanonicalName()}.
1186     * </p>
1187     *
1188     * <p>
1189     * The difference is significant only in case of classes that are inner classes of some other classes. In this case
1190     * the separator between the outer and inner class (possibly on multiple hierarchy level) has to be {@code $} (dollar
1191     * sign) and not {@code .} (dot), as it is returned by {@code Class.getName()}
1192     * </p>
1193     *
1194     * <p>
1195     * Note that this method is called from the {@link #getShortClassName(Class)} method using the string returned by
1196     * {@code Class.getName()}.
1197     * </p>
1198     *
1199     * <p>
1200     * Note that this method differs from {@link #getSimpleName(Class)} in that this will return, for example
1201     * {@code "Map.Entry"} whilst the {@link Class} variant will simply return {@code "Entry"}. In this example
1202     * the argument {@code className} is the string {@code java.util.Map$Entry} (note the {@code $} sign).
1203     * </p>
1204     *
1205     * @param className The className to get the short name for. It has to be formatted as returned by
1206     *        {@code Class.getName()} and not {@code Class.getCanonicalName()}.
1207     * @return The class name of the class without the package name or an empty string. If the class is an inner class then
1208     *         value contains the outer class or classes and the separator is replaced to be {@code .} (dot) character.
1209     */
1210    public static String getShortClassName(String className) {
1211        if (StringUtils.isEmpty(className)) {
1212            return StringUtils.EMPTY;
1213        }
1214        final StringBuilder arrayPrefix = new StringBuilder();
1215        // Handle array encoding
1216        if (className.startsWith("[")) {
1217            while (className.charAt(0) == '[') {
1218                className = className.substring(1);
1219                arrayPrefix.append("[]");
1220            }
1221            // Strip Object type encoding
1222            if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
1223                className = className.substring(1, className.length() - 1);
1224            }
1225            if (REVERSE_ABBREVIATION_MAP.containsKey(className)) {
1226                className = REVERSE_ABBREVIATION_MAP.get(className);
1227            }
1228        }
1229        final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
1230        final int innerIdx = className.indexOf(INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
1231        String out = className.substring(lastDotIdx + 1);
1232        if (innerIdx != -1) {
1233            out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
1234        }
1235        return out + arrayPrefix;
1236    }
1237
1238    /**
1239     * Gets the simple class name, handling {@code null} safely.
1240     *
1241     * @param cls The class for which to get the simple name; may be null.
1242     * @return The simple class name or the empty string in case the argument is {@code null}.
1243     * @since 3.0
1244     * @see Class#getSimpleName()
1245     */
1246    public static String getSimpleName(final Class<?> cls) {
1247        return getSimpleName(cls, StringUtils.EMPTY);
1248    }
1249
1250    /**
1251     * Gets the simple class name, handling {@code null} safely.
1252     *
1253     * @param cls The class for which to get the simple name; may be null.
1254     * @param valueIfNull The value to return if null.
1255     * @return The simple class name or {@code valueIfNull} if the argument {@code cls} is {@code null}.
1256     * @since 3.0
1257     * @see Class#getSimpleName()
1258     */
1259    public static String getSimpleName(final Class<?> cls, final String valueIfNull) {
1260        return cls == null ? valueIfNull : cls.getSimpleName();
1261    }
1262
1263    /**
1264     * Gets the object's simple class name, handling {@code null} safely.
1265     *
1266     * <p>
1267     * It is to note that this method is overloaded and in case the argument {@code object} is a {@link Class} object then
1268     * the {@link #getSimpleName(Class)} will be invoked. If this is a significant possibility then the caller should check
1269     * this case and call {@code
1270     * getSimpleName(Class.class)} or just simply use the string literal {@code "Class"}, which is the result of the method
1271     * in that case.
1272     * </p>
1273     *
1274     * @param object The object for which to get the simple class name; may be null.
1275     * @return The simple class name or the empty string in case the argument is {@code null}.
1276     * @since 3.7
1277     * @see Class#getSimpleName()
1278     */
1279    public static String getSimpleName(final Object object) {
1280        return getSimpleName(object, StringUtils.EMPTY);
1281    }
1282
1283    /**
1284     * Gets the object's simple class name, handling {@code null} safely.
1285     *
1286     * @param object The object for which to get the simple class name; may be null.
1287     * @param valueIfNull The value to return if {@code object} is {@code null}.
1288     * @return The simple class name or {@code valueIfNull} if the argument {@code object} is {@code null}.
1289     * @since 3.0
1290     * @see Class#getSimpleName()
1291     */
1292    public static String getSimpleName(final Object object, final String valueIfNull) {
1293        return object == null ? valueIfNull : object.getClass().getSimpleName();
1294    }
1295
1296    /**
1297     * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order,
1298     * excluding interfaces.
1299     *
1300     * @param type The type to get the class hierarchy from.
1301     * @return Iterable an Iterable over the class hierarchy of the given class.
1302     * @since 3.2
1303     */
1304    public static Iterable<Class<?>> hierarchy(final Class<?> type) {
1305        return hierarchy(type, Interfaces.EXCLUDE);
1306    }
1307
1308    /**
1309     * Gets an {@link Iterable} that can iterate over a class hierarchy in ascending (subclass to superclass) order.
1310     *
1311     * @param type The type to get the class hierarchy from.
1312     * @param interfacesBehavior switch indicating whether to include or exclude interfaces.
1313     * @return Iterable an Iterable over the class hierarchy of the given class.
1314     * @since 3.2
1315     */
1316    public static Iterable<Class<?>> hierarchy(final Class<?> type, final Interfaces interfacesBehavior) {
1317        final Iterable<Class<?>> classes = () -> {
1318            final AtomicReference<Class<?>> next = new AtomicReference<>(type);
1319            return new Iterator<Class<?>>() {
1320
1321                @Override
1322                public boolean hasNext() {
1323                    return next.get() != null;
1324                }
1325
1326                @Override
1327                public Class<?> next() {
1328                    return next.getAndUpdate(Class::getSuperclass);
1329                }
1330
1331                @Override
1332                public void remove() {
1333                    throw new UnsupportedOperationException();
1334                }
1335
1336            };
1337        };
1338        if (interfacesBehavior != Interfaces.INCLUDE) {
1339            return classes;
1340        }
1341        return () -> {
1342            final Set<Class<?>> seenInterfaces = new HashSet<>();
1343            final Iterator<Class<?>> wrapped = classes.iterator();
1344
1345            return new Iterator<Class<?>>() {
1346                Iterator<Class<?>> interfaces = Collections.emptyIterator();
1347
1348                @Override
1349                public boolean hasNext() {
1350                    return interfaces.hasNext() || wrapped.hasNext();
1351                }
1352
1353                @Override
1354                public Class<?> next() {
1355                    if (interfaces.hasNext()) {
1356                        final Class<?> nextInterface = interfaces.next();
1357                        seenInterfaces.add(nextInterface);
1358                        return nextInterface;
1359                    }
1360                    final Class<?> nextSuperclass = wrapped.next();
1361                    final Set<Class<?>> currentInterfaces = new LinkedHashSet<>();
1362                    walkInterfaces(currentInterfaces, nextSuperclass);
1363                    interfaces = currentInterfaces.iterator();
1364                    return nextSuperclass;
1365                }
1366
1367                @Override
1368                public void remove() {
1369                    throw new UnsupportedOperationException();
1370                }
1371
1372                private void walkInterfaces(final Set<Class<?>> addTo, final Class<?> c) {
1373                    for (final Class<?> iface : c.getInterfaces()) {
1374                        if (!seenInterfaces.contains(iface)) {
1375                            addTo.add(iface);
1376                        }
1377                        walkInterfaces(addTo, iface);
1378                    }
1379                }
1380
1381            };
1382        };
1383    }
1384
1385    /**
1386     * Tests whether one {@link Class} can be assigned to a variable of another {@link Class}.
1387     *
1388     * <p>
1389     * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
1390     * primitive classes and {@code null}s.
1391     * </p>
1392     *
1393     * <p>
1394     * Primitive widenings allow an int to be assigned to a long, float or double. This method returns the correct result
1395     * for these cases.
1396     * </p>
1397     *
1398     * <p>
1399     * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
1400     * and the toClass is non-primitive.
1401     * </p>
1402     *
1403     * <p>
1404     * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
1405     * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
1406     * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
1407     * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
1408     * </p>
1409     *
1410     * <p>
1411     * <strong>Since Lang 3.0,</strong> this method will default behavior for calculating assignability between primitive
1412     * and wrapper types <em>corresponding to the running Java version</em>; i.e. autoboxing will be the default behavior in
1413     * VMs running Java versions &gt; 1.5.
1414     * </p>
1415     *
1416     * @param cls The Class to check, may be null.
1417     * @param toClass The Class to try to assign into, returns false if null.
1418     * @return {@code true} if assignment possible.
1419     */
1420    public static boolean isAssignable(final Class<?> cls, final Class<?> toClass) {
1421        return isAssignable(cls, toClass, true);
1422    }
1423
1424    /**
1425     * Tests whether one {@link Class} can be assigned to a variable of another {@link Class}.
1426     *
1427     * <p>
1428     * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
1429     * primitive classes and {@code null}s.
1430     * </p>
1431     *
1432     * <p>
1433     * Primitive widenings allow an int to be assigned to a long, float or double. This method returns the correct result
1434     * for these cases.
1435     * </p>
1436     *
1437     * <p>
1438     * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
1439     * and the toClass is non-primitive.
1440     * </p>
1441     *
1442     * <p>
1443     * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
1444     * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
1445     * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
1446     * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
1447     * </p>
1448     *
1449     * @param cls The Class to check, may be null.
1450     * @param toClass The Class to try to assign into, returns false if null.
1451     * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers.
1452     * @return {@code true} if assignment possible.
1453     */
1454    public static boolean isAssignable(Class<?> cls, final Class<?> toClass, final boolean autoboxing) {
1455        if (toClass == null) {
1456            return false;
1457        }
1458        // have to check for null, as isAssignableFrom doesn't
1459        if (cls == null) {
1460            return !toClass.isPrimitive();
1461        }
1462        // autoboxing:
1463        if (autoboxing) {
1464            if (cls.isPrimitive() && !toClass.isPrimitive()) {
1465                cls = primitiveToWrapper(cls);
1466                if (cls == null) {
1467                    return false;
1468                }
1469            }
1470            if (toClass.isPrimitive() && !cls.isPrimitive()) {
1471                cls = wrapperToPrimitive(cls);
1472                if (cls == null) {
1473                    return false;
1474                }
1475            }
1476        }
1477        if (cls.equals(toClass)) {
1478            return true;
1479        }
1480        if (cls.isPrimitive()) {
1481            if (!toClass.isPrimitive()) {
1482                return false;
1483            }
1484            if (Integer.TYPE.equals(cls)) {
1485                return Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
1486            }
1487            if (Long.TYPE.equals(cls)) {
1488                return Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
1489            }
1490            if (Boolean.TYPE.equals(cls)) {
1491                return false;
1492            }
1493            if (Double.TYPE.equals(cls)) {
1494                return false;
1495            }
1496            if (Float.TYPE.equals(cls)) {
1497                return Double.TYPE.equals(toClass);
1498            }
1499            if (Character.TYPE.equals(cls)  || Short.TYPE.equals(cls)) {
1500                return Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass);
1501            }
1502            if (Byte.TYPE.equals(cls)) {
1503                return Short.TYPE.equals(toClass) || Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass)
1504                    || Double.TYPE.equals(toClass);
1505            }
1506            // should never get here
1507            return false;
1508        }
1509        return toClass.isAssignableFrom(cls);
1510    }
1511
1512    /**
1513     * Tests whether an array of Classes can be assigned to another array of Classes.
1514     *
1515     * <p>
1516     * This method calls {@link #isAssignable(Class, Class) isAssignable} for each Class pair in the input arrays. It can be
1517     * used to check if a set of arguments (the first parameter) are suitably compatible with a set of method parameter
1518     * types (the second parameter).
1519     * </p>
1520     *
1521     * <p>
1522     * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
1523     * primitive classes and {@code null}s.
1524     * </p>
1525     *
1526     * <p>
1527     * Primitive widenings allow an int to be assigned to a {@code long}, {@code float} or {@code double}. This method
1528     * returns the correct result for these cases.
1529     * </p>
1530     *
1531     * <p>
1532     * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
1533     * and the toClass is non-primitive.
1534     * </p>
1535     *
1536     * <p>
1537     * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
1538     * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
1539     * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
1540     * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
1541     * </p>
1542     *
1543     * <p>
1544     * <strong>Since Lang 3.0,</strong> this method will default behavior for calculating assignability between primitive
1545     * and wrapper types <em>corresponding to the running Java version</em>; i.e. autoboxing will be the default behavior in
1546     * VMs running Java versions &gt; 1.5.
1547     * </p>
1548     *
1549     * @param classArray The array of Classes to check, may be {@code null}.
1550     * @param toClassArray The array of Classes to try to assign into, may be {@code null}.
1551     * @return {@code true} if assignment possible.
1552     */
1553    public static boolean isAssignable(final Class<?>[] classArray, final Class<?>... toClassArray) {
1554        return isAssignable(classArray, toClassArray, true);
1555    }
1556
1557    /**
1558     * Tests whether an array of Classes can be assigned to another array of Classes.
1559     *
1560     * <p>
1561     * This method calls {@link #isAssignable(Class, Class) isAssignable} for each Class pair in the input arrays. It can be
1562     * used to check if a set of arguments (the first parameter) are suitably compatible with a set of method parameter
1563     * types (the second parameter).
1564     * </p>
1565     *
1566     * <p>
1567     * Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into account widenings of
1568     * primitive classes and {@code null}s.
1569     * </p>
1570     *
1571     * <p>
1572     * Primitive widenings allow an int to be assigned to a {@code long}, {@code float} or {@code double}. This method
1573     * returns the correct result for these cases.
1574     * </p>
1575     *
1576     * <p>
1577     * {@code null} may be assigned to any reference type. This method will return {@code true} if {@code null} is passed in
1578     * and the toClass is non-primitive.
1579     * </p>
1580     *
1581     * <p>
1582     * Specifically, this method tests whether the type represented by the specified {@link Class} parameter can be
1583     * converted to the type represented by this {@link Class} object via an identity conversion widening primitive or
1584     * widening reference conversion. See <em><a href="https://docs.oracle.com/javase/specs/">The Java Language
1585     * Specification</a></em>, sections 5.1.1, 5.1.2 and 5.1.4 for details.
1586     * </p>
1587     *
1588     * @param classArray The array of Classes to check, may be {@code null}
1589     * @param toClassArray The array of Classes to try to assign into, may be {@code null}
1590     * @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
1591     * @return {@code true} if assignment possible
1592     */
1593    public static boolean isAssignable(Class<?>[] classArray, Class<?>[] toClassArray, final boolean autoboxing) {
1594        if (!ArrayUtils.isSameLength(classArray, toClassArray)) {
1595            return false;
1596        }
1597        classArray = ArrayUtils.nullToEmpty(classArray);
1598        toClassArray = ArrayUtils.nullToEmpty(toClassArray);
1599        for (int i = 0; i < classArray.length; i++) {
1600            if (!isAssignable(classArray[i], toClassArray[i], autoboxing)) {
1601                return false;
1602            }
1603        }
1604        return true;
1605    }
1606
1607    /**
1608     * Tests whether the specified class an inner class or static nested class.
1609     *
1610     * @param cls The class to check, may be null.
1611     * @return {@code true} if the class is an inner or static nested class, false if not or {@code null}.
1612     */
1613    public static boolean isInnerClass(final Class<?> cls) {
1614        return cls != null && cls.getEnclosingClass() != null;
1615    }
1616
1617    /**
1618     * Tests whether the given {@code type} is a primitive or primitive wrapper ({@link Boolean}, {@link Byte},
1619     * {@link Character}, {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}).
1620     *
1621     * @param type The class to query or null.
1622     * @return true if the given {@code type} is a primitive or primitive wrapper ({@link Boolean}, {@link Byte},
1623     *         {@link Character}, {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}).
1624     * @since 3.1
1625     */
1626    public static boolean isPrimitiveOrWrapper(final Class<?> type) {
1627        return type != null && type.isPrimitive() || isPrimitiveWrapper(type);
1628    }
1629
1630    /**
1631     * Tests whether the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character},
1632     * {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}).
1633     *
1634     * @param type The class to query or null.
1635     * @return true if the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character},
1636     *         {@link Short}, {@link Integer}, {@link Long}, {@link Double}, {@link Float}).
1637     * @since 3.1
1638     */
1639    public static boolean isPrimitiveWrapper(final Class<?> type) {
1640        return WRAPPER_PRIMITIVE_MAP.containsKey(type);
1641    }
1642
1643    /**
1644     * Tests whether a {@link Class} is public.
1645     *
1646     * @param cls Class to test.
1647     * @return {@code true} if {@code cls} is public.
1648     * @since 3.13.0
1649     */
1650    public static boolean isPublic(final Class<?> cls) {
1651        return Modifier.isPublic(cls.getModifiers());
1652    }
1653
1654    /**
1655     * Converts the specified array of primitive Class objects to an array of its corresponding wrapper Class objects.
1656     *
1657     * @param classes The class array to convert, may be null or empty.
1658     * @return An array which contains for each given class, the wrapper class or the original class if class is not a primitive. {@code null} if null input.
1659     *         Empty array if an empty array passed in.
1660     * @since 2.1
1661     */
1662    public static Class<?>[] primitivesToWrappers(final Class<?>... classes) {
1663        if (classes == null) {
1664            return null;
1665        }
1666        if (classes.length == 0) {
1667            return classes;
1668        }
1669        return ArrayUtils.setAll(new Class[classes.length], i -> primitiveToWrapper(classes[i]));
1670    }
1671
1672    /**
1673     * Converts the specified primitive Class object to its corresponding wrapper Class object.
1674     *
1675     * <p>
1676     * NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
1677     * </p>
1678     *
1679     * @param cls The class to convert, may be null.
1680     * @return The wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive. {@code null} if null input.
1681     * @since 2.1
1682     */
1683    public static Class<?> primitiveToWrapper(final Class<?> cls) {
1684        return cls != null && cls.isPrimitive() ? PRIMITIVE_WRAPPER_MAP.get(cls) : cls;
1685    }
1686
1687    /**
1688     * Converts an array of {@link Object} in to an array of {@link Class} objects. If any of these objects is null, a null element will be inserted into the
1689     * array.
1690     *
1691     * <p>
1692     * This method returns {@code null} for a {@code null} input array.
1693     * </p>
1694     *
1695     * @param array An {@link Object} array.
1696     * @return A {@link Class} array, {@code null} if null array input.
1697     * @since 2.4
1698     */
1699    public static Class<?>[] toClass(final Object... array) {
1700        if (array == null) {
1701            return null;
1702        }
1703        if (array.length == 0) {
1704            return ArrayUtils.EMPTY_CLASS_ARRAY;
1705        }
1706        return ArrayUtils.setAll(new Class[array.length], i -> array[i] == null ? null : array[i].getClass());
1707    }
1708
1709    /**
1710     * Converts and cleans up a class name to a JLS style class name.
1711     * <p>
1712     * The provided class name is normalized by removing all whitespace. This is especially helpful when handling XML element values in which whitespace has not
1713     * been collapsed.
1714     * </p>
1715     *
1716     * @param className The class name.
1717     * @return The converted name.
1718     * @throws NullPointerException     Thrown if the className is null.
1719     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
1720     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
1721     * @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification
1722     *      CONSTANT_Class_info</a>
1723     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
1724     * @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
1725     */
1726    private static String toCleanName(final String className) {
1727        return toEncodedName(StringUtils.deleteWhitespace(className));
1728    }
1729
1730    /**
1731     * Converts a class name to a JLS style class name without normalizing whitespace.
1732     *
1733     * @param className The class name.
1734     * @return The converted name.
1735     * @throws NullPointerException     Thrown if the className is null.
1736     * @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
1737     * @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
1738     */
1739    private static String toEncodedName(final String className) {
1740        String canonicalName = className;
1741        Objects.requireNonNull(canonicalName, "className");
1742        if (canonicalName.isEmpty()) {
1743            throw new IllegalArgumentException("Class name is empty");
1744        }
1745        final String encodedArrayOpen = "[";
1746        final String encodedClassNameStart = "L";
1747        final String encodedClassNameEnd = ";";
1748        final boolean encodedName = canonicalName.startsWith(encodedArrayOpen) && canonicalName.endsWith(encodedClassNameEnd);
1749        if (encodedName) {
1750            final int arrIdx = canonicalName.indexOf(encodedClassNameStart);
1751            if (arrIdx > MAX_JVM_ARRAY_DIMENSION) {
1752                throw new IllegalArgumentException("Array dimension greater than JVM specification maximum of 255.");
1753            }
1754            if (arrIdx < 0) {
1755                throw new IllegalArgumentException("Expected 'L' after '[' for an array style string.");
1756            }
1757            final int cnLen = canonicalName.length() - (arrIdx + 2); // account for the ending ';'
1758            if (cnLen > MAX_CLASS_NAME_LENGTH) {
1759                throw new IllegalArgumentException(String.format("Class name greater than maximum length %,d", MAX_CLASS_NAME_LENGTH));
1760            }
1761        }
1762        final String arrayMarker = "[]";
1763        final int arrIdx = canonicalName.indexOf(arrayMarker);
1764        // The class name length without array markers.
1765        final int cnLen = arrIdx > 0 ? arrIdx : canonicalName.length();
1766        if (cnLen > MAX_CLASS_NAME_LENGTH && !encodedName) {
1767            throw new IllegalArgumentException(String.format("Class name greater than maximum length %,d", MAX_CLASS_NAME_LENGTH));
1768        }
1769        if (canonicalName.endsWith(arrayMarker)) {
1770            // Reject malformed inputs like "java.lang.String[]junk[]" or
1771            // "java.lang.String[]][]" where the suffix is not composed of
1772            // repeated "[]" pairs.
1773            final String tail = canonicalName.substring(arrIdx);
1774            if (!ARRAY_TAIL_PATTERN.matcher(tail).matches()) {
1775                throw new IllegalArgumentException("Malformed array name: " + canonicalName);
1776            }
1777            final int dims =  (canonicalName.length() - arrIdx) / 2;
1778            if (dims > MAX_JVM_ARRAY_DIMENSION) {
1779                throw new IllegalArgumentException("Array dimension greater than JVM specification maximum of 255.");
1780            }
1781            final StringBuilder classNameBuffer = new StringBuilder(StringUtils.repeat(encodedArrayOpen, dims));
1782            canonicalName = canonicalName.substring(0, arrIdx);
1783            final String abbreviation = ABBREVIATION_MAP.get(canonicalName);
1784            if (abbreviation != null) {
1785                classNameBuffer.append(abbreviation);
1786            } else {
1787                classNameBuffer.append(encodedClassNameStart).append(canonicalName).append(encodedClassNameEnd);
1788            }
1789            canonicalName = classNameBuffer.toString();
1790        }
1791        return canonicalName;
1792    }
1793
1794    /**
1795     * Decides if the part that was just copied to its destination location in the work array can be kept as it was copied
1796     * or must be abbreviated. It must be kept when the part is the last one, which is the simple name of the class. In this
1797     * case the {@code source} index, from where the characters are copied points one position after the last character,
1798     * a.k.a. {@code source ==
1799     * originalLength}
1800     *
1801     * <p>
1802     * If the part is not the last one then it can be kept unabridged if the number of the characters copied so far plus the
1803     * character that are to be copied is less than or equal to the desired length.
1804     * </p>
1805     *
1806     * @param runAheadTarget The target index (where the characters were copied to) pointing after the last character copied
1807     *        when the current part was copied.
1808     * @param source The source index (where the characters were copied from) pointing after the last character copied when
1809     *        the current part was copied.
1810     * @param originalLength The original length of the class full name, which is abbreviated.
1811     * @param desiredLength The desired length of the abbreviated class name.
1812     * @return {@code true} if it can be kept in its original length; {@code false} if the current part has to be abbreviated.
1813     */
1814    private static boolean useFull(final int runAheadTarget, final int source, final int originalLength, final int desiredLength) {
1815        return source >= originalLength || runAheadTarget + originalLength - source <= desiredLength;
1816    }
1817
1818    /**
1819     * Converts the specified array of wrapper Class objects to an array of its corresponding primitive Class objects.
1820     *
1821     * <p>
1822     * This method invokes {@code wrapperToPrimitive()} for each element of the passed in array.
1823     * </p>
1824     *
1825     * @param classes The class array to convert, may be null or empty.
1826     * @return An array which contains for each given class, the primitive class or {@code null} if the original class is not a wrapper class.
1827     *         {@code null} if null input. Empty array if an empty array passed in.
1828     * @see #wrapperToPrimitive(Class)
1829     * @since 2.4
1830     */
1831    public static Class<?>[] wrappersToPrimitives(final Class<?>... classes) {
1832        if (classes == null) {
1833            return null;
1834        }
1835        if (classes.length == 0) {
1836            return classes;
1837        }
1838        return ArrayUtils.setAll(new Class[classes.length], i -> wrapperToPrimitive(classes[i]));
1839    }
1840
1841    /**
1842     * Converts the specified wrapper class to its corresponding primitive class.
1843     *
1844     * <p>
1845     * This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is a wrapper class for a
1846     * primitive type, this primitive type will be returned (e.g. {@code Integer.TYPE} for {@code Integer.class}). For other
1847     * classes, or if the parameter is {@code null}, the return value is {@code null}.
1848     * </p>
1849     *
1850     * @param cls The class to convert, may be {@code null}.
1851     * @return The corresponding primitive type if {@code cls} is a wrapper class, {@code null} otherwise.
1852     * @see #primitiveToWrapper(Class)
1853     * @since 2.4
1854     */
1855    public static Class<?> wrapperToPrimitive(final Class<?> cls) {
1856        return WRAPPER_PRIMITIVE_MAP.get(cls);
1857    }
1858
1859    /**
1860     * ClassUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
1861     * {@code ClassUtils.getShortClassName(cls)}.
1862     *
1863     * <p>
1864     * This constructor is public to permit tools that require a JavaBean instance to operate.
1865     * </p>
1866     *
1867     * @deprecated TODO Make private in 4.0.
1868     */
1869    @Deprecated
1870    public ClassUtils() {
1871        // empty
1872    }
1873
1874}