ClassUtils.java
/*
* Copyright 2017-2020 original authors
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package io.micronaut.core.reflect;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.optim.StaticOptimizations;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.NOPLogger;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
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.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
/**
* Utility methods for loading classes.
*
* @author Graeme Rocher
* @since 1.0
*/
public class ClassUtils {
/**
* System property to indicate whether classloader logging should be activated. This is required
* because this class is used both at compilation time and runtime, and we don't want logging at compilation time.
*/
public static final String PROPERTY_MICRONAUT_CLASSLOADER_LOGGING = "micronaut.classloader.logging";
public static final Map<String, Class<?>> COMMON_CLASS_MAP = new HashMap<>(34);
public static final Map<String, Class<?>> BASIC_TYPE_MAP = new HashMap<>(18);
/**
* Default extension for class files.
*/
public static final String CLASS_EXTENSION = ".class";
/**
* A logger that should be used for any reflection access.
*/
public static final Logger REFLECTION_LOGGER;
private static final boolean ENABLE_CLASS_LOADER_LOGGING = Boolean.getBoolean(PROPERTY_MICRONAUT_CLASSLOADER_LOGGING);
private static final Set<String> MISSING_TYPES = StaticOptimizations.get(Optimizations.class)
.map(Optimizations::getMissingTypes)
.orElse(Collections.emptySet());
static {
REFLECTION_LOGGER = getLogger(ClassUtils.class);
}
@SuppressWarnings("unchecked")
private static final Map<String, Class<?>> PRIMITIVE_TYPE_MAP = CollectionUtils.mapOf(
"int", Integer.TYPE,
"boolean", Boolean.TYPE,
"long", Long.TYPE,
"byte", Byte.TYPE,
"double", Double.TYPE,
"float", Float.TYPE,
"char", Character.TYPE,
"short", Short.TYPE,
"void", void.class
);
@SuppressWarnings("unchecked")
private static final Map<String, Class<?>> PRIMITIVE_ARRAY_MAP = CollectionUtils.mapOf(
"int", int[].class,
"boolean", boolean[].class,
"long", long[].class,
"byte", byte[].class,
"double", double[].class,
"float", float[].class,
"char", char[].class,
"short", short[].class
);
static {
COMMON_CLASS_MAP.put(boolean.class.getName(), boolean.class);
COMMON_CLASS_MAP.put(byte.class.getName(), byte.class);
COMMON_CLASS_MAP.put(int.class.getName(), int.class);
COMMON_CLASS_MAP.put(long.class.getName(), long.class);
COMMON_CLASS_MAP.put(double.class.getName(), double.class);
COMMON_CLASS_MAP.put(float.class.getName(), float.class);
COMMON_CLASS_MAP.put(char.class.getName(), char.class);
COMMON_CLASS_MAP.put(short.class.getName(), short.class);
COMMON_CLASS_MAP.put(boolean[].class.getName(), boolean[].class);
COMMON_CLASS_MAP.put(byte[].class.getName(), byte[].class);
COMMON_CLASS_MAP.put(int[].class.getName(), int[].class);
COMMON_CLASS_MAP.put(long[].class.getName(), long[].class);
COMMON_CLASS_MAP.put(double[].class.getName(), double[].class);
COMMON_CLASS_MAP.put(float[].class.getName(), float[].class);
COMMON_CLASS_MAP.put(char[].class.getName(), char[].class);
COMMON_CLASS_MAP.put(short[].class.getName(), short[].class);
COMMON_CLASS_MAP.put(Boolean.class.getName(), Boolean.class);
COMMON_CLASS_MAP.put(Byte.class.getName(), Byte.class);
COMMON_CLASS_MAP.put(Integer.class.getName(), Integer.class);
COMMON_CLASS_MAP.put(Long.class.getName(), Long.class);
COMMON_CLASS_MAP.put(Short.class.getName(), Short.class);
COMMON_CLASS_MAP.put(Double.class.getName(), Double.class);
COMMON_CLASS_MAP.put(Float.class.getName(), Float.class);
COMMON_CLASS_MAP.put(Character.class.getName(), Character.class);
COMMON_CLASS_MAP.put(String.class.getName(), String.class);
COMMON_CLASS_MAP.put(CharSequence.class.getName(), CharSequence.class);
BASIC_TYPE_MAP.put(UUID.class.getName(), UUID.class);
BASIC_TYPE_MAP.put(BigDecimal.class.getName(), BigDecimal.class);
BASIC_TYPE_MAP.put(BigInteger.class.getName(), BigInteger.class);
BASIC_TYPE_MAP.put(URL.class.getName(), URL.class);
BASIC_TYPE_MAP.put(URI.class.getName(), URI.class);
BASIC_TYPE_MAP.put(TimeZone.class.getName(), TimeZone.class);
BASIC_TYPE_MAP.put(Charset.class.getName(), Charset.class);
BASIC_TYPE_MAP.put(Locale.class.getName(), Locale.class);
BASIC_TYPE_MAP.put(Duration.class.getName(), Duration.class);
BASIC_TYPE_MAP.put(Date.class.getName(), Date.class);
BASIC_TYPE_MAP.put(LocalDate.class.getName(), LocalDate.class);
BASIC_TYPE_MAP.put(Instant.class.getName(), Instant.class);
BASIC_TYPE_MAP.put(ZonedDateTime.class.getName(), ZonedDateTime.class);
BASIC_TYPE_MAP.put(LocalTime.class.getName(), LocalTime.class);
BASIC_TYPE_MAP.put(OffsetTime.class.getName(), OffsetTime.class);
BASIC_TYPE_MAP.put(OffsetDateTime.class.getName(), OffsetDateTime.class);
BASIC_TYPE_MAP.put(Period.class.getName(), Period.class);
BASIC_TYPE_MAP.put(YearMonth.class.getName(), YearMonth.class);
BASIC_TYPE_MAP.put(Year.class.getName(), Year.class);
BASIC_TYPE_MAP.put(MonthDay.class.getName(), MonthDay.class);
BASIC_TYPE_MAP.put(ZoneId.class.getName(), ZoneId.class);
BASIC_TYPE_MAP.put(ZoneOffset.class.getName(), ZoneOffset.class);
}
/**
* Special case {@code getLogger} method that should be used by classes that are used in the annotation processor.
*
* @param type The type
* @return The logger
*/
public static @NonNull Logger getLogger(@NonNull Class<?> type) {
if (ENABLE_CLASS_LOADER_LOGGING) {
return LoggerFactory.getLogger(type);
} else {
return NOPLogger.NOP_LOGGER;
}
}
/**
* Returns the array type for the given primitive type name.
*
* @param primitiveType The primitive type name
* @return The array type
*/
public static @NonNull Optional<Class<?>> arrayTypeForPrimitive(String primitiveType) {
if (primitiveType != null) {
return Optional.ofNullable(PRIMITIVE_ARRAY_MAP.get(primitiveType));
}
return Optional.empty();
}
/**
* <p>Converts a URI to a class file reference to the class name</p>.
*
* <p>ie. ClassUtils.pathToClassName("foo/bar/MyClass.class") == "foo.bar.MyClass"</p>
*
* @param path The path name
* @return The class name
*/
public static String pathToClassName(String path) {
path = path.replace('/', '.');
if (path.endsWith(CLASS_EXTENSION)) {
path = path.substring(0, path.length() - CLASS_EXTENSION.length());
}
return path;
}
/**
* Check whether the given class is present in the given classloader.
*
* @param name The name of the class
* @param classLoader The classloader. If null will fall back to attempt the thread context loader, otherwise the system loader
* @return True if it is
*/
public static boolean isPresent(String name, @Nullable ClassLoader classLoader) {
return forName(name, classLoader).isPresent();
}
/**
* Return whether the given class is a common type found in {@code java.lang} such as String or a primitive type.
*
* @param type The type
* @return True if it is
*/
public static boolean isJavaLangType(Class<?> type) {
String typeName = type.getName();
return isJavaLangType(typeName);
}
/**
* Return whether the given class is a common type found in {@code java.lang} such as String or a primitive type.
*
* @param typeName The type name
* @return True if it is
*/
public static boolean isJavaLangType(String typeName) {
return COMMON_CLASS_MAP.containsKey(typeName);
}
/**
* Expanded version of {@link #isJavaLangType(Class)} that includes common Java types like {@link URI}.
*
* @param type The URI
* @return True if is a Java basic type
*/
public static boolean isJavaBasicType(@Nullable Class<?> type) {
if (type == null) {
return false;
}
final String name = type.getName();
return isJavaBasicType(name);
}
/**
* Expanded version of {@link #isJavaLangType(Class)} that includes common Java types like {@link URI}.
*
* @param name The name of the type
* @return True if is a Java basic type
*/
public static boolean isJavaBasicType(@Nullable String name) {
if (StringUtils.isEmpty(name)) {
return false;
}
return isJavaLangType(name) || BASIC_TYPE_MAP.containsKey(name);
}
/**
* The primitive type for the given type name. For example the value "byte" returns {@link Byte#TYPE}.
*
* @param primitiveType The type name
* @return An optional type
*/
public static Optional<Class<?>> getPrimitiveType(String primitiveType) {
return Optional.ofNullable(PRIMITIVE_TYPE_MAP.get(primitiveType));
}
/**
* Attempt to load a class for the given name from the given class loader. This method should be used
* as a last resort, and note that any usage of this method will create complications on GraalVM.
*
* @param name The name of the class
* @param classLoader The classloader. If null will fall back to attempt the thread context loader, otherwise the system loader
* @return An optional of the class
*/
public static Optional<Class<?>> forName(String name, @Nullable ClassLoader classLoader) {
try {
if (MISSING_TYPES.contains(name)) {
return Optional.empty();
}
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
}
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
Optional<Class<?>> commonType = Optional.ofNullable(COMMON_CLASS_MAP.get(name));
if (commonType.isPresent()) {
return commonType;
} else {
if (REFLECTION_LOGGER.isDebugEnabled()) {
REFLECTION_LOGGER.debug("Attempting to dynamically load class {}", name);
}
Class<?> type = Class.forName(name, true, classLoader);
if (REFLECTION_LOGGER.isDebugEnabled()) {
REFLECTION_LOGGER.debug("Successfully loaded class {}", name);
}
return Optional.of(type);
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
if (REFLECTION_LOGGER.isDebugEnabled()) {
REFLECTION_LOGGER.debug("Class {} is not present", name);
}
return Optional.empty();
}
}
/**
* Builds a class hierarchy that includes all super classes
* and interfaces that the given class implements or extends from.
*
* @param type The class to start with
* @return The class hierarchy
*/
public static List<Class<?>> resolveHierarchy(Class<?> type) {
Class<?> superclass = type.getSuperclass();
List<Class<?>> hierarchy = new ArrayList<>();
List<Class<?>> interfaces = new ArrayList<>();
if (superclass != null) {
hierarchy.add(type);
populateHierarchyInterfaces(type, interfaces);
while (superclass != Object.class) {
if (!hierarchy.contains(superclass)) {
hierarchy.add(superclass);
}
populateHierarchyInterfaces(superclass, interfaces);
superclass = superclass.getSuperclass();
}
hierarchy.addAll(interfaces);
} else if (type.isInterface()) {
hierarchy.add(type);
populateHierarchyInterfaces(type, hierarchy);
}
if (type.isArray()) {
if (!type.getComponentType().isPrimitive()) {
hierarchy.add(Object[].class);
}
} else {
hierarchy.add(Object.class);
}
return hierarchy;
}
private static void populateHierarchyInterfaces(Class<?> superclass, List<Class<?>> hierarchy) {
for (Class<?> aClass : superclass.getInterfaces()) {
if (!hierarchy.contains(aClass)) {
hierarchy.add(aClass);
}
populateHierarchyInterfaces(aClass, hierarchy);
}
}
/**
* Optimizations for computing missing types.
*/
@Internal
public static final class Optimizations {
private final Set<String> missingTypes;
public Optimizations(Set<String> missingTypes) {
this.missingTypes = missingTypes;
}
public Set<String> getMissingTypes() {
return missingTypes;
}
}
}