ReflectionUtils.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.annotation.UsedByGeneratedCode;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.exception.InvocationException;
import io.micronaut.core.util.StringUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Utility methods for reflection related tasks. Micronaut tries to avoid using reflection wherever possible,
* this class is therefore considered an internal class and covers edge cases needed by Micronaut, often at compile time.
* <p>
* Do not use in application code.
*
* @author Graeme Rocher
* @since 1.0
*/
@Internal
public class ReflectionUtils {
/**
* Constant for empty class array.
*/
@UsedByGeneratedCode
public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS;
static {
LinkedHashMap<Class<?>, Class<?>> m = new LinkedHashMap<>();
m.put(boolean.class, Boolean.class);
m.put(byte.class, Byte.class);
m.put(char.class, Character.class);
m.put(double.class, Double.class);
m.put(float.class, Float.class);
m.put(int.class, Integer.class);
m.put(long.class, Long.class);
m.put(short.class, Short.class);
m.put(void.class, Void.class);
PRIMITIVES_TO_WRAPPERS = Collections.unmodifiableMap(m);
}
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE;
static {
LinkedHashMap<Class<?>, Class<?>> m = new LinkedHashMap<>();
m.put(Boolean.class, boolean.class);
m.put(Byte.class, byte.class);
m.put(Character.class, char.class);
m.put(Double.class, double.class);
m.put(Float.class, float.class);
m.put(Integer.class, int.class);
m.put(Long.class, long.class);
m.put(Short.class, short.class);
m.put(Void.class, void.class);
WRAPPER_TO_PRIMITIVE = Collections.unmodifiableMap(m);
}
/**
* Is the method a setter.
*
* @param name The method name
* @param args The arguments
* @return True if it is
*/
public static boolean isSetter(String name, Class<?>[] args) {
if (StringUtils.isEmpty(name) || args == null) {
return false;
}
if (args.length != 1) {
return false;
}
return NameUtils.isSetterName(name);
}
/**
* Obtain the wrapper type for the given primitive.
*
* @param primitiveType The primitive type
* @return The wrapper type
*/
public static Class<?> getWrapperType(Class<?> primitiveType) {
if (primitiveType.isPrimitive()) {
return PRIMITIVES_TO_WRAPPERS.get(primitiveType);
}
return primitiveType;
}
/**
* Obtain the primitive type for the given wrapper type.
*
* @param wrapperType The primitive type
* @return The wrapper type
*/
public static Class<?> getPrimitiveType(Class<?> wrapperType) {
Class<?> wrapper = WRAPPER_TO_PRIMITIVE.get(wrapperType);
if (wrapper != null) {
return wrapper;
}
return wrapperType;
}
/**
* Obtains a declared method.
*
* @param type The type
* @param methodName The method name
* @param argTypes The argument types
* @return The method
*/
public static Optional<Method> getDeclaredMethod(Class<?> type, String methodName, Class<?>... argTypes) {
try {
return Optional.of(type.getDeclaredMethod(methodName, argTypes));
} catch (NoSuchMethodException e) {
return Optional.empty();
}
}
/**
* Obtains a method.
*
* @param type The type
* @param methodName The method name
* @param argTypes The argument types
* @return An optional {@link Method}
*/
public static Optional<Method> getMethod(Class<?> type, String methodName, Class<?>... argTypes) {
try {
return Optional.of(type.getMethod(methodName, argTypes));
} catch (NoSuchMethodException e) {
return findMethod(type, methodName, argTypes);
}
}
/**
* Obtains a declared method.
*
* @param type The type
* @param argTypes The argument types
* @param <T> The generic type
* @return The method
*/
public static <T> Optional<Constructor<T>> findConstructor(Class<T> type, Class<?>... argTypes) {
try {
return Optional.of(type.getDeclaredConstructor(argTypes));
} catch (NoSuchMethodException e) {
return Optional.empty();
}
}
/**
* Invokes a method.
*
* @param instance The instance
* @param method The method
* @param arguments The arguments
* @param <R> The return type
* @param <T> The instance type
* @return The result
*/
public static <R, T> R invokeMethod(T instance, Method method, Object... arguments) {
try {
return (R) method.invoke(instance, arguments);
} catch (IllegalAccessException e) {
throw new InvocationException("Illegal access invoking method [" + method + "]: " + e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new InvocationException("Exception occurred invoking method [" + method + "]: " + e.getMessage(), e);
}
}
/**
* Invokes an inaccessible method.
*
* @param instance The instance
* @param method The method
* @param arguments The arguments
* @param <R> The return type
* @param <T> The instance type
* @return The result
* @since 4.8
*/
@UsedByGeneratedCode
public static <R, T> R invokeInaccessibleMethod(T instance, Method method, Object... arguments) {
try {
method.setAccessible(true);
return (R) method.invoke(instance, arguments);
} catch (IllegalAccessException e) {
throw new InvocationException("Illegal access invoking method [" + method + "]: " + e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new InvocationException("Exception occurred invoking method [" + method + "]: " + e.getMessage(), e);
}
}
/**
* Finds a method on the given type for the given name.
*
* @param type The type
* @param name The name
* @param argumentTypes The argument types
* @return An {@link Optional} contains the method or empty
*/
@Internal
public static Optional<Method> findMethod(Class<?> type, String name, Class<?>... argumentTypes) {
Class<?> currentType = type;
while (currentType != null) {
Method[] methods = currentType.isInterface() ? currentType.getMethods() : currentType.getDeclaredMethods();
for (Method method : methods) {
if (name.equals(method.getName()) && Arrays.equals(argumentTypes, method.getParameterTypes())) {
return Optional.of(method);
}
}
currentType = currentType.getSuperclass();
}
return Optional.empty();
}
/**
* Finds a method on the given type for the given name.
*
* @param type The type
* @param name The name
* @param argumentTypes The argument types
* @return An {@link Optional} contains the method or empty
*/
@UsedByGeneratedCode
@Internal
public static Method getRequiredMethod(Class<?> type, String name, Class<?>... argumentTypes) {
try {
return type.getDeclaredMethod(name, argumentTypes);
} catch (NoSuchMethodException e) {
return findMethod(type, name, argumentTypes)
.orElseThrow(() -> newNoSuchMethodError(type, name, argumentTypes));
}
}
/**
* Finds an internal method defined by the Micronaut API and throws a {@link NoSuchMethodError} if it doesn't exist.
*
* @param type The type
* @param name The name
* @param argumentTypes The argument types
* @return An {@link Optional} contains the method or empty
* @throws NoSuchMethodError If the method doesn't exist
*/
@Internal
public static Method getRequiredInternalMethod(Class<?> type, String name, Class<?>... argumentTypes) {
try {
return type.getDeclaredMethod(name, argumentTypes);
} catch (NoSuchMethodException e) {
return findMethod(type, name, argumentTypes)
.orElseThrow(() -> newNoSuchMethodInternalError(type, name, argumentTypes));
}
}
/**
* Finds an internal constructor defined by the Micronaut API and throws a {@link NoSuchMethodError} if it doesn't exist.
*
* @param type The type
* @param argumentTypes The argument types
* @param <T> The type
* @return An {@link Optional} contains the method or empty
* @throws NoSuchMethodError If the method doesn't exist
*/
@Internal
public static <T> Constructor<T> getRequiredInternalConstructor(Class<T> type, Class<?>... argumentTypes) {
try {
return type.getDeclaredConstructor(argumentTypes);
} catch (NoSuchMethodException e) {
throw newNoSuchConstructorInternalError(type, argumentTypes);
}
}
/**
* Finds a field on the given type for the given name.
*
* @param type The type
* @param name The name
* @return An {@link Optional} contains the method or empty
*/
@Internal
public static Field getRequiredField(Class<?> type, String name) {
try {
return type.getDeclaredField(name);
} catch (NoSuchFieldException e) {
Optional<Field> field = findField(type, name);
return field.orElseThrow(() -> new NoSuchFieldError("No field '" + name + "' found for type: " + type.getName()));
}
}
/**
* Finds field's value or return an empty if exception occurs or if the value is null.
*
* @param fieldOwnerClass The field owner class
* @param fieldName The field name
* @param instance The instance
* @return An {@link Optional} contains the value or empty of the value is null or an error occurred
* @since 4.0.0
*/
@Internal
public static Optional<Object> getFieldValue(@NonNull Class<?> fieldOwnerClass, @NonNull String fieldName, @NonNull Object instance) {
try {
final Field f = getRequiredField(fieldOwnerClass, fieldName);
f.setAccessible(true);
return Optional.ofNullable(f.get(instance));
} catch (Throwable t) {
return Optional.empty();
}
}
/**
* Finds a field in the type or super type.
*
* @param type The type
* @param name The field name
* @return An {@link Optional} of field
*/
@Internal
public static Optional<Field> findField(Class<?> type, String name) {
Optional<Field> declaredField = findDeclaredField(type, name);
if (declaredField.isEmpty()) {
while ((type = type.getSuperclass()) != null) {
declaredField = findField(type, name);
if (declaredField.isPresent()) {
break;
}
}
}
return declaredField;
}
/**
* Finds a method on the given type for the given name.
*
* @param type The type
* @param name The name
* @return An {@link Optional} contains the method or empty
*/
public static Stream<Method> findMethodsByName(Class<?> type, String name) {
Class<?> currentType = type;
Set<Method> methodSet = new HashSet<>();
while (currentType != null) {
Method[] methods = currentType.isInterface() ? currentType.getMethods() : currentType.getDeclaredMethods();
for (Method method : methods) {
if (name.equals(method.getName())) {
methodSet.add(method);
}
}
currentType = currentType.getSuperclass();
}
return methodSet.stream();
}
/**
* @param type The type
* @param name The field name
* @return An optional with the declared field
*/
public static Optional<Field> findDeclaredField(Class<?> type, String name) {
try {
Field declaredField = type.getDeclaredField(name);
return Optional.of(declaredField);
} catch (NoSuchFieldException e) {
return Optional.empty();
}
}
/**
* @param aClass A class
* @return All the interfaces
*/
public static Set<Class<?>> getAllInterfaces(Class<?> aClass) {
Set<Class<?>> interfaces = new HashSet<>();
return populateInterfaces(aClass, interfaces);
}
/**
* @param aClass A class
* @param interfaces The interfaces
* @return A set with the interfaces
*/
@SuppressWarnings("Duplicates")
protected static Set<Class<?>> populateInterfaces(Class<?> aClass, Set<Class<?>> interfaces) {
Class<?>[] theInterfaces = aClass.getInterfaces();
interfaces.addAll(Arrays.asList(theInterfaces));
for (Class<?> theInterface : theInterfaces) {
populateInterfaces(theInterface, interfaces);
}
if (!aClass.isInterface()) {
Class<?> superclass = aClass.getSuperclass();
while (superclass != null) {
populateInterfaces(superclass, interfaces);
superclass = superclass.getSuperclass();
}
}
return interfaces;
}
/**
* @param declaringType The declaring type
* @param name The method name
* @param argumentTypes The argument types
* @return A {@link NoSuchMethodError}
*/
public static NoSuchMethodError newNoSuchMethodError(Class<?> declaringType, String name, Class<?>[] argumentTypes) {
Stream<String> stringStream = Arrays.stream(argumentTypes).map(Class::getSimpleName);
String argsAsText = stringStream.collect(Collectors.joining(","));
return new NoSuchMethodError("Required method " + name + "(" + argsAsText + ") not found for class: " + declaringType.getName() + ". Most likely cause of this error is the method declaration is not annotated with @Executable. Alternatively check that there is not an unsupported or older version of a dependency present on the classpath. Check your classpath, and ensure the incompatible classes are not present and/or recompile classes as necessary.");
}
private static NoSuchMethodError newNoSuchMethodInternalError(Class<?> declaringType, String name, Class<?>[] argumentTypes) {
Stream<String> stringStream = Arrays.stream(argumentTypes).map(Class::getSimpleName);
String argsAsText = stringStream.collect(Collectors.joining(","));
return new NoSuchMethodError("Micronaut method " + declaringType.getName() + "." + name + "(" + argsAsText + ") not found. Most likely reason for this issue is that you are running a newer version of Micronaut with code compiled against an older version. Please recompile the offending classes");
}
private static NoSuchMethodError newNoSuchConstructorInternalError(Class<?> declaringType, Class<?>[] argumentTypes) {
Stream<String> stringStream = Arrays.stream(argumentTypes).map(Class::getSimpleName);
String argsAsText = stringStream.collect(Collectors.joining(","));
return new NoSuchMethodError("Micronaut constructor " + declaringType.getName() + "(" + argsAsText + ") not found. Most likely reason for this issue is that you are running a newer version of Micronaut with code compiled against an older version. Please recompile the offending classes");
}
/**
* Sets the value of the given field reflectively.
* @param field The field
* @param instance The instance
* @param value The value
*/
public static void setField(
@NonNull Field field,
@NonNull Object instance,
@Nullable Object value) {
try {
ClassUtils.REFLECTION_LOGGER.debug("Reflectively setting field {} to value {} on object {}", field, value, value);
field.setAccessible(true);
field.set(instance, value);
} catch (Throwable e) {
throw new InvocationException("Exception occurred setting field [" + field + "]: " + e.getMessage(), e);
}
}
/**
* Gets the value of the given field reflectively.
*
* @param clazz The class
* @param fieldName The fieldName
* @param instance The instance
*
* @return field value of instance
*
* @since 3.7.0
*/
@UsedByGeneratedCode
public static Object getField(@NonNull Class<?> clazz, @NonNull String fieldName, @NonNull Object instance) {
try {
ClassUtils.REFLECTION_LOGGER.debug("Reflectively getting field {} of class {} and instance {}", fieldName, clazz, instance);
Field field = getRequiredField(clazz, fieldName);
field.setAccessible(true);
return field.get(instance);
} catch (Throwable e) {
throw new InvocationException("Exception occurred getting a field [" + fieldName + "] of class [" + clazz + "]: " + e.getMessage(), e);
}
}
/**
* Sets the value of the given field reflectively.
* @param clazz The class
* @param fieldName The fieldName
* @param instance The instance
* @param value The value
* @since 4.0.0
*/
public static void setField(@NonNull Class<?> clazz,
@NonNull String fieldName,
@NonNull Object instance,
@Nullable Object value) {
try {
Field field = findField(clazz, fieldName)
.orElseThrow(() -> new IllegalStateException("Field with name: " + fieldName + " not found in class: " + clazz));
ClassUtils.REFLECTION_LOGGER.debug("Reflectively setting field {} to value {} on object {}", field, value, value);
field.setAccessible(true);
field.set(instance, value);
} catch (Throwable e) {
throw new InvocationException("Exception occurred setting field [" + fieldName + "]: " + e.getMessage(), e);
}
}
}