Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.cedarsoftware.util.ReflectionUtils Maven / Gradle / Ivy
package com.cedarsoftware.util;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
/**
* Utilities to simplify writing reflective code as well as improve performance of reflective operations like
* method and annotation lookups.
*
* @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 final class ReflectionUtils {
/** System property key controlling the reflection cache size. */
private static final String CACHE_SIZE_PROPERTY = "reflection.utils.cache.size";
private static final int DEFAULT_CACHE_SIZE = 1500;
private static final int CACHE_SIZE = Math.max(1,
Integer.getInteger(CACHE_SIZE_PROPERTY, DEFAULT_CACHE_SIZE));
// Add a new cache for storing the sorted constructor arrays
private static final AtomicReference[]>> SORTED_CONSTRUCTORS_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
private static final AtomicReference>> CONSTRUCTOR_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
private static final AtomicReference> METHOD_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
private static final AtomicReference>> FIELDS_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
private static final AtomicReference> FIELD_NAME_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE * 10)));
private static final AtomicReference> CLASS_ANNOTATION_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
private static final AtomicReference> METHOD_ANNOTATION_CACHE =
new AtomicReference<>(ensureThreadSafe(new LRUCache<>(CACHE_SIZE)));
/** Wrap the map if it is not already concurrent. */
private static Map ensureThreadSafe(Map candidate) {
if (candidate instanceof ConcurrentMap || candidate instanceof LRUCache) {
return candidate; // already thread-safe
}
return new ConcurrentHashMapNullSafe<>(candidate);
}
private static void swap(AtomicReference ref, T newValue) {
Objects.requireNonNull(newValue, "cache must not be null");
ref.set(newValue); // atomic & happens-before
}
/**
* Sets a custom cache implementation for method lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing method lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setMethodCache(Map cache) {
swap(METHOD_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for field lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing field lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setClassFieldsCache(Map> cache) {
swap(FIELDS_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for field lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing field lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setFieldCache(Map cache) {
swap(FIELD_NAME_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for class annotation lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing class annotation lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setClassAnnotationCache(Map cache) {
swap(CLASS_ANNOTATION_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for method annotation lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing method annotation lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setMethodAnnotationCache(Map cache) {
swap(METHOD_ANNOTATION_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for constructor lookups.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing constructor lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setConstructorCache(Map> cache) {
swap(CONSTRUCTOR_CACHE, ensureThreadSafe(cache));
}
/**
* Sets a custom cache implementation for sorted constructors lookup.
*
* This method allows switching out the default LRUCache implementation with a custom
* cache implementation. The provided cache must be thread-safe and should implement
* the Map interface. This method is typically called once during application initialization.
*
*
* @param cache The custom cache implementation to use for storing constructor lookups.
* Must be thread-safe and implement Map interface.
*/
public static void setSortedConstructorsCache(Map[]> cache) {
swap(SORTED_CONSTRUCTORS_CACHE, ensureThreadSafe(cache));
}
private ReflectionUtils() { }
private static final class ClassAnnotationCacheKey {
private final String classLoaderName;
private final String className;
private final String annotationClassName;
private final int hash;
ClassAnnotationCacheKey(Class> clazz, Class extends Annotation> annotationClass) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.annotationClassName = annotationClass.getName();
this.hash = Objects.hash(classLoaderName, className, annotationClassName);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ClassAnnotationCacheKey)) return false;
ClassAnnotationCacheKey that = (ClassAnnotationCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className) &&
Objects.equals(annotationClassName, that.annotationClassName);
}
@Override
public int hashCode() {
return hash;
}
}
private static final class MethodAnnotationCacheKey {
private final String classLoaderName;
private final String className;
private final String methodName;
private final String parameterTypes;
private final String annotationClassName;
private final int hash;
MethodAnnotationCacheKey(Method method, Class extends Annotation> annotationClass) {
Class> declaringClass = method.getDeclaringClass();
this.classLoaderName = getClassLoaderName(declaringClass);
this.className = declaringClass.getName();
this.methodName = method.getName();
this.parameterTypes = makeParamKey(method.getParameterTypes());
this.annotationClassName = annotationClass.getName();
this.hash = Objects.hash(classLoaderName, className, methodName, parameterTypes, annotationClassName);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MethodAnnotationCacheKey)) return false;
MethodAnnotationCacheKey that = (MethodAnnotationCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className) &&
Objects.equals(methodName, that.methodName) &&
Objects.equals(parameterTypes, that.parameterTypes) &&
Objects.equals(annotationClassName, that.annotationClassName);
}
@Override
public int hashCode() {
return hash;
}
}
private static final class ConstructorCacheKey {
private final String classLoaderName;
private final String className;
private final String parameterTypes;
private final int hash;
ConstructorCacheKey(Class> clazz, Class>... types) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.parameterTypes = makeParamKey(types);
this.hash = Objects.hash(classLoaderName, className, parameterTypes);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ConstructorCacheKey)) return false;
ConstructorCacheKey that = (ConstructorCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className) &&
Objects.equals(parameterTypes, that.parameterTypes);
}
@Override
public int hashCode() {
return hash;
}
}
// Add this class definition with the other cache keys
private static final class SortedConstructorsCacheKey {
private final String classLoaderName;
private final String className;
private final int hash;
SortedConstructorsCacheKey(Class> clazz) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.hash = Objects.hash(classLoaderName, className);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SortedConstructorsCacheKey)) return false;
SortedConstructorsCacheKey that = (SortedConstructorsCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className);
}
@Override
public int hashCode() {
return hash;
}
}
private static final class FieldNameCacheKey {
private final String classLoaderName;
private final String className;
private final String fieldName;
private final int hash;
FieldNameCacheKey(Class> clazz, String fieldName) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.fieldName = fieldName;
this.hash = Objects.hash(classLoaderName, className, fieldName);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FieldNameCacheKey)) return false;
FieldNameCacheKey that = (FieldNameCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className) &&
Objects.equals(fieldName, that.fieldName);
}
@Override
public int hashCode() {
return hash;
}
}
private static final class FieldsCacheKey {
private final String classLoaderName;
private final String className;
private final Predicate predicate;
private final boolean deep;
private final int hash;
FieldsCacheKey(Class> clazz, Predicate predicate, boolean deep) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.predicate = predicate;
this.deep = deep;
// Include predicate in hash calculation
this.hash = Objects.hash(classLoaderName, className, deep, System.identityHashCode(predicate));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FieldsCacheKey)) return false;
FieldsCacheKey other = (FieldsCacheKey) o;
return deep == other.deep &&
Objects.equals(classLoaderName, other.classLoaderName) &&
Objects.equals(className, other.className) &&
predicate == other.predicate; // Use identity comparison for predicates
}
@Override
public int hashCode() {
return hash;
}
}
private static class MethodCacheKey {
private final String classLoaderName;
private final String className;
private final String methodName;
private final String parameterTypes;
private final int hash;
public MethodCacheKey(Class> clazz, String methodName, Class>... types) {
this.classLoaderName = getClassLoaderName(clazz);
this.className = clazz.getName();
this.methodName = methodName;
this.parameterTypes = makeParamKey(types);
// Pre-compute hash code
this.hash = Objects.hash(classLoaderName, className, methodName, parameterTypes);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MethodCacheKey)) return false;
MethodCacheKey that = (MethodCacheKey) o;
return Objects.equals(classLoaderName, that.classLoaderName) &&
Objects.equals(className, that.className) &&
Objects.equals(methodName, that.methodName) &&
Objects.equals(parameterTypes, that.parameterTypes);
}
@Override
public int hashCode() {
return hash;
}
}
public static final Predicate DEFAULT_FIELD_FILTER = field -> {
if (Modifier.isStatic(field.getModifiers())) {
return false;
}
String fieldName = field.getName();
Class> declaringClass = field.getDeclaringClass();
if (declaringClass.isEnum() &&
("internal".equals(fieldName) || "ENUM$VALUES".equals(fieldName))) {
return false;
}
if ("metaClass".equals(fieldName) &&
"groovy.lang.MetaClass".equals(field.getType().getName())) {
return false;
}
return !(declaringClass.isAssignableFrom(Enum.class) &&
(fieldName.equals("hash") || fieldName.equals("ordinal")));
};
/**
* Searches for a specific annotation on a class, examining the entire inheritance hierarchy.
* Results (including misses) are cached for performance.
*
* This method performs an exhaustive search through:
*
* The class itself
* All superclasses
* All implemented interfaces
* All super-interfaces
*
*
* Key behaviors:
*
* Caches both found annotations and misses (nulls)
* Handles different classloaders correctly
* Uses depth-first search through the inheritance hierarchy
* Prevents circular reference issues
* Returns the first matching annotation found
* Thread-safe implementation
*
*
* Example usage:
*
* JsonObject anno = ReflectionUtils.getClassAnnotation(MyClass.class, JsonObject.class);
* if (anno != null) {
* // Process annotation...
* }
*
*
* @param classToCheck The class to search for the annotation
* @param annoClass The annotation class to search for
* @param The type of the annotation
* @return The annotation if found, null otherwise
* @throws IllegalArgumentException if either classToCheck or annoClass is null
*/
public static T getClassAnnotation(final Class> classToCheck, final Class annoClass) {
if (classToCheck == null) {
return null;
}
Convention.throwIfNull(annoClass, "annotation class cannot be null");
final ClassAnnotationCacheKey key = new ClassAnnotationCacheKey(classToCheck, annoClass);
// Use computeIfAbsent to ensure only one instance (or null) is stored per key
Annotation annotation = CLASS_ANNOTATION_CACHE.get().computeIfAbsent(key, k -> {
// If findClassAnnotation() returns null, that null will be stored in the cache
return findClassAnnotation(classToCheck, annoClass);
});
// Cast the stored Annotation (or null) back to the desired type
return (T) annotation;
}
private static T findClassAnnotation(Class> classToCheck, Class annoClass) {
final Set> visited = new HashSet<>();
final LinkedList> stack = new LinkedList<>();
stack.add(classToCheck);
while (!stack.isEmpty()) {
Class> classToChk = stack.pop();
if (classToChk == null || visited.contains(classToChk)) {
continue;
}
visited.add(classToChk);
T a = classToChk.getAnnotation(annoClass);
if (a != null) {
return a;
}
stack.push(classToChk.getSuperclass());
addInterfaces(classToChk, stack);
}
return null;
}
private static void addInterfaces(final Class> classToCheck, final LinkedList> stack) {
for (Class> interFace : classToCheck.getInterfaces()) {
stack.push(interFace);
}
}
/**
* Searches for a specific annotation on a method, examining the entire inheritance hierarchy.
* Results (including misses) are cached for performance.
*
* This method performs an exhaustive search through:
*
* The method in the declaring class
* Matching methods in all superclasses
* Matching methods in all implemented interfaces
* Matching methods in all super-interfaces
*
*
* Key behaviors:
*
* Caches both found annotations and misses (nulls)
* Handles different classloaders correctly
* Uses depth-first search through the inheritance hierarchy
* Matches methods by name and parameter types
* Prevents circular reference issues
* Returns the first matching annotation found
* Thread-safe implementation
*
*
* Example usage:
*
* Method method = obj.getClass().getMethod("processData", String.class);
* JsonProperty anno = ReflectionUtils.getMethodAnnotation(method, JsonProperty.class);
* if (anno != null) {
* // Process annotation...
* }
*
*
* @param method The method to search for the annotation
* @param annoClass The annotation class to search for
* @param The type of the annotation
* @return The annotation if found, null otherwise
* @throws IllegalArgumentException if either method or annoClass is null
*/
public static T getMethodAnnotation(final Method method, final Class annoClass) {
Convention.throwIfNull(method, "method cannot be null");
Convention.throwIfNull(annoClass, "annotation class cannot be null");
final MethodAnnotationCacheKey key = new MethodAnnotationCacheKey(method, annoClass);
// Atomically retrieve or compute the annotation from the cache
Annotation annotation = METHOD_ANNOTATION_CACHE.get().computeIfAbsent(key, k -> {
// Search the class hierarchy
Class> currentClass = method.getDeclaringClass();
while (currentClass != null) {
try {
Method currentMethod = currentClass.getDeclaredMethod(
method.getName(),
method.getParameterTypes()
);
T found = currentMethod.getAnnotation(annoClass);
if (found != null) {
return found; // store in cache
}
} catch (Exception ignored) {
// Not found in currentClass, go to superclass
}
currentClass = currentClass.getSuperclass();
}
// Check interfaces
for (Class> iface : method.getDeclaringClass().getInterfaces()) {
try {
Method ifaceMethod = iface.getMethod(method.getName(), method.getParameterTypes());
T found = ifaceMethod.getAnnotation(annoClass);
if (found != null) {
return found; // store in cache
}
} catch (Exception ignored) {
// Not found in this interface, move on
}
}
// No annotation found - store null
return null;
});
// Cast result back to T (or null)
return (T) annotation;
}
/**
* Retrieves a specific field from a class by name, searching through the entire class hierarchy
* (including superclasses). Results are cached for performance.
*
* This method:
*
* Searches through all fields (public, protected, package, private)
* Includes fields from superclasses
* Excludes static fields
* Makes non-public fields accessible
* Caches results (including misses) for performance
*
*
* Example usage:
*
* Field nameField = ReflectionUtils.getField(Employee.class, "name");
* if (nameField != null) {
* nameField.set(employee, "John");
* }
*
*
* @param c The class to search for the field
* @param fieldName The name of the field to find
* @return The Field object if found, null if the field doesn't exist
* @throws IllegalArgumentException if either the class or fieldName is null
*/
public static Field getField(Class> c, String fieldName) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(fieldName, "fieldName cannot be null");
final FieldNameCacheKey key = new FieldNameCacheKey(c, fieldName);
// Atomically retrieve or compute the field from the cache
return FIELD_NAME_CACHE.get().computeIfAbsent(key, k -> {
Collection fields = getAllDeclaredFields(c); // returns all fields in c's hierarchy
for (Field field : fields) {
if (fieldName.equals(field.getName())) {
return field;
}
}
return null; // no matching field
});
}
/**
* Retrieves the declared fields of a class (not it's parent) using a custom field filter, with caching for
* performance. This method provides direct field access with customizable filtering criteria.
*
* Key features:
*
* Custom field filtering through provided Predicate
* Returns only fields declared directly on the specified class (not from superclasses)
* Caches results for both successful lookups and misses
* Makes non-public fields accessible when possible
* Returns an unmodifiable List to prevent modification
*
*
* Implementation details:
*
* Thread-safe caching mechanism
* Handles different classloaders correctly
* Maintains consistent order of fields
* Caches results per class/filter combination
*
*
* Example usage:
*
{@code
* // Get non-static public fields only
* List publicFields = getDeclaredFields(MyClass.class,
* field -> !Modifier.isStatic(field.getModifiers()) &&
* Modifier.isPublic(field.getModifiers()));
*
* // Get fields with specific names
* Set allowedNames = Set.of("id", "name", "value");
* List specificFields = getDeclaredFields(MyClass.class,
* field -> allowedNames.contains(field.getName()));
* }
*
* @param c The class whose declared fields are to be retrieved (must not be null)
* @param fieldFilter Predicate to determine which fields should be included (must not be null)
* @return An unmodifiable list of fields that match the filter criteria
* @throws IllegalArgumentException if either the class or fieldFilter is null
* @see Field
* @see Predicate
* @see #getAllDeclaredFields(Class) For retrieving fields from the entire class hierarchy
*/
public static List getDeclaredFields(final Class> c, final Predicate fieldFilter) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(fieldFilter, "fieldFilter cannot be null");
final FieldsCacheKey key = new FieldsCacheKey(c, fieldFilter, false);
// Atomically compute and cache the unmodifiable List if absent
Collection cachedFields = FIELDS_CACHE.get().computeIfAbsent(key, k -> {
Field[] declared = c.getDeclaredFields();
List filteredList = new ArrayList<>(declared.length);
for (Field field : declared) {
if (!fieldFilter.test(field)) {
continue;
}
ClassUtilities.trySetAccessible(field);
filteredList.add(field);
}
// Return an unmodifiable List so it cannot be mutated later
return Collections.unmodifiableList(filteredList);
});
// Cast back to List (we stored an unmodifiable List in the map)
return (List) cachedFields;
}
/**
* Retrieves the declared fields of a class (not it's parent) using the default field filter, with caching for
* performance. This method provides the same functionality as {@link #getDeclaredFields(Class, Predicate)}
* but uses the default field filter.
*
* The default filter excludes:
*
* Static fields
* Internal enum fields ("internal" and "ENUM$VALUES")
* Enum base class fields ("hash" and "ordinal")
* Groovy's metaClass field
*
*
*
* @param c The class whose complete field hierarchy is to be retrieved
* @return An unmodifiable list of all fields in the class hierarchy that pass the default filter
* @throws IllegalArgumentException if the class is null
* @see #getDeclaredFields(Class, Predicate) For retrieving fields with a custom filter
*/
public static List getDeclaredFields(final Class> c) {
return getDeclaredFields(c, DEFAULT_FIELD_FILTER);
}
/**
* Retrieves all fields from a class and its complete inheritance hierarchy using a custom field filter.
*
* Key features:
*
* Custom field filtering through provided Predicate
* Includes fields from the specified class and all superclasses
* Caches results for performance optimization
* Makes non-public fields accessible when possible
*
*
* Implementation details:
*
* Thread-safe caching mechanism
* Maintains consistent order (subclass fields before superclass fields)
* Returns an unmodifiable List to prevent modification
* Uses recursive caching strategy for optimal performance
*
*
* Example usage:
*
{@code
* // Get all non-transient fields in hierarchy
* List persistentFields = getAllDeclaredFields(MyClass.class,
* field -> !Modifier.isTransient(field.getModifiers()));
*
* // Get all fields matching specific name pattern
* List matchingFields = getAllDeclaredFields(MyClass.class,
* field -> field.getName().startsWith("customer"));
* }
*
* @param c The class whose complete field hierarchy is to be retrieved (must not be null)
* @param fieldFilter Predicate to determine which fields should be included (must not be null)
* @return An unmodifiable list of all matching fields in the class hierarchy
* @throws IllegalArgumentException if either the class or fieldFilter is null
* @see Field
* @see Predicate
* @see #getAllDeclaredFields(Class) For retrieving fields using the default filter
*/
public static List getAllDeclaredFields(final Class> c, final Predicate fieldFilter) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(fieldFilter, "fieldFilter cannot be null");
final FieldsCacheKey key = new FieldsCacheKey(c, fieldFilter, true);
// Atomically compute and cache the unmodifiable list, if not already present
Collection cached = FIELDS_CACHE.get().computeIfAbsent(key, k -> {
// Collect fields from class + superclasses
List allFields = new ArrayList<>();
Class> current = c;
while (current != null) {
allFields.addAll(getDeclaredFields(current, fieldFilter));
current = current.getSuperclass();
}
// Return an unmodifiable list to prevent further modification
return Collections.unmodifiableList(allFields);
});
// We know we stored a List, so cast is safe
return (List) cached;
}
/**
* Retrieves all fields from a class and its complete inheritance hierarchy using the default field filter.
* The default filter excludes:
*
* Static fields
* Internal enum fields ("internal" and "ENUM$VALUES")
* Enum base class fields ("hash" and "ordinal")
* Groovy's metaClass field
*
*
* This method is equivalent to calling {@link #getAllDeclaredFields(Class, Predicate)} with the default
* field filter.
*
* @param c The class whose complete field hierarchy is to be retrieved
* @return An unmodifiable list of all fields in the class hierarchy that pass the default filter
* @throws IllegalArgumentException if the class is null
* @see #getAllDeclaredFields(Class, Predicate) For retrieving fields with a custom filter
*/
public static List getAllDeclaredFields(final Class> c) {
return getAllDeclaredFields(c, DEFAULT_FIELD_FILTER);
}
/**
* Returns all Fields from a class (including inherited) as a Map filtered by the provided predicate.
*
* The returned Map uses String field names as keys and Field objects as values, with special
* handling for name collisions across the inheritance hierarchy.
*
* Field name mapping rules:
*
* Simple field names (e.g., "name") are used when no collision exists
* On collision, fully qualified names (e.g., "com.example.Parent.name") are used
* Child class fields take precedence for simple name mapping
* Parent class fields use fully qualified names when shadowed
*
*
* Example usage:
*
{@code
* // Get all non-transient fields
* Map persistentFields = getAllDeclaredFieldsMap(
* MyClass.class,
* field -> !Modifier.isTransient(field.getModifiers())
* );
*
* // Get all fields with specific annotation
* Map annotatedFields = getAllDeclaredFieldsMap(
* MyClass.class,
* field -> field.isAnnotationPresent(MyAnnotation.class)
* );
* }
*
* @param c Class whose fields are being fetched (must not be null)
* @param fieldFilter Predicate to determine which fields should be included (must not be null)
* @return Map of filtered fields, keyed by field name (or fully qualified name on collision)
* @throws IllegalArgumentException if either the class or fieldFilter is null
* @see #getAllDeclaredFields(Class, Predicate)
* @see #getAllDeclaredFieldsMap(Class)
*/
public static Map getAllDeclaredFieldsMap(Class> c, Predicate fieldFilter) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(fieldFilter, "fieldFilter cannot be null");
Map fieldMap = new LinkedHashMap<>();
Collection fields = getAllDeclaredFields(c, fieldFilter); // Uses FIELDS_CACHE internally
for (Field field : fields) {
String fieldName = field.getName();
if (fieldMap.containsKey(fieldName)) { // Can happen when parent and child class both have private field with same name
fieldMap.put(field.getDeclaringClass().getName() + '.' + fieldName, field);
} else {
fieldMap.put(fieldName, field);
}
}
return fieldMap;
}
/**
* Returns all Fields from a class (including inherited) as a Map, using the default field filter.
* This method provides the same functionality as {@link #getAllDeclaredFieldsMap(Class, Predicate)}
* but uses the default field filter which excludes:
*
* Static fields
* Internal enum fields ("internal" and "ENUM$VALUES")
* Enum base class fields ("hash" and "ordinal")
* Groovy's metaClass field
*
*
* @param c Class whose fields are being fetched
* @return Map of filtered fields, keyed by field name (or fully qualified name on collision)
* @throws IllegalArgumentException if the class is null
* @see #getAllDeclaredFieldsMap(Class, Predicate)
*/
public static Map getAllDeclaredFieldsMap(Class> c) {
return getAllDeclaredFieldsMap(c, DEFAULT_FIELD_FILTER);
}
/**
* @deprecated As of 3.0.0, replaced by {@link #getAllDeclaredFields(Class)}.
* Note that getAllDeclaredFields() includes transient fields and synthetic fields
* (like "this$"). If you need the old behavior, filter the additional fields:
* {@code
* // Get fields excluding transient and synthetic fields
* Map fields = getAllDeclaredFields(MyClass.class, field ->
* DEFAULT_FIELD_FILTER.test(field) &&
* !Modifier.isTransient(field.getModifiers()) &&
* !field.isSynthetic()
* );
* }
* This method may be removed in 3.0.0.
*/
@Deprecated
public static Collection getDeepDeclaredFields(Class> c) {
Convention.throwIfNull(c, "Class cannot be null");
// Combine DEFAULT_FIELD_FILTER with additional criteria for legacy behavior
Predicate legacyFilter = field ->
DEFAULT_FIELD_FILTER.test(field) &&
!Modifier.isTransient(field.getModifiers()) &&
!field.isSynthetic();
// Use the getAllDeclaredFields with the combined filter
return getAllDeclaredFields(c, legacyFilter);
}
/**
* @deprecated As of 3.0.0, replaced by {@link #getAllDeclaredFieldsMap(Class)}.
* Note that getAllDeclaredFieldsMap() includes transient fields and synthetic fields
* (like "this$"). If you need the old behavior, filter the additional fields:
* {@code
* // Get fields excluding transient and synthetic fields
* List fields = getAllDeclaredFieldsMap(MyClass.class, field ->
* DEFAULT_FIELD_FILTER.test(field) &&
* !Modifier.isTransient(field.getModifiers()) &&
* !field.isSynthetic()
* );
* }
* This method may be removed in 3.0.0.
*/
@Deprecated
public static Map getDeepDeclaredFieldMap(Class> c) {
Convention.throwIfNull(c, "class cannot be null");
// Combine DEFAULT_FIELD_FILTER with additional criteria for legacy behavior
Predicate legacyFilter = field ->
DEFAULT_FIELD_FILTER.test(field) &&
!Modifier.isTransient(field.getModifiers()) &&
!field.isSynthetic();
return getAllDeclaredFieldsMap(c, legacyFilter);
}
/**
* @deprecated As of 3.0.0, replaced by {@link #getAllDeclaredFields(Class)}.
* Note that getAllDeclaredFields() includes transient fields and synthetic fields
* (like "this$"). If you need the old behavior, filter the additional fields:
* {@code
// Combine DEFAULT_FIELD_FILTER with additional criteria for legacy behavior
Predicate legacyFilter = field ->
DEFAULT_FIELD_FILTER.test(field) &&
!Modifier.isTransient(field.getModifiers()) &&
!field.isSynthetic();
* }
* This method will be removed in 3.0.0 or soon after.
*/
@Deprecated
public static void getDeclaredFields(Class> c, Collection fields) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(fields, "fields collection cannot be null");
try {
// Combine DEFAULT_FIELD_FILTER with additional criteria for legacy behavior
Predicate legacyFilter = field ->
DEFAULT_FIELD_FILTER.test(field) &&
!Modifier.isTransient(field.getModifiers()) &&
!field.isSynthetic();
// Get filtered fields and add them to the provided collection
List filteredFields = getDeclaredFields(c, legacyFilter);
fields.addAll(filteredFields);
} catch (Throwable t) {
ExceptionUtilities.safelyIgnoreException(t);
}
}
/**
* Simplifies reflective method invocation by wrapping checked exceptions into runtime exceptions.
* This method provides a cleaner API for reflection-based method calls.
*
* Key features:
*
* Converts checked exceptions to runtime exceptions
* Preserves the original exception cause
* Provides clear error messages
* Handles null checking for both method and instance
*
*
* Exception handling:
*
* IllegalAccessException → RuntimeException
* InvocationTargetException → RuntimeException (with target exception)
* Null method → IllegalArgumentException
* Null instance → IllegalArgumentException
*
*
* Example usage:
*
* Method method = ReflectionUtils.getMethod(obj.getClass(), "processData", String.class);
* Object result = ReflectionUtils.call(obj, method, "input data");
*
* // No need for try-catch blocks for checked exceptions
* // Just handle RuntimeException if needed
*
*
* @param instance The object instance on which to call the method
* @param method The Method object representing the method to call
* @param args The arguments to pass to the method (may be empty)
* @return The result of the method invocation, or null for void methods
* @throws IllegalArgumentException if either method or instance is null
* @throws RuntimeException if the method is inaccessible or throws an exception
* @see Method#invoke(Object, Object...) For the underlying reflection mechanism
*/
public static Object call(Object instance, Method method, Object... args) {
if (method == null) {
String className = (instance == null) ? "null instance" : instance.getClass().getName();
throw new IllegalArgumentException("null Method passed to ReflectionUtils.call() on instance of type: " + className);
}
if (instance == null) {
throw new IllegalArgumentException("Cannot call [" + method.getName() + "()] on a null object.");
}
try {
return method.invoke(instance, args);
} catch (IllegalAccessException | InvocationTargetException e) {
ExceptionUtilities.uncheckedThrow(e);
return null; // never executed
}
}
/**
* Provides a simplified, cached reflection API for method invocation using method name.
* This method combines method lookup and invocation in one step, with results cached
* for performance.
*
* Key features:
*
* Caches method lookups for improved performance
* Handles different classloaders correctly
* Converts checked exceptions to runtime exceptions
* Caches both successful lookups and misses
* Thread-safe implementation
*
*
* Limitations:
*
* Does not distinguish between overloaded methods with same parameter count
* Only matches by method name and parameter count
* Always selects the first matching method found
* Only finds public methods
*
*
* Exception handling:
*
* Method not found → IllegalArgumentException
* IllegalAccessException → RuntimeException
* InvocationTargetException → RuntimeException (with target exception)
* Null instance/methodName → IllegalArgumentException
*
*
* Example usage:
*
* // Simple case - no method overloading
* Object result = ReflectionUtils.call(myObject, "processData", "input");
*
* // For overloaded methods, use the more specific call() method:
* Method specific = ReflectionUtils.getMethod(myObject.getClass(), "processData", String.class);
* Object result = ReflectionUtils.call(myObject, specific, "input");
*
*
* @param instance The object instance on which to call the method
* @param methodName The name of the method to call
* @param args The arguments to pass to the method (may be empty)
* @return The result of the method invocation, or null for void methods
* @throws IllegalArgumentException if the method cannot be found, or if instance/methodName is null
* @throws RuntimeException if the method is inaccessible or throws an exception
* @see #call(Object, Method, Object...) For handling overloaded methods
* @see #getMethod(Class, String, Class...) For explicit method lookup with parameter types
*/
public static Object call(Object instance, String methodName, Object... args) {
Method method = getMethod(instance, methodName, args.length);
try {
return method.invoke(instance, args);
} catch (IllegalAccessException | InvocationTargetException e) {
ExceptionUtilities.uncheckedThrow(e);
return null; // never executed
}
}
/**
* Retrieves a method of any access level by name and parameter types, with sophisticated
* caching for optimal performance. This method searches through the class hierarchy and
* attempts to make non-public methods accessible.
*
* Key features:
*
* Finds methods of any access level (public, protected, package, private)
* Includes bridge methods (compiler-generated for generic type erasure)
* Includes synthetic methods (compiler-generated for lambdas, inner classes)
* Attempts to make non-public methods accessible
* Caches both successful lookups and misses
* Handles different classloaders correctly
* Thread-safe implementation
* Searches entire inheritance hierarchy
*
*
* @param c The class to search for the method
* @param methodName The name of the method to find
* @param types The parameter types for the method (empty array for no-arg methods)
* @return The Method object if found and made accessible, null if not found
* @throws IllegalArgumentException if class or methodName is null
*/
public static Method getMethod(Class> c, String methodName, Class>... types) {
Convention.throwIfNull(c, "class cannot be null");
Convention.throwIfNull(methodName, "methodName cannot be null");
final MethodCacheKey key = new MethodCacheKey(c, methodName, types);
// Atomically retrieve (or compute) the method
return METHOD_CACHE.get().computeIfAbsent(key, k -> {
Method method = null;
Class> current = c;
while (current != null && method == null) {
try {
method = current.getDeclaredMethod(methodName, types);
ClassUtilities.trySetAccessible(method);
} catch (Exception ignored) {
// Move on up the superclass chain
}
current = current.getSuperclass();
}
// Will be null if not found
return method;
});
}
/**
* Retrieves a method by name and argument count from an object instance (or Class), using a
* deterministic selection strategy when multiple matching methods exist.
*
* Key features:
*
* Finds methods of any access level (public, protected, package, private)
* Uses deterministic method selection strategy
* Attempts to make non-public methods accessible
* Caches both successful lookups and misses
* Handles different classloaders correctly
* Thread-safe implementation
* Searches entire inheritance hierarchy
*
*
* Method selection priority (when multiple methods match):
*
* 1. Non-synthetic/non-bridge methods preferred
* 2. Higher accessibility preferred (public > protected > package > private)
* 3. Most specific declaring class in hierarchy preferred
*
*
* Example usage:
*
* // Will select most accessible, non-synthetic method with two parameters
* Method method = ReflectionUtils.getMethod(myObject, "processData", 2);
*
* // For exact method selection, use getMethod with specific types:
* Method specific = ReflectionUtils.getMethod(
* myObject.getClass(),
* "processData",
* String.class, Integer.class
* );
*
*
* @param instance The object instance on which to find the method (can also be a Class)
* @param methodName The name of the method to find
* @param argCount The number of parameters the method should have
* @return The Method object, made accessible if necessary
* @throws IllegalArgumentException if the method is not found or if bean/methodName is null
* @see #getMethod(Class, String, Class...) For finding methods with specific parameter types
*/
public static Method getMethod(Object instance, String methodName, int argCount) {
Convention.throwIfNull(instance, "Object instance cannot be null");
Convention.throwIfNull(methodName, "Method name cannot be null");
if (argCount < 0) {
throw new IllegalArgumentException("Argument count cannot be negative");
}
Class> beanClass = (instance instanceof Class) ? (Class>) instance : instance.getClass();
Class>[] types = new Class>[argCount];
Arrays.fill(types, Object.class);
MethodCacheKey key = new MethodCacheKey(beanClass, methodName, types);
// Check cache first
Method cached = METHOD_CACHE.get().get(key);
if (cached != null || METHOD_CACHE.get().containsKey(key)) {
return cached;
}
// Collect all matching methods
List candidates = new ArrayList<>();
Class> current = beanClass;
while (current != null) {
for (Method method : current.getDeclaredMethods()) {
if (method.getName().equals(methodName) && method.getParameterCount() == argCount) {
candidates.add(method);
}
}
current = current.getSuperclass();
}
if (candidates.isEmpty()) {
throw new IllegalArgumentException(
String.format("Method '%s' with %d parameters not found in %s or its superclasses",
methodName, argCount, beanClass.getName())
);
}
// Select the best matching method using our composite strategy
Method selected = selectMethod(candidates);
// Attempt to make the method accessible
ClassUtilities.trySetAccessible(selected);
// Cache the result
METHOD_CACHE.get().put(key, selected);
return selected;
}
/**
* Selects the most appropriate method using a composite selection strategy.
* Selection criteria are applied in order of priority.
*/
private static Method selectMethod(List candidates) {
return candidates.stream()
.min((m1, m2) -> {
// First, prefer non-synthetic/non-bridge methods
if (m1.isSynthetic() != m2.isSynthetic()) {
return m1.isSynthetic() ? 1 : -1;
}
if (m1.isBridge() != m2.isBridge()) {
return m1.isBridge() ? 1 : -1;
}
// Then, prefer more accessible methods
int accessDiff = getAccessibilityScore(m2.getModifiers()) -
getAccessibilityScore(m1.getModifiers());
if (accessDiff != 0) return accessDiff;
// Finally, prefer methods declared in most specific class
if (m1.getDeclaringClass().isAssignableFrom(m2.getDeclaringClass())) return 1;
if (m2.getDeclaringClass().isAssignableFrom(m1.getDeclaringClass())) return -1;
return 0;
})
.orElse(candidates.get(0));
}
/**
* Returns an accessibility score for method modifiers.
* Higher scores indicate greater accessibility.
*/
private static int getAccessibilityScore(int modifiers) {
if (Modifier.isPublic(modifiers)) return 4;
if (Modifier.isProtected(modifiers)) return 3;
if (Modifier.isPrivate(modifiers)) return 1;
return 2; // package-private
}
/**
* Gets a constructor for the specified class with the given parameter types,
* regardless of access level (public, protected, private, or package).
* Both successful lookups and misses are cached for performance.
*
* This method:
*
* Searches for constructors of any access level
* Attempts to make non-public constructors accessible
* Returns the constructor even if it cannot be made accessible
* Caches both found constructors and misses
* Handles different classloaders correctly
*
*
* Note: Finding a constructor does not guarantee that the caller has the necessary
* permissions to invoke it. Security managers or module restrictions may prevent
* access even if the constructor is found and marked accessible.
*
* @param clazz The class whose constructor is to be retrieved
* @param parameterTypes The parameter types for the constructor
* @return The constructor matching the specified parameters, or null if not found
* @throws IllegalArgumentException if the class is null
*/
public static Constructor> getConstructor(Class> clazz, Class>... parameterTypes) {
Convention.throwIfNull(clazz, "class cannot be null");
final ConstructorCacheKey key = new ConstructorCacheKey(clazz, parameterTypes);
// Atomically retrieve or compute the cached constructor
return CONSTRUCTOR_CACHE.get().computeIfAbsent(key, k -> {
try {
// Try to fetch the constructor reflectively
Constructor> ctor = clazz.getDeclaredConstructor(parameterTypes);
ClassUtilities.trySetAccessible(ctor);
return ctor;
} catch (Exception ignored) {
// If no such constructor exists, store null in the cache
return null;
}
});
}
/**
* Returns all constructors for a class, ordered optimally for instantiation.
* Constructors are ordered by accessibility (public, protected, package, private)
* and within each level by parameter count (most specific first).
*
* @param clazz The class to get constructors for
* @return Array of constructors in optimal order
*/
public static Constructor>[] getAllConstructors(Class> clazz) {
if (clazz == null) {
return new Constructor>[0];
}
// Create proper cache key with classloader information
SortedConstructorsCacheKey key = new SortedConstructorsCacheKey(clazz);
// Use the cache to avoid repeated sorting
return SORTED_CONSTRUCTORS_CACHE.get().computeIfAbsent(key,
k -> getAllConstructorsInternal(clazz));
}
/**
* Worker method that retrieves and sorts constructors.
* This method ensures all constructors are accessible and cached individually.
*/
private static Constructor>[] getAllConstructorsInternal(Class> clazz) {
// Get the declared constructors
Constructor>[] declared = clazz.getDeclaredConstructors();
if (declared.length == 0) {
return declared;
}
// Cache each constructor individually and ensure they're accessible
for (int i = 0; i < declared.length; i++) {
final Constructor> ctor = declared[i];
Class>[] paramTypes = ctor.getParameterTypes();
ConstructorCacheKey key = new ConstructorCacheKey(clazz, paramTypes);
// Retrieve from cache or add to cache
declared[i] = CONSTRUCTOR_CACHE.get().computeIfAbsent(key, k -> {
ClassUtilities.trySetAccessible(ctor);
return ctor;
});
}
// Create a sorted copy of the constructors
Constructor>[] result = new Constructor>[declared.length];
System.arraycopy(declared, 0, result, 0, declared.length);
// Sort the constructors in optimal order if there's more than one
if (result.length > 1) {
boolean isFinal = Modifier.isFinal(clazz.getModifiers());
boolean isException = Throwable.class.isAssignableFrom(clazz);
Arrays.sort(result, (c1, c2) -> {
// First, sort by accessibility (public > protected > package > private)
int mod1 = c1.getModifiers();
int mod2 = c2.getModifiers();
boolean isPublic1 = Modifier.isPublic(mod1);
boolean isPublic2 = Modifier.isPublic(mod2);
boolean isProtected1 = Modifier.isProtected(mod1);
boolean isProtected2 = Modifier.isProtected(mod2);
boolean isPrivate1 = Modifier.isPrivate(mod1);
boolean isPrivate2 = Modifier.isPrivate(mod2);
// Compare accessibility levels
if (isPublic1 != isPublic2) {
return isPublic1 ? -1 : 1; // public first
}
if (isProtected1 != isProtected2) {
return isProtected1 ? -1 : 1; // protected before package/private
}
if (isPrivate1 != isPrivate2) {
return isPrivate1 ? 1 : -1; // private last
}
// Within same accessibility level, sort by parameter count
int paramDiff = c1.getParameterCount() - c2.getParameterCount();
// For exceptions/final classes: prefer more parameters
// For regular classes: also prefer more parameters (more specific first)
if (isFinal || isException) {
return -paramDiff; // More parameters first
} else {
return -paramDiff; // More parameters first (more specific)
}
});
}
return result;
}
private static String makeParamKey(Class>... parameterTypes) {
if (parameterTypes == null || parameterTypes.length == 0) {
return "";
}
StringBuilder builder = new StringBuilder(32);
builder.append(':');
for (int i = 0; i < parameterTypes.length; i++) {
if (i > 0) {
builder.append('|');
}
Class> param = parameterTypes[i];
builder.append(param.getName());
}
return builder.toString();
}
/**
* Fetches a no-argument method from the specified class, caching the result for subsequent lookups.
* This is intended for methods that are not overloaded and require no arguments
* (e.g., simple getter methods).
*
* If the class contains multiple methods with the same name, an
* {@code IllegalArgumentException} is thrown.
*
* @param clazz the class that contains the desired method
* @param methodName the name of the no-argument method to locate
* @return the {@code Method} instance found on the given class
* @throws IllegalArgumentException if the method is not found or if multiple
* methods with the same name exist
*/
public static Method getNonOverloadedMethod(Class> clazz, String methodName) {
if (clazz == null) {
throw new IllegalArgumentException("Attempted to call getMethod() [" + methodName + "()] on a null class.");
}
if (StringUtilities.isEmpty(methodName)) {
throw new IllegalArgumentException("Attempted to call getMethod() with a null or blank method name on class: " + clazz.getName());
}
// Create a cache key for a method with no parameters
MethodCacheKey key = new MethodCacheKey(clazz, methodName);
return METHOD_CACHE.get().computeIfAbsent(key, k -> {
Method foundMethod = null;
for (Method m : clazz.getMethods()) {
if (methodName.equals(m.getName())) {
if (foundMethod != null) {
throw new IllegalArgumentException("Method: " + methodName + "() called on a class with overloaded methods "
+ "- ambiguous as to which one to return. Use getMethod() with argument types or argument count.");
}
foundMethod = m;
}
}
if (foundMethod == null) {
throw new IllegalArgumentException("Method: " + methodName + "() is not found on class: " + clazz.getName()
+ ". Perhaps the method is protected, private, or misspelled?");
}
return foundMethod;
});
}
/**
* Return the name of the class on the object, or "null" if the object is null.
* @param o Object to get the class name.
* @return String name of the class or "null"
*/
public static String getClassName(Object o) {
return o == null ? "null" : o.getClass().getName();
}
// Constant pool tags
private final static int CONSTANT_UTF8 = 1;
private final static int CONSTANT_INTEGER = 3;
private final static int CONSTANT_FLOAT = 4;
private final static int CONSTANT_LONG = 5;
private final static int CONSTANT_DOUBLE = 6;
private final static int CONSTANT_CLASS = 7;
private final static int CONSTANT_STRING = 8;
private final static int CONSTANT_FIELDREF = 9;
private final static int CONSTANT_METHODREF = 10;
private final static int CONSTANT_INTERFACEMETHODREF = 11;
private final static int CONSTANT_NAMEANDTYPE = 12;
private final static int CONSTANT_METHODHANDLE = 15;
private final static int CONSTANT_METHODTYPE = 16;
private final static int CONSTANT_DYNAMIC = 17;
private final static int CONSTANT_INVOKEDYNAMIC = 18;
private final static int CONSTANT_MODULE = 19;
private final static int CONSTANT_PACKAGE = 20;
/**
* Given a byte[] of a Java .class file (compiled Java), this code will retrieve the class name from those bytes.
* This method supports class files up to the latest JDK version.
*
* @param byteCode byte[] of compiled byte code
* @return String fully qualified class name
* @throws IOException if there are problems reading the byte code (thrown as unchecked)
* @throws IllegalStateException if the class file format is not recognized
*/
public static String getClassNameFromByteCode(byte[] byteCode) {
try (InputStream is = new ByteArrayInputStream(byteCode);
DataInputStream dis = new DataInputStream(is)) {
dis.readInt(); // magic number
dis.readShort(); // minor version
dis.readShort(); // major version
int cpcnt = (dis.readShort() & 0xffff) - 1;
int[] classes = new int[cpcnt];
String[] strings = new String[cpcnt];
int t;
for (int i = 0; i < cpcnt; i++) {
t = dis.read(); // tag - 1 byte
switch (t) {
case CONSTANT_UTF8:
strings[i] = dis.readUTF();
break;
case CONSTANT_INTEGER:
case CONSTANT_FLOAT:
dis.readInt(); // bytes
break;
case CONSTANT_LONG:
case CONSTANT_DOUBLE:
dis.readInt(); // high_bytes
dis.readInt(); // low_bytes
i++; // All 8-byte constants take up two entries
break;
case CONSTANT_CLASS:
classes[i] = dis.readShort() & 0xffff;
break;
case CONSTANT_STRING:
dis.readShort(); // string_index
break;
case CONSTANT_FIELDREF:
case CONSTANT_METHODREF:
case CONSTANT_INTERFACEMETHODREF:
dis.readShort(); // class_index
dis.readShort(); // name_and_type_index
break;
case CONSTANT_NAMEANDTYPE:
dis.readShort(); // name_index
dis.readShort(); // descriptor_index
break;
case CONSTANT_METHODHANDLE:
dis.readByte(); // reference_kind
dis.readShort(); // reference_index
break;
case CONSTANT_METHODTYPE:
dis.readShort(); // descriptor_index
break;
case CONSTANT_DYNAMIC:
case CONSTANT_INVOKEDYNAMIC:
dis.readShort(); // bootstrap_method_attr_index
dis.readShort(); // name_and_type_index
break;
case CONSTANT_MODULE:
case CONSTANT_PACKAGE:
dis.readShort(); // name_index
break;
default:
throw new IllegalStateException("Unrecognized constant pool tag: " + t);
}
}
dis.readShort(); // access flags
int thisClassIndex = dis.readShort() & 0xffff; // this_class
int stringIndex = classes[thisClassIndex - 1];
String className = strings[stringIndex - 1];
return className.replace('/', '.');
} catch (IOException e) {
ExceptionUtilities.uncheckedThrow(e);
return null; // unreachable
}
}
/**
* Returns true if the JavaCompiler (JDK) is available at runtime, false if running under a JRE.
*/
public static boolean isJavaCompilerAvailable() {
// Allow tests to simulate running on a JRE by setting a system property.
if (Boolean.getBoolean("java.util.force.jre")) {
return false;
}
try {
Class> toolProvider = Class.forName("javax.tools.ToolProvider");
Object compiler = toolProvider.getMethod("getSystemJavaCompiler").invoke(null);
return compiler != null;
} catch (Throwable t) {
return false;
}
}
/**
* Return a String representation of the class loader, or "bootstrap" if null.
*
* @param c The class whose class loader is to be identified.
* @return A String representing the class loader.
*/
private static String getClassLoaderName(Class> c) {
ClassLoader loader = c.getClassLoader();
if (loader == null) {
return "bootstrap";
}
// Example: "org.example.MyLoader@1a2b3c4"
return loader.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(loader));
}
}