All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.util.ClassUtilities Maven / Gradle / Ivy

The newest version!
package com.cedarsoftware.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.Deque;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.PriorityQueue;
import java.util.Properties;
import java.util.Queue;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Stack;
import java.util.StringJoiner;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import com.cedarsoftware.util.convert.Converter;

import static com.cedarsoftware.util.ExceptionUtilities.safelyIgnoreException;

/**
 * A utility class providing various methods for working with Java {@link Class} objects and related operations.
 * 

* {@code ClassUtilities} includes functionalities such as: *

*
    *
  • Determining inheritance distance between two classes or interfaces ({@link #computeInheritanceDistance}).
  • *
  • Checking if a class is primitive or a primitive wrapper ({@link #isPrimitive}).
  • *
  • Converting between primitive types and their wrapper classes ({@link #toPrimitiveWrapperClass}).
  • *
  • Loading resources from the classpath as strings or byte arrays ({@link #loadResourceAsString} and {@link #loadResourceAsBytes}).
  • *
  • Providing custom mappings for class aliases ({@link #addPermanentClassAlias} and {@link #removePermanentClassAlias}).
  • *
  • Identifying whether all constructors in a class are private ({@link #areAllConstructorsPrivate}).
  • *
  • Finding the most specific matching class in an inheritance hierarchy ({@link #findClosest}).
  • *
* *

Inheritance Distance

*

* The {@link #computeInheritanceDistance(Class, Class)} method calculates the number of inheritance steps * between two classes or interfaces. If there is no relationship, it returns {@code -1}. *

* *

Primitive and Wrapper Handling

*
    *
  • Supports identification of primitive types and their wrappers.
  • *
  • Handles conversions between primitive types and their wrapper classes.
  • *
  • Considers primitive types and their wrappers interchangeable for certain operations.
  • *
* *

Resource Loading

*

* Includes methods for loading resources from the classpath as strings or byte arrays, throwing appropriate * exceptions if the resource cannot be found or read. *

* *

OSGi and JPMS ClassLoader Support

*

* Detects and supports environments such as OSGi or JPMS for proper class loading. Uses caching * for efficient retrieval of class loaders in these environments. *

* *

Design Notes

*
    *
  • This class is designed to be a static utility class and should not be instantiated.
  • *
  • It uses internal caching for operations like class aliasing and OSGi class loading to optimize performance.
  • *
* *

Usage Example

*
{@code
 * // Compute inheritance distance
 * int distance = ClassUtilities.computeInheritanceDistance(ArrayList.class, List.class); // Outputs 1
 *
 * // Check if a class is primitive
 * boolean isPrimitive = ClassUtilities.isPrimitive(int.class); // Outputs true
 *
 * // Load a resource as a string
 * String resourceContent = ClassUtilities.loadResourceAsString("example.txt");
 * }
* * @see Class * @see ClassLoader * @see Modifier * * @author John DeRegnaucourt ([email protected]) *
* Copyright (c) Cedar Software LLC *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* License *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class ClassUtilities { private static final Logger LOG = Logger.getLogger(ClassUtilities.class.getName()); static { LoggingConfig.init(); } private ClassUtilities() { } private static final Map> nameToClass = new ConcurrentHashMap<>(); private static final Map, Class> wrapperMap; private static final Map, Class> PRIMITIVE_TO_WRAPPER = new ClassValueMap<>(); private static final Map, Class> WRAPPER_TO_PRIMITIVE = new ClassValueMap<>(); // Cache for OSGi ClassLoader to avoid repeated reflection calls private static final Map, ClassLoader> osgiClassLoaders = new ClassValueMap<>(); private static final ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader(); private static volatile boolean useUnsafe = false; private static volatile Unsafe unsafe; private static final Map, Supplier> DIRECT_CLASS_MAPPING = new ClassValueMap<>(); private static final Map, Supplier> ASSIGNABLE_CLASS_MAPPING = new ClassValueMap<>(); /** * A cache that maps a Class to its associated enum type (if any). */ private static final ClassValue> ENUM_CLASS_CACHE = new ClassValue>() { @Override protected Class computeValue(Class type) { return computeEnum(type); } }; /** * Add a cache for successful constructor selections */ private static final Map, Constructor> SUCCESSFUL_CONSTRUCTOR_CACHE = new ClassValueMap<>(); /** * Cache for class hierarchy information */ private static final Map, ClassHierarchyInfo> CLASS_HIERARCHY_CACHE = new ClassValueMap<>(); static { // DIRECT_CLASS_MAPPING for concrete types DIRECT_CLASS_MAPPING.put(Date.class, Date::new); DIRECT_CLASS_MAPPING.put(StringBuilder.class, StringBuilder::new); DIRECT_CLASS_MAPPING.put(StringBuffer.class, StringBuffer::new); DIRECT_CLASS_MAPPING.put(Locale.class, Locale::getDefault); DIRECT_CLASS_MAPPING.put(TimeZone.class, TimeZone::getDefault); DIRECT_CLASS_MAPPING.put(Timestamp.class, () -> new Timestamp(System.currentTimeMillis())); DIRECT_CLASS_MAPPING.put(java.sql.Date.class, () -> new java.sql.Date(System.currentTimeMillis())); DIRECT_CLASS_MAPPING.put(LocalDate.class, LocalDate::now); DIRECT_CLASS_MAPPING.put(LocalDateTime.class, LocalDateTime::now); DIRECT_CLASS_MAPPING.put(OffsetDateTime.class, OffsetDateTime::now); DIRECT_CLASS_MAPPING.put(ZonedDateTime.class, ZonedDateTime::now); DIRECT_CLASS_MAPPING.put(ZoneId.class, ZoneId::systemDefault); DIRECT_CLASS_MAPPING.put(AtomicBoolean.class, AtomicBoolean::new); DIRECT_CLASS_MAPPING.put(AtomicInteger.class, AtomicInteger::new); DIRECT_CLASS_MAPPING.put(AtomicLong.class, AtomicLong::new); DIRECT_CLASS_MAPPING.put(URL.class, () -> ExceptionUtilities.safelyIgnoreException(() -> new URL("http://localhost"), null)); DIRECT_CLASS_MAPPING.put(URI.class, () -> ExceptionUtilities.safelyIgnoreException(() -> new URI("http://localhost"), null)); DIRECT_CLASS_MAPPING.put(Object.class, Object::new); DIRECT_CLASS_MAPPING.put(String.class, () -> ""); DIRECT_CLASS_MAPPING.put(BigInteger.class, () -> BigInteger.ZERO); DIRECT_CLASS_MAPPING.put(BigDecimal.class, () -> BigDecimal.ZERO); DIRECT_CLASS_MAPPING.put(Class.class, () -> String.class); DIRECT_CLASS_MAPPING.put(Calendar.class, Calendar::getInstance); DIRECT_CLASS_MAPPING.put(Instant.class, Instant::now); DIRECT_CLASS_MAPPING.put(Duration.class, () -> Duration.ofSeconds(10)); DIRECT_CLASS_MAPPING.put(Period.class, () -> Period.ofDays(0)); DIRECT_CLASS_MAPPING.put(Year.class, Year::now); DIRECT_CLASS_MAPPING.put(YearMonth.class, YearMonth::now); DIRECT_CLASS_MAPPING.put(MonthDay.class, MonthDay::now); DIRECT_CLASS_MAPPING.put(ZoneOffset.class, () -> ZoneOffset.UTC); DIRECT_CLASS_MAPPING.put(OffsetTime.class, OffsetTime::now); DIRECT_CLASS_MAPPING.put(LocalTime.class, LocalTime::now); DIRECT_CLASS_MAPPING.put(ByteBuffer.class, () -> ByteBuffer.allocate(0)); DIRECT_CLASS_MAPPING.put(CharBuffer.class, () -> CharBuffer.allocate(0)); // Collection classes DIRECT_CLASS_MAPPING.put(HashSet.class, HashSet::new); DIRECT_CLASS_MAPPING.put(TreeSet.class, TreeSet::new); DIRECT_CLASS_MAPPING.put(HashMap.class, HashMap::new); DIRECT_CLASS_MAPPING.put(TreeMap.class, TreeMap::new); DIRECT_CLASS_MAPPING.put(Hashtable.class, Hashtable::new); DIRECT_CLASS_MAPPING.put(ArrayList.class, ArrayList::new); DIRECT_CLASS_MAPPING.put(LinkedList.class, LinkedList::new); DIRECT_CLASS_MAPPING.put(Vector.class, Vector::new); DIRECT_CLASS_MAPPING.put(Stack.class, Stack::new); DIRECT_CLASS_MAPPING.put(Properties.class, Properties::new); DIRECT_CLASS_MAPPING.put(ConcurrentHashMap.class, ConcurrentHashMap::new); DIRECT_CLASS_MAPPING.put(LinkedHashMap.class, LinkedHashMap::new); DIRECT_CLASS_MAPPING.put(LinkedHashSet.class, LinkedHashSet::new); DIRECT_CLASS_MAPPING.put(ArrayDeque.class, ArrayDeque::new); DIRECT_CLASS_MAPPING.put(PriorityQueue.class, PriorityQueue::new); // Concurrent collections DIRECT_CLASS_MAPPING.put(CopyOnWriteArrayList.class, CopyOnWriteArrayList::new); DIRECT_CLASS_MAPPING.put(CopyOnWriteArraySet.class, CopyOnWriteArraySet::new); DIRECT_CLASS_MAPPING.put(LinkedBlockingQueue.class, LinkedBlockingQueue::new); DIRECT_CLASS_MAPPING.put(LinkedBlockingDeque.class, LinkedBlockingDeque::new); DIRECT_CLASS_MAPPING.put(ConcurrentSkipListMap.class, ConcurrentSkipListMap::new); DIRECT_CLASS_MAPPING.put(ConcurrentSkipListSet.class, ConcurrentSkipListSet::new); // Additional Map implementations DIRECT_CLASS_MAPPING.put(WeakHashMap.class, WeakHashMap::new); DIRECT_CLASS_MAPPING.put(IdentityHashMap.class, IdentityHashMap::new); DIRECT_CLASS_MAPPING.put(EnumMap.class, () -> new EnumMap<>(TimeUnit.class)); // Utility classes DIRECT_CLASS_MAPPING.put(UUID.class, UUID::randomUUID); DIRECT_CLASS_MAPPING.put(Currency.class, () -> Currency.getInstance(Locale.getDefault())); DIRECT_CLASS_MAPPING.put(Pattern.class, () -> Pattern.compile(".*")); DIRECT_CLASS_MAPPING.put(BitSet.class, BitSet::new); DIRECT_CLASS_MAPPING.put(StringJoiner.class, () -> new StringJoiner(",")); // Optional types DIRECT_CLASS_MAPPING.put(Optional.class, Optional::empty); DIRECT_CLASS_MAPPING.put(OptionalInt.class, OptionalInt::empty); DIRECT_CLASS_MAPPING.put(OptionalLong.class, OptionalLong::empty); DIRECT_CLASS_MAPPING.put(OptionalDouble.class, OptionalDouble::empty); // Stream types DIRECT_CLASS_MAPPING.put(Stream.class, Stream::empty); DIRECT_CLASS_MAPPING.put(IntStream.class, IntStream::empty); DIRECT_CLASS_MAPPING.put(LongStream.class, LongStream::empty); DIRECT_CLASS_MAPPING.put(DoubleStream.class, DoubleStream::empty); // Primitive arrays DIRECT_CLASS_MAPPING.put(boolean[].class, () -> new boolean[0]); DIRECT_CLASS_MAPPING.put(byte[].class, () -> new byte[0]); DIRECT_CLASS_MAPPING.put(short[].class, () -> new short[0]); DIRECT_CLASS_MAPPING.put(int[].class, () -> new int[0]); DIRECT_CLASS_MAPPING.put(long[].class, () -> new long[0]); DIRECT_CLASS_MAPPING.put(float[].class, () -> new float[0]); DIRECT_CLASS_MAPPING.put(double[].class, () -> new double[0]); DIRECT_CLASS_MAPPING.put(char[].class, () -> new char[0]); DIRECT_CLASS_MAPPING.put(Object[].class, () -> ArrayUtilities.EMPTY_OBJECT_ARRAY); // Boxed primitive arrays DIRECT_CLASS_MAPPING.put(Boolean[].class, () -> new Boolean[0]); DIRECT_CLASS_MAPPING.put(Byte[].class, () -> new Byte[0]); DIRECT_CLASS_MAPPING.put(Short[].class, () -> new Short[0]); DIRECT_CLASS_MAPPING.put(Integer[].class, () -> new Integer[0]); DIRECT_CLASS_MAPPING.put(Long[].class, () -> new Long[0]); DIRECT_CLASS_MAPPING.put(Float[].class, () -> new Float[0]); DIRECT_CLASS_MAPPING.put(Double[].class, () -> new Double[0]); DIRECT_CLASS_MAPPING.put(Character[].class, () -> new Character[0]); // ASSIGNABLE_CLASS_MAPPING for interfaces and abstract classes // Order from most specific to most general ASSIGNABLE_CLASS_MAPPING.put(EnumSet.class, () -> null); // Specific collection types ASSIGNABLE_CLASS_MAPPING.put(BlockingDeque.class, LinkedBlockingDeque::new); ASSIGNABLE_CLASS_MAPPING.put(Deque.class, ArrayDeque::new); ASSIGNABLE_CLASS_MAPPING.put(BlockingQueue.class, LinkedBlockingQueue::new); ASSIGNABLE_CLASS_MAPPING.put(Queue.class, LinkedList::new); // Specific set types ASSIGNABLE_CLASS_MAPPING.put(NavigableSet.class, TreeSet::new); ASSIGNABLE_CLASS_MAPPING.put(SortedSet.class, TreeSet::new); ASSIGNABLE_CLASS_MAPPING.put(Set.class, LinkedHashSet::new); // Specific map types ASSIGNABLE_CLASS_MAPPING.put(ConcurrentMap.class, ConcurrentHashMap::new); ASSIGNABLE_CLASS_MAPPING.put(NavigableMap.class, TreeMap::new); ASSIGNABLE_CLASS_MAPPING.put(SortedMap.class, TreeMap::new); ASSIGNABLE_CLASS_MAPPING.put(Map.class, LinkedHashMap::new); // List and more general collection types ASSIGNABLE_CLASS_MAPPING.put(List.class, ArrayList::new); ASSIGNABLE_CLASS_MAPPING.put(Collection.class, ArrayList::new); // Iterators and enumerations ASSIGNABLE_CLASS_MAPPING.put(ListIterator.class, () -> new ArrayList<>().listIterator()); ASSIGNABLE_CLASS_MAPPING.put(Iterator.class, Collections::emptyIterator); ASSIGNABLE_CLASS_MAPPING.put(Enumeration.class, Collections::emptyEnumeration); // Other interfaces ASSIGNABLE_CLASS_MAPPING.put(RandomAccess.class, ArrayList::new); ASSIGNABLE_CLASS_MAPPING.put(CharSequence.class, StringBuilder::new); ASSIGNABLE_CLASS_MAPPING.put(Comparable.class, () -> ""); // String implements Comparable ASSIGNABLE_CLASS_MAPPING.put(Cloneable.class, ArrayList::new); // ArrayList implements Cloneable ASSIGNABLE_CLASS_MAPPING.put(AutoCloseable.class, () -> new ByteArrayInputStream(new byte[0])); // Most general ASSIGNABLE_CLASS_MAPPING.put(Iterable.class, ArrayList::new); nameToClass.put("boolean", Boolean.TYPE); nameToClass.put("char", Character.TYPE); nameToClass.put("byte", Byte.TYPE); nameToClass.put("short", Short.TYPE); nameToClass.put("int", Integer.TYPE); nameToClass.put("long", Long.TYPE); nameToClass.put("float", Float.TYPE); nameToClass.put("double", Double.TYPE); nameToClass.put("string", String.class); nameToClass.put("date", Date.class); nameToClass.put("class", Class.class); PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class); PRIMITIVE_TO_WRAPPER.put(long.class, Long.class); PRIMITIVE_TO_WRAPPER.put(double.class, Double.class); PRIMITIVE_TO_WRAPPER.put(float.class, Float.class); PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class); PRIMITIVE_TO_WRAPPER.put(char.class, Character.class); PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class); PRIMITIVE_TO_WRAPPER.put(short.class, Short.class); PRIMITIVE_TO_WRAPPER.put(void.class, Void.class); // Initialize wrapper mappings WRAPPER_TO_PRIMITIVE.put(Boolean.class, boolean.class); WRAPPER_TO_PRIMITIVE.put(Byte.class, byte.class); WRAPPER_TO_PRIMITIVE.put(Character.class, char.class); WRAPPER_TO_PRIMITIVE.put(Short.class, short.class); WRAPPER_TO_PRIMITIVE.put(Integer.class, int.class); WRAPPER_TO_PRIMITIVE.put(Long.class, long.class); WRAPPER_TO_PRIMITIVE.put(Float.class, float.class); WRAPPER_TO_PRIMITIVE.put(Double.class, double.class); WRAPPER_TO_PRIMITIVE.put(Void.class, void.class); Map, Class> map = new ClassValueMap<>(); map.putAll(PRIMITIVE_TO_WRAPPER); map.putAll(WRAPPER_TO_PRIMITIVE); wrapperMap = Collections.unmodifiableMap(map); } /** * Converts a wrapper class to its corresponding primitive type. * * @param toType The wrapper class to convert to its primitive equivalent. * Must be one of the standard Java wrapper classes (e.g., Integer.class, Boolean.class). * @return The primitive class corresponding to the provided wrapper class or null if toType is not a primitive wrapper. * @throws IllegalArgumentException If toType is null */ public static Class getPrimitiveFromWrapper(Class toType) { Convention.throwIfNull(toType, "toType cannot be null"); return WRAPPER_TO_PRIMITIVE.get(toType); } /** * Container for class hierarchy information to avoid redundant calculations * Not considered API. Do not use this class in your code. */ public static class ClassHierarchyInfo { private final Set> allSupertypes; private final Map, Integer> distanceMap; private final int depth; // Store depth as a field ClassHierarchyInfo(Set> supertypes, Map, Integer> distances, Class sourceClass) { this.allSupertypes = Collections.unmodifiableSet(supertypes); this.distanceMap = Collections.unmodifiableMap(distances); // Calculate the depth during construction int maxDepth = 0; Class current = sourceClass; while (current != null) { current = current.getSuperclass(); maxDepth++; } this.depth = maxDepth - 1; // -1 because we counted steps, not classes } public Map, Integer> getDistanceMap() { return distanceMap; } Set> getAllSupertypes() { return allSupertypes; } int getDistance(Class type) { return distanceMap.getOrDefault(type, -1); } public int getDepth() { return depth; } } /** * Registers a permanent alias name for a class to support Class.forName() lookups. * * @param clazz the class to alias * @param alias the alternative name for the class */ public static void addPermanentClassAlias(Class clazz, String alias) { nameToClass.put(alias, clazz); } /** * Removes a previously registered class alias. * * @param alias the alias name to remove */ public static void removePermanentClassAlias(String alias) { nameToClass.remove(alias); } /** * Computes the inheritance distance between two classes/interfaces/primitive types. * Results are cached for performance. * * @param source The source class, interface, or primitive type. * @param destination The destination class, interface, or primitive type. * @return The number of steps from the source to the destination, or -1 if no path exists. */ public static int computeInheritanceDistance(Class source, Class destination) { if (source == null || destination == null) { return -1; } if (source.equals(destination)) { return 0; } // Handle primitives specially if (source.isPrimitive() || isPrimitive(source)) { if (destination.isPrimitive() || isPrimitive(destination)) { return areSamePrimitiveType(source, destination) ? 0 : -1; } } // Use the cached hierarchy info for non-primitive cases return getClassHierarchyInfo(source).getDistance(destination); } /** * Determines if two primitive or wrapper types represent the same primitive type. * * @param source The source type to compare * @param destination The destination type to compare * @return true if both types represent the same primitive type, false otherwise */ private static boolean areSamePrimitiveType(Class source, Class destination) { // If both are primitive, they must be exactly the same type if (source.isPrimitive() && destination.isPrimitive()) { return source.equals(destination); } // Get normalized primitive types (if they are wrappers, get the primitive equivalent) Class sourcePrimitive = source.isPrimitive() ? source : WRAPPER_TO_PRIMITIVE.get(source); Class destPrimitive = destination.isPrimitive() ? destination : WRAPPER_TO_PRIMITIVE.get(destination); // If either conversion failed, they're not compatible if (sourcePrimitive == null || destPrimitive == null) { return false; } // Check if they represent the same primitive type (e.g., int.class and Integer.class) return sourcePrimitive.equals(destPrimitive); } /** * @param c Class to test * @return boolean true if the passed in class is a Java primitive, false otherwise. The Wrapper classes * Integer, Long, Boolean, etc. are considered primitives by this method. */ public static boolean isPrimitive(Class c) { return c.isPrimitive() || WRAPPER_TO_PRIMITIVE.containsKey(c); } /** * Given the passed in String class name, return the named JVM class. * * @param name String name of a JVM class. * @param classLoader ClassLoader to use when searching for JVM classes. * @return Class instance of the named JVM class or null if not found. */ public static Class forName(String name, ClassLoader classLoader) { if (StringUtilities.isEmpty(name)) { return null; } try { return internalClassForName(name, classLoader); } catch (SecurityException e) { throw new IllegalArgumentException("Security exception, classForName() call on: " + name, e); } catch (Exception e) { return null; } } /** * Used internally to load a class by name, and takes care of caching name mappings for speed. * * @param name String name of a JVM class. * @param classLoader ClassLoader to use when searching for JVM classes. * @return Class instance of the named JVM class */ private static Class internalClassForName(String name, ClassLoader classLoader) throws ClassNotFoundException { Class c = nameToClass.get(name); if (c != null) { return c; } // Check name before loading (quick rejection) if (SecurityChecker.isSecurityBlockedName(name)) { throw new SecurityException("For security reasons, cannot load: " + name); } c = loadClass(name, classLoader); // Perform full security check on loaded class SecurityChecker.verifyClass(c); nameToClass.put(name, c); return c; } /** * loadClass() provided by: Thomas Margreiter *

* Loads a class using the specified ClassLoader, with recursive handling for array types * and primitive arrays. * * @param name the fully qualified class name or array type descriptor * @param classLoader the ClassLoader to use * @return the loaded Class object * @throws ClassNotFoundException if the class cannot be found */ private static Class loadClass(String name, ClassLoader classLoader) throws ClassNotFoundException { String className = name; boolean arrayType = false; Class primitiveArray = null; while (className.startsWith("[")) { arrayType = true; if (className.endsWith(";")) { className = className.substring(0, className.length() - 1); } switch (className) { case "[B": primitiveArray = byte[].class; break; case "[S": primitiveArray = short[].class; break; case "[I": primitiveArray = int[].class; break; case "[J": primitiveArray = long[].class; break; case "[F": primitiveArray = float[].class; break; case "[D": primitiveArray = double[].class; break; case "[Z": primitiveArray = boolean[].class; break; case "[C": primitiveArray = char[].class; break; } int startpos = className.startsWith("[L") ? 2 : 1; className = className.substring(startpos); } Class currentClass = null; if (null == primitiveArray) { try { currentClass = classLoader.loadClass(className); } catch (ClassNotFoundException e) { ClassLoader ctx = Thread.currentThread().getContextClassLoader(); if (ctx != null) { currentClass = ctx.loadClass(className); } else { currentClass = Class.forName(className, false, getClassLoader(ClassUtilities.class)); } } } if (arrayType) { currentClass = (null != primitiveArray) ? primitiveArray : Array.newInstance(currentClass, 0).getClass(); while (name.startsWith("[[")) { currentClass = Array.newInstance(currentClass, 0).getClass(); name = name.substring(1); } } return currentClass; } /** * Determines if a class is declared as final. *

* Checks if the class has the {@code final} modifier, indicating that it cannot be subclassed. *

* *

Example:

*
{@code
     * boolean isFinal = ClassUtilities.isClassFinal(String.class);  // Returns true
     * boolean notFinal = ClassUtilities.isClassFinal(ArrayList.class);  // Returns false
     * }
* * @param c the class to check, must not be null * @return true if the class is final, false otherwise * @throws NullPointerException if the input class is null */ public static boolean isClassFinal(Class c) { return (c.getModifiers() & Modifier.FINAL) != 0; } /** * Determines if all constructors in a class are declared as private. *

* This method is useful for identifying classes that enforce singleton patterns * or utility classes that should not be instantiated. *

* *

Example:

*
{@code
     * // Utility class with private constructor
     * public final class Utils {
     *     private Utils() {}
     * }
     *
     * boolean isPrivate = ClassUtilities.areAllConstructorsPrivate(Utils.class);  // Returns true
     * boolean notPrivate = ClassUtilities.areAllConstructorsPrivate(String.class);  // Returns false
     * }
* * @param c the class to check, must not be null * @return true if all constructors in the class are private, false if any constructor is non-private * @throws NullPointerException if the input class is null */ public static boolean areAllConstructorsPrivate(Class c) { Constructor[] constructors = ReflectionUtils.getAllConstructors(c); for (Constructor constructor : constructors) { if ((constructor.getModifiers() & Modifier.PRIVATE) == 0) { return false; } } return true; } /** * Converts a primitive class to its corresponding wrapper class. *

* If the input class is already a non-primitive type, it is returned unchanged. * For primitive types, returns the corresponding wrapper class (e.g., {@code int.class} → {@code Integer.class}). *

* *

Examples:

*
{@code
     * Class intWrapper = ClassUtilities.toPrimitiveWrapperClass(int.class);     // Returns Integer.class
     * Class boolWrapper = ClassUtilities.toPrimitiveWrapperClass(boolean.class); // Returns Boolean.class
     * Class sameClass = ClassUtilities.toPrimitiveWrapperClass(String.class);    // Returns String.class
     * }
* *

Supported Primitive Types:

*
    *
  • {@code boolean.class} → {@code Boolean.class}
  • *
  • {@code byte.class} → {@code Byte.class}
  • *
  • {@code char.class} → {@code Character.class}
  • *
  • {@code double.class} → {@code Double.class}
  • *
  • {@code float.class} → {@code Float.class}
  • *
  • {@code int.class} → {@code Integer.class}
  • *
  • {@code long.class} → {@code Long.class}
  • *
  • {@code short.class} → {@code Short.class}
  • *
  • {@code void.class} → {@code Void.class}
  • *
* * @param primitiveClass the class to convert, must not be null * @return the wrapper class if the input is primitive, otherwise the input class itself * @throws NullPointerException if the input class is null * @throws IllegalArgumentException if the input class is not a recognized primitive type */ public static Class toPrimitiveWrapperClass(Class primitiveClass) { if (!primitiveClass.isPrimitive()) { return primitiveClass; } Class c = PRIMITIVE_TO_WRAPPER.get(primitiveClass); if (c == null) { throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class"); } return c; } /** * Determines if one class is the wrapper type of the other. *

* This method checks if there is a primitive-wrapper relationship between two classes. * For example, {@code Integer.class} wraps {@code int.class} and vice versa. *

* *

Examples:

*
{@code
     * boolean wraps = ClassUtilities.doesOneWrapTheOther(Integer.class, int.class);    // Returns true
     * boolean wraps2 = ClassUtilities.doesOneWrapTheOther(int.class, Integer.class);   // Returns true
     * boolean noWrap = ClassUtilities.doesOneWrapTheOther(Integer.class, long.class);  // Returns false
     * }
* *

Supported Wrapper Pairs:

*
    *
  • {@code Boolean.class} ↔ {@code boolean.class}
  • *
  • {@code Byte.class} ↔ {@code byte.class}
  • *
  • {@code Character.class} ↔ {@code char.class}
  • *
  • {@code Double.class} ↔ {@code double.class}
  • *
  • {@code Float.class} ↔ {@code float.class}
  • *
  • {@code Integer.class} ↔ {@code int.class}
  • *
  • {@code Long.class} ↔ {@code long.class}
  • *
  • {@code Short.class} ↔ {@code short.class}
  • *
* * @param x first class to check * @param y second class to check * @return true if one class is the wrapper of the other, false otherwise. * If either argument is {@code null}, this method returns {@code false}. */ public static boolean doesOneWrapTheOther(Class x, Class y) { return wrapperMap.get(x) == y; } /** * Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither. * * @return the appropriate ClassLoader */ public static ClassLoader getClassLoader() { return getClassLoader(ClassUtilities.class); } /** * Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither. * * @param anchorClass the class to use as reference for loading * @return the appropriate ClassLoader */ public static ClassLoader getClassLoader(final Class anchorClass) { if (anchorClass == null) { throw new IllegalArgumentException("Anchor class cannot be null"); } checkSecurityAccess(); // Try OSGi first ClassLoader cl = getOSGiClassLoader(anchorClass); if (cl != null) { return cl; } // Try context class loader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { return cl; } // Try anchor class loader cl = anchorClass.getClassLoader(); if (cl != null) { return cl; } // Last resort return SYSTEM_LOADER; } /** * Checks if the current security manager allows class loader access. *

* This uses {@link SecurityManager}, which is deprecated in recent JDKs. * When no security manager is present, this method performs no checks. *

*/ private static void checkSecurityAccess() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("getClassLoader")); } } /** * Attempts to retrieve the OSGi Bundle's ClassLoader. * * @param classFromBundle the class from which to get the bundle * @return the OSGi Bundle's ClassLoader if in an OSGi environment; otherwise, null */ private static ClassLoader getOSGiClassLoader(final Class classFromBundle) { return osgiClassLoaders.computeIfAbsent(classFromBundle, ClassUtilities::getOSGiClassLoader0); } /** * Internal method to retrieve the OSGi Bundle's ClassLoader using reflection. * * @param classFromBundle the class from which to get the bundle * @return the OSGi Bundle's ClassLoader if in an OSGi environment; otherwise, null */ private static ClassLoader getOSGiClassLoader0(final Class classFromBundle) { try { // Load the FrameworkUtil class from OSGi Class frameworkUtilClass = Class.forName("org.osgi.framework.FrameworkUtil"); // Get the getBundle(Class) method Method getBundleMethod = frameworkUtilClass.getMethod("getBundle", Class.class); // Invoke FrameworkUtil.getBundle(classFromBundle) to get the Bundle instance Object bundle = getBundleMethod.invoke(null, classFromBundle); if (bundle != null) { // Get BundleWiring class Class bundleWiringClass = Class.forName("org.osgi.framework.wiring.BundleWiring"); // Get the adapt(Class) method Method adaptMethod = bundle.getClass().getMethod("adapt", Class.class); // Invoke bundle.adapt(BundleWiring.class) to get the BundleWiring instance Object bundleWiring = adaptMethod.invoke(bundle, bundleWiringClass); if (bundleWiring != null) { // Get the getClassLoader() method from BundleWiring Method getClassLoaderMethod = bundleWiringClass.getMethod("getClassLoader"); // Invoke getClassLoader() to obtain the ClassLoader Object classLoader = getClassLoaderMethod.invoke(bundleWiring); if (classLoader instanceof ClassLoader) { return (ClassLoader) classLoader; } } } } catch (Exception e) { // OSGi environment not detected or an error occurred // Silently ignore as this is expected in non-OSGi environments } return null; } /** * Finds the closest matching class in an inheritance hierarchy from a map of candidate classes. *

* This method searches through a map of candidate classes to find the one that is most closely * related to the input class in terms of inheritance distance. The search prioritizes: *

    *
  • Exact class match (returns immediately)
  • *
  • Closest superclass/interface in the inheritance hierarchy
  • *
*

* This method is typically used for cache misses when looking up class-specific handlers * or processors. * * @param The type of value stored in the candidateClasses map * @param clazz The class to find a match for (must not be null) * @param candidateClasses Map of candidate classes and their associated values (must not be null) * @param defaultClass Default value to return if no suitable match is found * @return The value associated with the closest matching class, or defaultClass if no match found * @throws IllegalArgumentException if {@code clazz} or {@code candidateClasses} is null * * @see ClassUtilities#computeInheritanceDistance(Class, Class) */ public static T findClosest(Class clazz, Map, T> candidateClasses, T defaultClass) { Convention.throwIfNull(clazz, "Source class cannot be null"); Convention.throwIfNull(candidateClasses, "Candidate classes Map cannot be null"); // First try exact match T exactMatch = candidateClasses.get(clazz); if (exactMatch != null) { return exactMatch; } // If no exact match, then look for closest inheritance match T closest = defaultClass; int minDistance = Integer.MAX_VALUE; Class closestClass = null; for (Map.Entry, T> entry : candidateClasses.entrySet()) { Class candidateClass = entry.getKey(); int distance = ClassUtilities.computeInheritanceDistance(clazz, candidateClass); if (distance != -1 && (distance < minDistance || (distance == minDistance && shouldPreferNewCandidate(candidateClass, closestClass)))) { minDistance = distance; closest = entry.getValue(); closestClass = candidateClass; } } return closest; } /** * Determines if a new candidate class should be preferred over the current closest class when * they have equal inheritance distances. *

* The selection logic follows these rules in order: *

    *
  1. If there is no current class (null), the new candidate is preferred
  2. *
  3. Classes are preferred over interfaces
  4. *
  5. When both are classes or both are interfaces, the more specific type is preferred
  6. *
* * @param newClass the candidate class being evaluated (must not be null) * @param currentClass the current closest matching class (may be null) * @return true if newClass should be preferred over currentClass, false otherwise */ private static boolean shouldPreferNewCandidate(Class newClass, Class currentClass) { if (currentClass == null) return true; // Prefer classes to interfaces if (newClass.isInterface() != currentClass.isInterface()) { return !newClass.isInterface(); } // If both are classes or both are interfaces, prefer the more specific one return newClass.isAssignableFrom(currentClass); } /** * Loads resource content as a {@link String}. *

* This method delegates to {@link #loadResourceAsBytes(String)} which first * attempts to resolve the resource using the current thread's context * {@link ClassLoader} and then falls back to the {@code ClassUtilities} * class loader. *

* * @param resourceName Name of the resource file. * @return Content of the resource file as a String. */ public static String loadResourceAsString(String resourceName) { byte[] resourceBytes = loadResourceAsBytes(resourceName); return new String(resourceBytes, StandardCharsets.UTF_8); } /** * Loads resource content as a byte[] using the following lookup order: *
    *
  1. The current thread's context {@link ClassLoader}
  2. *
  3. The {@code ClassUtilities} class loader
  4. *
* * @param resourceName Name of the resource file. * @return Content of the resource file as a byte[]. * @throws IllegalArgumentException if the resource cannot be found * @throws UncheckedIOException if there is an error reading the resource * @throws NullPointerException if resourceName is null */ public static byte[] loadResourceAsBytes(String resourceName) { Objects.requireNonNull(resourceName, "resourceName cannot be null"); InputStream inputStream = null; ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { inputStream = cl.getResourceAsStream(resourceName); } if (inputStream == null) { cl = ClassUtilities.getClassLoader(ClassUtilities.class); inputStream = cl.getResourceAsStream(resourceName); } if (inputStream == null) { throw new IllegalArgumentException("Resource not found: " + resourceName); } try (InputStream in = inputStream) { return readInputStreamFully(in); } catch (IOException e) { throw new UncheckedIOException("Error reading resource: " + resourceName, e); } } private static final int BUFFER_SIZE = 65536; /** * Reads an InputStream fully and returns its content as a byte array. * * @param inputStream InputStream to read. * @return Content of the InputStream as byte array. * @throws IOException if an I/O error occurs. */ private static byte[] readInputStreamFully(InputStream inputStream) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(BUFFER_SIZE); byte[] data = new byte[BUFFER_SIZE]; int nRead; while ((nRead = inputStream.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); return buffer.toByteArray(); } private static Object getArgForType(com.cedarsoftware.util.convert.Converter converter, Class argType) { if (isPrimitive(argType)) { return converter.convert(null, argType); // Get the defaults (false, 0, 0.0d, etc.) } Supplier directClassMapping = DIRECT_CLASS_MAPPING.get(argType); if (directClassMapping != null) { return directClassMapping.get(); } for (Map.Entry, Supplier> entry : ASSIGNABLE_CLASS_MAPPING.entrySet()) { if (entry.getKey().isAssignableFrom(argType)) { return entry.getValue().get(); } } if (argType.isArray()) { return Array.newInstance(argType.getComponentType(), 0); } return null; } /** * Optimally match arguments to constructor parameters with minimal collection creation. * * @param converter Converter to use for type conversions * @param values Collection of potential arguments * @param parameters Array of parameter types to match against * @param allowNulls Whether to allow null values for non-primitive parameters * @return Array of values matched to the parameters in the correct order */ private static Object[] matchArgumentsToParameters(Converter converter, Collection values, Parameter[] parameters, boolean allowNulls) { if (parameters == null || parameters.length == 0) { return ArrayUtilities.EMPTY_OBJECT_ARRAY; // Reuse a static empty array } // Create result array and tracking arrays Object[] result = new Object[parameters.length]; boolean[] parameterMatched = new boolean[parameters.length]; // For tracking available values (more efficient than repeated removal from list) Object[] valueArray = values.toArray(); boolean[] valueUsed = new boolean[valueArray.length]; // PHASE 1: Find exact type matches - highest priority findExactMatches(valueArray, valueUsed, parameters, parameterMatched, result); // PHASE 2: Find assignable type matches with inheritance findInheritanceMatches(valueArray, valueUsed, parameters, parameterMatched, result); // PHASE 3: Find primitive/wrapper matches findPrimitiveWrapperMatches(valueArray, valueUsed, parameters, parameterMatched, result); // PHASE 4: Find convertible type matches findConvertibleMatches(converter, valueArray, valueUsed, parameters, parameterMatched, result); // PHASE 5: Fill remaining unmatched parameters with defaults or nulls fillRemainingParameters(converter, parameters, parameterMatched, result, allowNulls); return result; } /** * Find exact type matches between values and parameters */ private static void findExactMatches(Object[] values, boolean[] valueUsed, Parameter[] parameters, boolean[] parameterMatched, Object[] result) { int valLen = values.length; int paramLen = parameters.length; for (int i = 0; i < paramLen; i++) { if (parameterMatched[i]) continue; Class paramType = parameters[i].getType(); for (int j = 0; j < valLen; j++) { if (valueUsed[j]) continue; Object value = values[j]; if (value != null && value.getClass() == paramType) { result[i] = value; parameterMatched[i] = true; valueUsed[j] = true; break; } } } } /** * Find matches based on inheritance relationships */ private static void findInheritanceMatches(Object[] values, boolean[] valueUsed, Parameter[] parameters, boolean[] parameterMatched, Object[] result) { // For each unmatched parameter, find the best inheritance match for (int i = 0; i < parameters.length; i++) { if (parameterMatched[i]) continue; Class paramType = parameters[i].getType(); int bestDistance = Integer.MAX_VALUE; int bestValueIndex = -1; for (int j = 0; j < values.length; j++) { if (valueUsed[j]) continue; Object value = values[j]; if (value == null) continue; Class valueClass = value.getClass(); int distance = ClassUtilities.computeInheritanceDistance(valueClass, paramType); if (distance >= 0 && distance < bestDistance) { bestDistance = distance; bestValueIndex = j; } } if (bestValueIndex >= 0) { result[i] = values[bestValueIndex]; parameterMatched[i] = true; valueUsed[bestValueIndex] = true; } } } /** * Find matches between primitives and their wrapper types */ private static void findPrimitiveWrapperMatches(Object[] values, boolean[] valueUsed, Parameter[] parameters, boolean[] parameterMatched, Object[] result) { for (int i = 0; i < parameters.length; i++) { if (parameterMatched[i]) continue; Class paramType = parameters[i].getType(); for (int j = 0; j < values.length; j++) { if (valueUsed[j]) continue; Object value = values[j]; if (value == null) continue; Class valueClass = value.getClass(); if (doesOneWrapTheOther(paramType, valueClass)) { result[i] = value; parameterMatched[i] = true; valueUsed[j] = true; break; } } } } /** * Find matches that require type conversion */ private static void findConvertibleMatches(Converter converter, Object[] values, boolean[] valueUsed, Parameter[] parameters, boolean[] parameterMatched, Object[] result) { for (int i = 0; i < parameters.length; i++) { if (parameterMatched[i]) continue; Class paramType = parameters[i].getType(); for (int j = 0; j < values.length; j++) { if (valueUsed[j]) continue; Object value = values[j]; if (value == null) continue; Class valueClass = value.getClass(); if (converter.isSimpleTypeConversionSupported(paramType, valueClass)) { try { Object converted = converter.convert(value, paramType); result[i] = converted; parameterMatched[i] = true; valueUsed[j] = true; break; } catch (Exception ignored) { // Conversion failed, continue } } } } } /** * Fill any remaining unmatched parameters with default values or nulls */ private static void fillRemainingParameters(Converter converter, Parameter[] parameters, boolean[] parameterMatched, Object[] result, boolean allowNulls) { for (int i = 0; i < parameters.length; i++) { if (parameterMatched[i]) continue; Parameter parameter = parameters[i]; Class paramType = parameter.getType(); if (allowNulls && !paramType.isPrimitive()) { result[i] = null; } else { // Get default value for the type Object defaultValue = getArgForType(converter, paramType); // If no default and primitive, convert null if (defaultValue == null && paramType.isPrimitive()) { defaultValue = converter.convert(null, paramType); } result[i] = defaultValue; } } } /** * Returns the index of the smallest value in an array. * @param array The array to search. * @return The index of the smallest value, or -1 if the array is empty. * @deprecated */ @Deprecated public static int indexOfSmallestValue(int[] array) { if (array == null || array.length == 0) { return -1; // Return -1 for null or empty array. } int minValue = Integer.MAX_VALUE; int minIndex = -1; for (int i = 0; i < array.length; i++) { if (array[i] < minValue) { minValue = array[i]; minIndex = i; } } return minIndex; } /** * Returns the related enum class for the provided class, if one exists. * * @param c the class to check; may be null * @return the related enum class, or null if none is found */ public static Class getClassIfEnum(Class c) { if (c == null) { return null; } return ENUM_CLASS_CACHE.get(c); } /** * Computes the enum type for a given class by first checking if the class itself is an enum, * then traversing its superclass hierarchy, and finally its enclosing classes. * * @param c the class to check; not null * @return the related enum class if found, or null otherwise */ private static Class computeEnum(Class c) { // Fast path: if the class itself is an enum (and not java.lang.Enum), return it immediately. if (c.isEnum() && c != Enum.class) { return c; } // Traverse the superclass chain. Class current = c; while ((current = current.getSuperclass()) != null) { if (current.isEnum() && current != Enum.class) { return current; } } // Traverse the enclosing class chain. current = c.getEnclosingClass(); while (current != null) { if (current.isEnum() && current != Enum.class) { return current; } current = current.getEnclosingClass(); } return null; } /** * Create a new instance of the specified class, optionally using provided constructor arguments. *

* This method attempts to instantiate a class using the following strategies in order: *

    *
  1. Using cached successful constructor from previous instantiations
  2. *
  3. Using constructors in optimal order (public, protected, package, private)
  4. *
  5. Within each accessibility level, trying constructors with more parameters first
  6. *
  7. For each constructor, trying with exact matches first, then allowing null values
  8. *
  9. Using unsafe instantiation (if enabled)
  10. *
* * @param c Class to instantiate * @param arguments Can be: * - null or empty (no-arg constructor) * - Map<String, Object> to match by parameter name (when available) or type * - Collection<?> of values to match by type * - Object[] of values to match by type * - Single value for single-argument constructors * @return A new instance of the specified class * @throws IllegalArgumentException if the class cannot be instantiated or arguments are invalid */ public static Object newInstance(Class c, Object arguments) { return newInstance(com.cedarsoftware.util.Converter.getInstance(), c, arguments); } /** * Create a new instance of the specified class, optionally using provided constructor arguments. *

* This method attempts to instantiate a class using the following strategies in order: *

    *
  1. Using cached successful constructor from previous instantiations
  2. *
  3. Using constructors in optimal order (public, protected, package, private)
  4. *
  5. Within each accessibility level, trying constructors with more parameters first
  6. *
  7. For each constructor, trying with exact matches first, then allowing null values
  8. *
  9. Using unsafe instantiation (if enabled)
  10. *
* * @param converter Converter instance used to convert null values to appropriate defaults for primitive types * @param c Class to instantiate * @param arguments Can be: * - null or empty (no-arg constructor) * - Map<String, Object> to match by parameter name (when available) or type * - Collection<?> of values to match by type * - Object[] of values to match by type * - Single value for single-argument constructors * @return A new instance of the specified class * @throws IllegalArgumentException if the class cannot be instantiated or arguments are invalid */ public static Object newInstance(Converter converter, Class c, Object arguments) { Convention.throwIfNull(c, "Class cannot be null"); Convention.throwIfNull(converter, "Converter cannot be null"); // Normalize arguments to Collection format for existing code Collection normalizedArgs; Map namedParameters = null; boolean hasNamedParameters = false; if (arguments == null) { normalizedArgs = Collections.emptyList(); } else if (arguments instanceof Collection) { normalizedArgs = (Collection) arguments; } else if (arguments instanceof Map) { Map map = (Map) arguments; // Check once if we have generated keys boolean generatedKeys = hasGeneratedKeys(map); if (!generatedKeys) { hasNamedParameters = true; namedParameters = map; } // Convert map values to collection for fallback if (generatedKeys) { // Preserve order for generated keys (arg0, arg1, etc.) List orderedValues = new ArrayList<>(); for (int i = 0; i < map.size(); i++) { orderedValues.add(map.get("arg" + i)); } normalizedArgs = orderedValues; } else { normalizedArgs = map.values(); } } else if (arguments.getClass().isArray()) { normalizedArgs = converter.convert(arguments, Collection.class); } else { // Single value - wrap in collection normalizedArgs = Collections.singletonList(arguments); } // Try parameter name matching first if we have named parameters if (hasNamedParameters && namedParameters != null) { LOG.log(Level.FINE, "Attempting parameter name matching for class: {0}", c.getName()); LOG.log(Level.FINER, "Provided parameter names: {0}", namedParameters.keySet()); try { Object result = newInstanceWithNamedParameters(converter, c, namedParameters); if (result != null) { LOG.log(Level.FINE, "Successfully created instance of {0} using parameter names", c.getName()); return result; } } catch (Exception e) { LOG.log(Level.FINE, "Parameter name matching failed for {0}: {1}", new Object[]{c.getName(), e.getMessage()}); LOG.log(Level.FINER, "Falling back to positional argument matching"); } } // Call existing implementation LOG.log(Level.FINER, "Using positional argument matching for {0}", c.getName()); Set> visited = Collections.newSetFromMap(new IdentityHashMap<>()); try { return newInstance(converter, c, normalizedArgs, visited); } catch (Exception e) { // If we were trying with map values and it failed, try with null (no-arg constructor) if (arguments instanceof Map && normalizedArgs != null && !normalizedArgs.isEmpty()) { LOG.log(Level.FINER, "Positional matching with map values failed for {0}, trying no-arg constructor", c.getName()); return newInstance(converter, c, null, visited); } throw e; } } private static Object newInstanceWithNamedParameters(Converter converter, Class c, Map namedParams) { // Get all constructors using ReflectionUtils for caching Constructor[] sortedConstructors = ReflectionUtils.getAllConstructors(c); boolean isFinal = Modifier.isFinal(c.getModifiers()); boolean isException = Throwable.class.isAssignableFrom(c); LOG.log(Level.FINER, "Class {0} is {1}{2}", new Object[]{c.getName(), isFinal ? "final" : "non-final", isException ? " (Exception type)" : ""}); LOG.log(Level.FINER, "Trying {0} constructors for {1}", new Object[]{sortedConstructors.length, c.getName()}); // First check if ANY constructor has real parameter names boolean anyConstructorHasRealNames = false; for (Constructor constructor : sortedConstructors) { Parameter[] parameters = constructor.getParameters(); if (parameters.length > 0) { String firstParamName = parameters[0].getName(); if (!firstParamName.matches("arg\\d+")) { anyConstructorHasRealNames = true; break; } } } // If no constructors have real parameter names, bail out early if (!anyConstructorHasRealNames) { boolean hasParameterizedConstructor = false; for (Constructor cons : sortedConstructors) { if (cons.getParameterCount() > 0) { hasParameterizedConstructor = true; break; } } if (hasParameterizedConstructor) { LOG.log(Level.FINE, "No constructors for {0} have real parameter names - cannot use parameter matching", c.getName()); return null; // This will trigger fallback to positional matching } } for (Constructor constructor : sortedConstructors) { LOG.log(Level.FINER, "Trying constructor: {0}", constructor); // Get parameter names Parameter[] parameters = constructor.getParameters(); String[] paramNames = new String[parameters.length]; boolean hasRealNames = true; for (int i = 0; i < parameters.length; i++) { paramNames[i] = parameters[i].getName(); LOG.log(Level.FINEST, " Parameter {0}: name=''{1}'', type={2}", new Object[]{i, paramNames[i], parameters[i].getType().getSimpleName()}); // Check if we have real parameter names or just arg0, arg1, etc. if (paramNames[i].matches("arg\\d+")) { hasRealNames = false; } } if (!hasRealNames && parameters.length > 0) { LOG.log(Level.FINER, " Skipping constructor - parameter names not available"); continue; // Skip this constructor for parameter matching } // Try to match all parameters Object[] args = new Object[parameters.length]; boolean allMatched = true; for (int i = 0; i < parameters.length; i++) { if (namedParams.containsKey(paramNames[i])) { Object value = namedParams.get(paramNames[i]); try { // Check if conversion is needed - if value is already assignable to target type, use as-is if (value != null && parameters[i].getType().isAssignableFrom(value.getClass())) { args[i] = value; } else { // Convert if necessary args[i] = converter.convert(value, parameters[i].getType()); } LOG.log(Level.FINEST, " Matched parameter ''{0}'' with value: {1}", new Object[]{paramNames[i], value}); } catch (Exception conversionException) { allMatched = false; break; } } else { LOG.log(Level.FINER, " Missing parameter: {0}", paramNames[i]); allMatched = false; break; } } if (allMatched) { try { Object instance = constructor.newInstance(args); LOG.log(Level.FINE, " Successfully created instance of {0}", c.getName()); return instance; } catch (Exception e) { LOG.log(Level.FINER, " Failed to invoke constructor: {0}", e.getMessage()); } } } return null; // Indicate failure to create with named parameters } // Add this as a static field near the top of ClassUtilities private static final Pattern ARG_PATTERN = Pattern.compile("arg\\d+"); /** * Check if the map has generated keys (arg0, arg1, etc.) */ private static boolean hasGeneratedKeys(Map map) { if (map.isEmpty()) { return false; } // Check if all keys match the pattern arg0, arg1, etc. for (String key : map.keySet()) { if (!ARG_PATTERN.matcher(key).matches()) { return false; } } return true; } /** * @deprecated Use {@link #newInstance(Converter, Class, Object)} instead. * @param converter Converter instance * @param c Class to instantiate * @param argumentValues Collection of constructor arguments * @return A new instance of the specified class * @see #newInstance(Converter, Class, Object) */ @Deprecated public static Object newInstance(Converter converter, Class c, Collection argumentValues) { return newInstance(converter, c, (Object) argumentValues); } private static Object newInstance(Converter converter, Class c, Collection argumentValues, Set> visitedClasses) { Convention.throwIfNull(c, "Class cannot be null"); // Do security check FIRST SecurityChecker.verifyClass(c); if (visitedClasses.contains(c)) { throw new IllegalStateException("Circular reference detected for " + c.getName()); } // Then do other validations if (c.isInterface()) { throw new IllegalArgumentException("Cannot instantiate interface: " + c.getName()); } if (Modifier.isAbstract(c.getModifiers())) { throw new IllegalArgumentException("Cannot instantiate abstract class: " + c.getName()); } // First attempt: Check if we have a previously successful constructor for this class List normalizedArgs = argumentValues == null ? new ArrayList<>() : new ArrayList<>(argumentValues); Constructor cachedConstructor = SUCCESSFUL_CONSTRUCTOR_CACHE.get(c); if (cachedConstructor != null) { try { Parameter[] parameters = cachedConstructor.getParameters(); // Try both approaches with the cached constructor try { Object[] argsNonNull = matchArgumentsToParameters(converter, normalizedArgs, parameters, false); return cachedConstructor.newInstance(argsNonNull); } catch (Exception e) { Object[] argsNull = matchArgumentsToParameters(converter, normalizedArgs, parameters, true); return cachedConstructor.newInstance(argsNull); } } catch (Exception ignored) { // If cached constructor fails, continue with regular instantiation // and potentially update the cache } } // Handle inner classes - with circular reference protection if (c.getEnclosingClass() != null && !Modifier.isStatic(c.getModifiers())) { visitedClasses.add(c); try { // For inner classes, try to get the enclosing instance Class enclosingClass = c.getEnclosingClass(); if (!visitedClasses.contains(enclosingClass)) { Object enclosingInstance = newInstance(converter, enclosingClass, Collections.emptyList(), visitedClasses); Constructor constructor = ReflectionUtils.getConstructor(c, enclosingClass); if (constructor != null) { // Cache this successful constructor SUCCESSFUL_CONSTRUCTOR_CACHE.put(c, constructor); return constructor.newInstance(enclosingInstance); } } } catch (Exception ignored) { // Fall through to regular instantiation if this fails } } // Get constructors - already sorted in optimal order by ReflectionUtils.getAllConstructors Constructor[] constructors = ReflectionUtils.getAllConstructors(c); List exceptions = new ArrayList<>(); // Collect all exceptions for better diagnostics // Try each constructor in order for (Constructor constructor : constructors) { Parameter[] parameters = constructor.getParameters(); // Attempt instantiation with this constructor try { // Try with non-null arguments first (more precise matching) Object[] argsNonNull = matchArgumentsToParameters(converter, normalizedArgs, parameters, false); Object instance = constructor.newInstance(argsNonNull); // Cache this successful constructor for future use SUCCESSFUL_CONSTRUCTOR_CACHE.put(c, constructor); return instance; } catch (Exception e1) { exceptions.add(e1); // If that fails, try with nulls allowed for unmatched parameters try { Object[] argsNull = matchArgumentsToParameters(converter, normalizedArgs, parameters, true); Object instance = constructor.newInstance(argsNull); // Cache this successful constructor for future use SUCCESSFUL_CONSTRUCTOR_CACHE.put(c, constructor); return instance; } catch (Exception e2) { exceptions.add(e2); // Continue to next constructor } } } // Last resort: try unsafe instantiation Object instance = tryUnsafeInstantiation(c); if (instance != null) { return instance; } // If we get here, we couldn't create the instance String msg = "Unable to instantiate: " + c.getName(); if (!exceptions.isEmpty()) { // Include the most relevant exception message Exception lastException = exceptions.get(exceptions.size() - 1); msg += " - Most recent error: " + lastException.getMessage(); // Optionally include all exception messages for detailed troubleshooting if (exceptions.size() > 1) { StringBuilder errorDetails = new StringBuilder("\nAll constructor errors:\n"); for (int i = 0; i < exceptions.size(); i++) { Exception e = exceptions.get(i); errorDetails.append(" ").append(i + 1).append(") ") .append(e.getClass().getSimpleName()).append(": ") .append(e.getMessage()).append("\n"); } msg += errorDetails.toString(); } } throw new IllegalArgumentException(msg); } static void trySetAccessible(AccessibleObject object) { try { object.setAccessible(true); } catch (SecurityException e) { LOG.log(Level.WARNING, "Unable to set accessible: " + object + " - " + e.getMessage()); } catch (Throwable t) { safelyIgnoreException(t); } } // Try instantiation via unsafe (if turned on). It is off by default. Use // ClassUtilities.setUseUnsafe(true) to enable it. This may result in heap-dumps // for e.g. ConcurrentHashMap or can cause problems when the class is not initialized, // that's why we try ordinary constructors first. private static Object tryUnsafeInstantiation(Class c) { if (useUnsafe) { try { return unsafe.allocateInstance(c); } catch (Exception ignored) { } } return null; } /** * Globally turn on (or off) the 'unsafe' option of Class construction. The * unsafe option relies on {@code sun.misc.Unsafe} and should be used with * caution as it may break on future JDKs or under strict security managers. * It is used when all constructors have been tried and the Java class could * not be instantiated. * * @param state boolean true = on, false = off */ public static void setUseUnsafe(boolean state) { useUnsafe = state; if (state) { try { unsafe = new Unsafe(); } catch (Exception e) { useUnsafe = false; } } } /** * Cached reference to InaccessibleObjectException class (Java 9+), or null if not available */ private static final Class INACCESSIBLE_OBJECT_EXCEPTION_CLASS; static { Class clazz = null; try { clazz = Class.forName("java.lang.reflect.InaccessibleObjectException"); } catch (ClassNotFoundException e) { // Java 8 or earlier - this exception doesn't exist } INACCESSIBLE_OBJECT_EXCEPTION_CLASS = clazz; } /** * Logs reflection access issues in a concise, readable format without stack traces. * Useful for expected access failures due to module restrictions or private access. * * @param accessible The field, method, or constructor that couldn't be accessed * @param e The exception that was thrown * @param operation Description of what was being attempted (e.g., "read field", "invoke method") */ public static void logAccessIssue(AccessibleObject accessible, Exception e, String operation) { if (!LOG.isLoggable(Level.FINEST)) { return; } String elementType; String elementName; String declaringClass; String modifiers; if (accessible instanceof Field) { Field field = (Field) accessible; elementType = "field"; elementName = field.getName(); declaringClass = field.getDeclaringClass().getName(); modifiers = Modifier.toString(field.getModifiers()); } else if (accessible instanceof Method) { Method method = (Method) accessible; elementType = "method"; elementName = method.getName() + "()"; declaringClass = method.getDeclaringClass().getName(); modifiers = Modifier.toString(method.getModifiers()); } else if (accessible instanceof Constructor) { Constructor constructor = (Constructor) accessible; elementType = "constructor"; elementName = constructor.getDeclaringClass().getSimpleName() + "()"; declaringClass = constructor.getDeclaringClass().getName(); modifiers = Modifier.toString(constructor.getModifiers()); } else { elementType = "member"; elementName = accessible.toString(); declaringClass = "unknown"; modifiers = ""; } // Determine the reason for the access failure String reason = null; if (e instanceof IllegalAccessException) { String msg = e.getMessage(); if (msg != null) { if (msg.contains("module")) { reason = "Java module system restriction"; } else if (msg.contains("private")) { reason = "private access"; } else if (msg.contains("protected")) { reason = "protected access"; } else if (msg.contains("package")) { reason = "package-private access"; } } } else if (INACCESSIBLE_OBJECT_EXCEPTION_CLASS != null && INACCESSIBLE_OBJECT_EXCEPTION_CLASS.isInstance(e)) { reason = "Java module system restriction (InaccessibleObjectException)"; } else if (e instanceof SecurityException) { reason = "Security manager restriction"; } if (reason == null) { reason = e.getClass().getSimpleName(); } // Log the concise message if (operation != null && !operation.isEmpty()) { LOG.log(Level.FINEST, "Cannot {0} {1} {2} ''{3}'' on {4} ({5})", new Object[]{operation, modifiers, elementType, elementName, declaringClass, reason}); } else { LOG.log(Level.FINEST, "Cannot access {0} {1} ''{2}'' on {3} ({4})", new Object[]{modifiers, elementType, elementName, declaringClass, reason}); } } /** * Convenience method for field access issues */ public static void logFieldAccessIssue(Field field, Exception e) { logAccessIssue(field, e, "read"); } /** * Convenience method for method invocation issues */ public static void logMethodAccessIssue(Method method, Exception e) { logAccessIssue(method, e, "invoke"); } /** * Convenience method for constructor access issues */ public static void logConstructorAccessIssue(Constructor constructor, Exception e) { logAccessIssue(constructor, e, "invoke"); } /** * Returns all equally "lowest" common supertypes (classes or interfaces) shared by both * {@code classA} and {@code classB}, excluding any types specified in {@code excludeSet}. * * @param classA the first class, may be null * @param classB the second class, may be null * @param excluded a set of classes or interfaces to exclude from the final result * @return a {@code Set} of the most specific common supertypes, excluding any in excluded set */ public static Set> findLowestCommonSupertypesExcluding( Class classA, Class classB, Set> excluded) { if (classA == null || classB == null) { return Collections.emptySet(); } if (classA.equals(classB)) { // If it's in the excluded list, return empty; otherwise return singleton return excluded.contains(classA) ? Collections.emptySet() : Collections.singleton(classA); } // 1) Get unmodifiable views for better performance Set> allA = getClassHierarchyInfo(classA).getAllSupertypes(); Set> allB = getClassHierarchyInfo(classB).getAllSupertypes(); // 2) Create a modifiable copy of the intersection, filtering excluded items Set> common = new LinkedHashSet<>(); for (Class type : allA) { if (allB.contains(type) && !excluded.contains(type)) { common.add(type); } } if (common.isEmpty()) { return Collections.emptySet(); } // 3) Sort by descending depth List> candidates = new ArrayList<>(common); candidates.sort((x, y) -> { int dx = getClassHierarchyInfo(x).getDepth(); int dy = getClassHierarchyInfo(y).getDepth(); return Integer.compare(dy, dx); // descending }); // 4) Identify "lowest" types Set> lowest = new LinkedHashSet<>(); Set> unionOfAncestors = new HashSet<>(); for (Class type : candidates) { if (unionOfAncestors.contains(type)) { // type is an ancestor of something already in 'lowest' continue; } // type is indeed a "lowest" so far lowest.add(type); // Add all type's supertypes to the union set unionOfAncestors.addAll(getClassHierarchyInfo(type).getAllSupertypes()); } return lowest; } /** * Returns all equally "lowest" common supertypes (classes or interfaces) that * both {@code classA} and {@code classB} share, automatically excluding * {@code Object, Serializable, Externalizable, Cloneable}. *

* This method is a convenience wrapper around * {@link #findLowestCommonSupertypesExcluding(Class, Class, Set)} using a skip list * that includes {@code Object, Serializable, Externalizable, Cloneable}. In other words, if the only common * ancestor is {@code Object.class}, this method returns an empty set. *

* *

Example: *

{@code
     * Set> supertypes = findLowestCommonSupertypes(Integer.class, Double.class);
     * // Potentially returns [Number, Comparable] because those are
     * // equally specific and not ancestors of one another, ignoring Object.class.
     * }
* * @param classA the first class, may be null * @param classB the second class, may be null * @return a {@code Set} of all equally "lowest" common supertypes, excluding * {@code Object, Serializable, Externalizable, Cloneable}; or an empty * set if none are found beyond {@code Object} (or if either input is null) * @see #findLowestCommonSupertypesExcluding(Class, Class, Set) */ public static Set> findLowestCommonSupertypes(Class classA, Class classB) { return findLowestCommonSupertypesExcluding(classA, classB, CollectionUtilities.setOf(Object.class, Serializable.class, Externalizable.class, Cloneable.class)); } /** * Returns the *single* most specific type from findLowestCommonSupertypes(...). * If there's more than one, returns any one (or null if none). */ public static Class findLowestCommonSupertype(Class classA, Class classB) { Set> all = findLowestCommonSupertypes(classA, classB); return all.isEmpty() ? null : all.iterator().next(); } /** * Gets the complete hierarchy information for a class, including all supertypes * and their inheritance distances from the source class. * * @param clazz The class to analyze * @return ClassHierarchyInfo containing all supertypes and distances */ public static ClassHierarchyInfo getClassHierarchyInfo(Class clazz) { return CLASS_HIERARCHY_CACHE.computeIfAbsent(clazz, key -> { // Compute all supertypes and their distances in one pass Set> allSupertypes = new LinkedHashSet<>(); Map, Integer> distanceMap = new HashMap<>(); // BFS to find all supertypes and compute distances in one pass Queue> queue = new ArrayDeque<>(); queue.add(key); distanceMap.put(key, 0); // Distance to self is 0 while (!queue.isEmpty()) { Class current = queue.poll(); int currentDistance = distanceMap.get(current); if (current != null && allSupertypes.add(current)) { // Add superclass with distance+1 Class superclass = current.getSuperclass(); if (superclass != null && !distanceMap.containsKey(superclass)) { distanceMap.put(superclass, currentDistance + 1); queue.add(superclass); } // Add interfaces with distance+1 for (Class iface : current.getInterfaces()) { if (!distanceMap.containsKey(iface)) { distanceMap.put(iface, currentDistance + 1); queue.add(iface); } } } } return new ClassHierarchyInfo(Collections.unmodifiableSet(allSupertypes), Collections.unmodifiableMap(distanceMap), key); }); } // Convenience boolean method public static boolean haveCommonAncestor(Class a, Class b) { return !findLowestCommonSupertypes(a, b).isEmpty(); } // Static fields for the SecurityChecker class private static final ClassValueSet BLOCKED_CLASSES = new ClassValueSet(); private static final Set BLOCKED_CLASS_NAMES_SET = new HashSet<>(SecurityChecker.SECURITY_BLOCKED_CLASS_NAMES); // Cache for classes that have been checked and found to be inheriting from blocked classes private static final ClassValueSet INHERITS_FROM_BLOCKED = new ClassValueSet(); // Cache for classes that have been checked and found to be safe private static final ClassValueSet VERIFIED_SAFE_CLASSES = new ClassValueSet(); static { // Pre-populate with all blocked classes for (Class blockedClass : SecurityChecker.SECURITY_BLOCKED_CLASSES.toSet()) { BLOCKED_CLASSES.add(blockedClass); } } private static final ClassValue SECURITY_CHECK_CACHE = new ClassValue() { @Override protected Boolean computeValue(Class type) { // Direct blocked class check (ultra-fast with ClassValueSet) if (BLOCKED_CLASSES.contains(type)) { return Boolean.TRUE; } // Fast name-based check if (BLOCKED_CLASS_NAMES_SET.contains(type.getName())) { return Boolean.TRUE; } // Check if already verified as inheriting from blocked if (INHERITS_FROM_BLOCKED.contains(type)) { return Boolean.TRUE; } // Check if already verified as safe if (VERIFIED_SAFE_CLASSES.contains(type)) { return Boolean.FALSE; } // Need to check inheritance - use ClassHierarchyInfo for (Class superType : getClassHierarchyInfo(type).getAllSupertypes()) { if (BLOCKED_CLASSES.contains(superType)) { // Cache for future checks INHERITS_FROM_BLOCKED.add(type); return Boolean.TRUE; } } // Class is safe VERIFIED_SAFE_CLASSES.add(type); return Boolean.FALSE; } }; public static class SecurityChecker { // Combine all security-sensitive classes in one place static final ClassValueSet SECURITY_BLOCKED_CLASSES = new ClassValueSet(Arrays.asList( ClassLoader.class, ProcessBuilder.class, Process.class, Constructor.class, Method.class, Field.class, Runtime.class, System.class )); // Add specific class names that might be loaded dynamically static final Set SECURITY_BLOCKED_CLASS_NAMES = new HashSet<>(Collections.singletonList( "java.lang.ProcessImpl" // Add any other specific class names )); /** * Checks if a class is blocked for security reasons. * * @param clazz The class to check * @return true if the class is blocked, false otherwise */ public static boolean isSecurityBlocked(Class clazz) { return SECURITY_CHECK_CACHE.get(clazz); } /** * Checks if a class name is directly in the blocked list. * Used before class loading. * * @param className The class name to check * @return true if the class name is blocked, false otherwise */ public static boolean isSecurityBlockedName(String className) { return BLOCKED_CLASS_NAMES_SET.contains(className); } /** * Throws an exception if the class is blocked for security reasons. * * @param clazz The class to verify * @throws SecurityException if the class is blocked */ public static void verifyClass(Class clazz) { if (isSecurityBlocked(clazz)) { throw new SecurityException( "For security reasons, json-io does not allow instantiation of: " + clazz.getName()); } } } }