org.infinispan.cdi.common.util.Reflections Maven / Gradle / Ivy
package org.infinispan.cdi.common.util;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;
import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.BeanManager;
/**
* Utility class for working with JDK Reflection and also CDI's
* {@link Annotated} metadata.
*
* @author Stuart Douglas
* @author Pete Muir
*/
public class Reflections {
/**
* An empty array of type {@link Annotation}, useful converting lists to
* arrays.
*/
public static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
/**
* An empty array of type {@link Object}, useful for converting lists to
* arrays.
*/
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
public static final Type[] EMPTY_TYPES = {};
public static final Class>[] EMPTY_CLASSES = new Class>[0];
/**
*
* Perform a runtime cast. Similar to {@link Class#cast(Object)}, but useful
* when you do not have a {@link Class} object for type you wish to cast to.
*
*
*
* {@link Class#cast(Object)} should be used if possible
*
*
* @param the type to cast to
* @param obj the object to perform the cast on
* @return the casted object
* @throws ClassCastException if the type T is not a subtype of the object
* @see Class#cast(Object)
*/
@SuppressWarnings("unchecked")
public static T cast(Object obj) {
return (T) obj;
}
/**
* Get all the declared methods on the class hierarchy. This will
* return overridden methods.
*
* @param clazz The class to search
* @return the set of all declared methods or an empty set if there are none
*/
public static Set getAllDeclaredMethods(Class> clazz) {
HashSet methods = new HashSet();
for (Class> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
for (Method a : c.getDeclaredMethods()) {
methods.add(a);
}
}
return methods;
}
private static String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args) {
StringBuilder message = new StringBuilder(String.format("Exception invoking method [%s] on object [%s], using arguments [", method.getName(), obj));
if (args != null)
for (int i = 0; i < args.length; i++)
message.append((i > 0 ? "," : "") + args[i]);
message.append("]");
return message.toString();
}
/**
* Set the accessibility flag on the {@link AccessibleObject} as described in
* {@link AccessibleObject#setAccessible(boolean)} within the context of
* a {@link PrivilegedAction}.
*
* @param member the accessible object type
* @param member the accessible object
* @return the accessible object after the accessible flag has been altered
*/
public static A setAccessible(A member) {
AccessController.doPrivileged(new SetAccessiblePrivilegedAction(member));
return member;
}
/**
*
* Invoke the specified method on the provided instance, passing any additional
* arguments included in this method as arguments to the specified method.
*
*
*
* This method attempts to set the accessible flag of the method in a
* {@link PrivilegedAction} before invoking the method if the first argument
* is true.
*
*
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the
* expected return type set to {@link Object}.
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static Object invokeMethod(boolean setAccessible, Method method, Object instance, Object... args) {
return invokeMethod(setAccessible, method, Object.class, instance, args);
}
/**
*
* Invoke the specified method on the provided instance, passing any additional
* arguments included in this method as arguments to the specified method.
*
*
* This method provides the same functionality and throws the same exceptions as
* {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the
* expected return type set to {@link Object} and honoring the accessibility of
* the method.
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static T invokeMethod(Method method, Class expectedReturnType, Object instance, Object... args) {
return invokeMethod(false, method, expectedReturnType, instance, args);
}
/**
*
* Invoke the method on the instance, with any arguments specified, casting
* the result of invoking the method to the expected return type.
*
*
*
* This method wraps {@link Method#invoke(Object, Object...)}, converting the
* checked exceptions that {@link Method#invoke(Object, Object...)} specifies
* to runtime exceptions.
*
*
*
* If instructed, this method attempts to set the accessible flag of the method in a
* {@link PrivilegedAction} before invoking the method.
*
*
* @param setAccessible flag indicating whether method should first be set as
* accessible
* @param method the method to invoke
* @param instance the instance to invoke the method
* @param args the arguments to the method
* @return the result of invoking the method, or null if the method's return
* type is void
* @throws RuntimeException if this Method
object enforces Java
* language access control and the underlying method is
* inaccessible or if the underlying method throws an exception or
* if the initialization provoked by this method fails.
* @throws IllegalArgumentException if the method is an instance method and
* the specified instance
argument is not an instance
* of the class or interface declaring the underlying method (or
* of a subclass or implementor thereof); if the number of actual
* and formal parameters differ; if an unwrapping conversion for
* primitive arguments fails; or if, after possible unwrapping, a
* parameter value cannot be converted to the corresponding formal
* parameter type by a method invocation conversion.
* @throws NullPointerException if the specified instance
is
* null and the method is an instance method.
* @throws ClassCastException if the result of invoking the method cannot be
* cast to the expectedReturnType
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
* @see Method#invoke(Object, Object...)
*/
public static T invokeMethod(boolean setAccessible, Method method, Class expectedReturnType, Object instance, Object... args) {
if (setAccessible && !method.isAccessible()) {
setAccessible(method);
}
try {
return expectedReturnType.cast(method.invoke(instance, args));
} catch (IllegalAccessException ex) {
throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, instance, args), ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex.getCause());
} catch (NullPointerException ex) {
NullPointerException ex2 = new NullPointerException(buildInvokeMethodErrorMessage(method, instance, args));
ex2.initCause(ex.getCause());
throw ex2;
} catch (ExceptionInInitializerError e) {
ExceptionInInitializerError e2 = new ExceptionInInitializerError(buildInvokeMethodErrorMessage(method, instance, args));
e2.initCause(e.getCause());
throw e2;
}
}
private static String buildGetFieldValueErrorMessage(Field field, Object obj) {
return String.format("Exception reading [%s] field from object [%s].", field.getName(), obj);
}
/**
*
* Get the value of the field, on the specified instance, casting the value
* of the field to the expected type.
*
*
*
* This method wraps {@link Field#get(Object)}, converting the checked
* exceptions that {@link Field#get(Object)} specifies to runtime exceptions.
*
*
* @param the type of the field's value
* @param field the field to operate on
* @param instance the instance from which to retrieve the value
* @param expectedType the expected type of the field's value
* @return the value of the field
* @throws RuntimeException if the underlying field is inaccessible.
* @throws IllegalArgumentException if the specified instance
is not an
* instance of the class or interface declaring the underlying
* field (or a subclass or implementor thereof).
* @throws NullPointerException if the specified instance
is null and the field
* is an instance field.
* @throws ExceptionInInitializerError if the initialization provoked by this
* method fails.
*/
public static T getFieldValue(Field field, Object instance, Class expectedType) {
try {
return Reflections.cast(field.get(instance));
} catch (IllegalAccessException e) {
throw new RuntimeException(buildGetFieldValueErrorMessage(field, instance), e);
} catch (NullPointerException ex) {
NullPointerException ex2 = new NullPointerException(buildGetFieldValueErrorMessage(field, instance));
ex2.initCause(ex.getCause());
throw ex2;
} catch (ExceptionInInitializerError e) {
ExceptionInInitializerError e2 = new ExceptionInInitializerError(buildGetFieldValueErrorMessage(field, instance));
e2.initCause(e.getCause());
throw e2;
}
}
/**
* Extract the raw type, given a type.
*
* @param the type
* @param type the type to extract the raw type from
* @return the raw type, or null if the raw type cannot be determined.
*/
@SuppressWarnings("unchecked")
public static Class getRawType(Type type) {
if (type instanceof Class>) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
if (((ParameterizedType) type).getRawType() instanceof Class>) {
return (Class) ((ParameterizedType) type).getRawType();
}
}
return null;
}
/**
* Check if a class is serializable.
*
* @param clazz The class to check
* @return true if the class implements serializable or is a primitive
*/
public static boolean isSerializable(Class> clazz) {
return clazz.isPrimitive() || Serializable.class.isAssignableFrom(clazz);
}
/**
* Check the assignability of one type to another, taking into account the
* actual type arguements
*
* @param rawType1 the raw type of the class to check
* @param actualTypeArguments1 the actual type arguements to check, or an
* empty array if not a parameterized type
* @param rawType2 the raw type of the class to check
* @param actualTypeArguments2 the actual type arguements to check, or an
* empty array if not a parameterized type
* @return
*/
public static boolean isAssignableFrom(Class> rawType1, Type[] actualTypeArguments1, Class> rawType2, Type[] actualTypeArguments2) {
return Types.boxedClass(rawType1).isAssignableFrom(Types.boxedClass(rawType2)) && isAssignableFrom(actualTypeArguments1, actualTypeArguments2);
}
public static boolean matches(Class> rawType1, Type[] actualTypeArguments1, Class> rawType2, Type[] actualTypeArguments2) {
return Types.boxedClass(rawType1).equals(Types.boxedClass(rawType2)) && isAssignableFrom(actualTypeArguments1, actualTypeArguments2);
}
public static boolean isAssignableFrom(Type[] actualTypeArguments1, Type[] actualTypeArguments2) {
for (int i = 0; i < actualTypeArguments1.length; i++) {
Type type1 = actualTypeArguments1[i];
Type type2 = Object.class;
if (actualTypeArguments2.length > i) {
type2 = actualTypeArguments2[i];
}
if (!isAssignableFrom(type1, type2)) {
return false;
}
}
return true;
}
public static boolean matches(Type type1, Set extends Type> types2) {
for (Type type2 : types2) {
if (matches(type1, type2)) {
return true;
}
}
return false;
}
public static boolean isAssignableFrom(Type type1, Type[] types2) {
for (Type type2 : types2) {
if (isAssignableFrom(type1, type2)) {
return true;
}
}
return false;
}
public static boolean isAssignableFrom(Type type1, Type type2) {
if (type1 instanceof Class>) {
Class> clazz = (Class>) type1;
if (isAssignableFrom(clazz, EMPTY_TYPES, type2)) {
return true;
}
}
if (type1 instanceof ParameterizedType) {
ParameterizedType parameterizedType1 = (ParameterizedType) type1;
if (parameterizedType1.getRawType() instanceof Class>) {
if (isAssignableFrom((Class>) parameterizedType1.getRawType(), parameterizedType1.getActualTypeArguments(), type2)) {
return true;
}
}
}
if (type1 instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type1;
if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) {
return true;
}
}
if (type2 instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type2;
if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) {
return true;
}
}
if (type1 instanceof TypeVariable>) {
TypeVariable> typeVariable = (TypeVariable>) type1;
if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) {
return true;
}
}
if (type2 instanceof TypeVariable>) {
TypeVariable> typeVariable = (TypeVariable>) type2;
if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) {
return true;
}
}
return false;
}
public static boolean matches(Type type1, Type type2) {
if (type1 instanceof Class>) {
Class> clazz = (Class>) type1;
if (matches(clazz, EMPTY_TYPES, type2)) {
return true;
}
}
if (type1 instanceof ParameterizedType) {
ParameterizedType parameterizedType1 = (ParameterizedType) type1;
if (parameterizedType1.getRawType() instanceof Class>) {
if (matches((Class>) parameterizedType1.getRawType(), parameterizedType1.getActualTypeArguments(), type2)) {
return true;
}
}
}
if (type1 instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type1;
if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) {
return true;
}
}
if (type2 instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type2;
if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) {
return true;
}
}
if (type1 instanceof TypeVariable>) {
TypeVariable> typeVariable = (TypeVariable>) type1;
if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) {
return true;
}
}
if (type2 instanceof TypeVariable>) {
TypeVariable> typeVariable = (TypeVariable>) type2;
if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) {
return true;
}
}
return false;
}
public static boolean isTypeBounded(Type type, Type[] lowerBounds, Type[] upperBounds) {
if (lowerBounds.length > 0) {
if (!isAssignableFrom(type, lowerBounds)) {
return false;
}
}
if (upperBounds.length > 0) {
if (!isAssignableFrom(upperBounds, type)) {
return false;
}
}
return true;
}
public static boolean isAssignableFrom(Class> rawType1, Type[] actualTypeArguments1, Type type2) {
if (type2 instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type2;
if (parameterizedType.getRawType() instanceof Class>) {
if (isAssignableFrom(rawType1, actualTypeArguments1, (Class>) parameterizedType.getRawType(), parameterizedType.getActualTypeArguments())) {
return true;
}
}
} else if (type2 instanceof Class>) {
Class> clazz = (Class>) type2;
if (isAssignableFrom(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) {
return true;
}
} else if (type2 instanceof TypeVariable>) {
TypeVariable> typeVariable = (TypeVariable>) type2;
if (isTypeBounded(rawType1, actualTypeArguments1, typeVariable.getBounds())) {
return true;
}
}
return false;
}
public static boolean matches(Class> rawType1, Type[] actualTypeArguments1, Type type2) {
if (type2 instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type2;
if (parameterizedType.getRawType() instanceof Class>) {
if (matches(rawType1, actualTypeArguments1, (Class>) parameterizedType.getRawType(), parameterizedType.getActualTypeArguments())) {
return true;
}
}
} else if (type2 instanceof Class>) {
Class> clazz = (Class>) type2;
if (matches(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) {
return true;
}
}
return false;
}
/**
* Check whether whether any of the types1 matches a type in types2
*
* @param types1
* @param types2
* @return
*/
public static boolean matches(Set types1, Set types2) {
for (Type type : types1) {
if (matches(type, types2)) {
return true;
}
}
return false;
}
public static boolean isAssignableFrom(Type[] types1, Type type2) {
for (Type type : types1) {
if (isAssignableFrom(type, type2)) {
return true;
}
}
return false;
}
private Reflections() {
}
/**
* Inspects an annotated element for the given meta annotation. This should
* only be used for user defined meta annotations, where the annotation must
* be physically present.
*
* @param element The element to inspect
* @param annotationType The meta annotation to search for
* @return The annotation instance found on this element or null if no
* matching annotation was found.
*/
public static A getMetaAnnotation(Annotated element, final Class annotationType) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().isAnnotationPresent(annotationType)) {
return annotation.annotationType().getAnnotation(annotationType);
}
}
return null;
}
/**
* Extract the qualifiers from a set of annotations.
*
* @param beanManager the beanManager to use to determine if an annotation is
* a qualifier
* @param annotations the annotations to check
* @return any qualifiers present in annotations
*/
@SuppressWarnings("unchecked")
public static Set getQualifiers(BeanManager beanManager, Iterable annotations) {
return getQualifiers(beanManager, new Iterable[]{annotations});
}
/**
* Extract the qualifiers from a set of annotations.
*
* @param beanManager the beanManager to use to determine if an annotation is
* a qualifier
* @param annotations the annotations to check
* @return any qualifiers present in annotations
*/
public static Set getQualifiers(BeanManager beanManager, Iterable... annotations) {
Set qualifiers = new HashSet();
for (Iterable annotationSet : annotations) {
for (Annotation annotation : annotationSet) {
if (beanManager.isQualifier(annotation.annotationType())) {
qualifiers.add(annotation);
}
}
}
return qualifiers;
}
/**
* Get all the declared fields on the class hierarchy. This will
* return overridden fields.
*
* @param clazz The class to search
* @return the set of all declared fields or an empty set if there are none
*/
public static Set getAllDeclaredFields(Class> clazz) {
HashSet fields = new HashSet();
for (Class> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) {
for (Field a : c.getDeclaredFields()) {
fields.add(a);
}
}
return fields;
}
}