package rocks.inspectit.server.diagnosis.engine.util;
import static com.google.common.base.Preconditions.checkNotNull;
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.List;
import rocks.inspectit.server.diagnosis.engine.rule.exception.RuleDefinitionException;
/**
* Class contains utility methods to work with reflection API.
*
* @author Claudio Waldvogel, Alexander Wert
*/
public final class ReflectionUtils {
/**
* Private constructor.
*/
private ReflectionUtils() {
}
/**
* Represents a callback while searching for annotations on a type. The <code>Visitor</code> is
* used to lookup a certain annotation on a type, utilize it, and return something.
*
* @param <A>
* The annotation to by searched.
* @param <T>
* The type where containing the annotation.
* @param <R>
* The return type.
*/
public interface Visitor<A extends Annotation, T, R> {
/**
* Callback method to process a detected annotation.
*
* @param annotation
* The annotation type.
* @param type
* The type which owns the annotation.
* @return Any object.
*
* @throws RuleDefinitionException
* If a rule definition annotation is invalid.
*/
R visit(A annotation, T type) throws RuleDefinitionException;
}
/**
* Tries to instantiate a class. Intended to avoid boiler plate try/catch code.
*
* @param clazz
* The class to be instantiated.
* @param <T>
* The type of the class
* @return A new instance of the the <code>class</code>.
*/
public static <T> T tryInstantiate(Class<? extends T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Failed to instantiate clazz.", e);
}
}
/**
* Checks if a Class provides a public constructor without any arguments.
*
* @param clazz
* The class to be checked
* @return true if constructor is present, false otherwise.
*/
public static boolean hasNoArgsConstructor(Class<?> clazz) {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Modifier.isPublic(constructor.getModifiers()) && (constructor.getParameterTypes().length == 0)) {
return true;
}
}
return false;
}
/**
* Searches for the given annotation in the given class.
*
* @param clazz
* The class to be checked.
* @param annotationClass
* The annotation to be looked up.
* @param <T>
* The type of annotation
* @return The found annotation instance, <code>null</code> otherwise.
*/
@SuppressWarnings("unchecked")
public static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotationClass) {
checkNotNull(clazz, "Class must not be null!");
Annotation[] annotations = clazz.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(annotationClass)) {
return (T) annotation;
}
}
for (Class<?> interfaze : clazz.getInterfaces()) {
T annotation = findAnnotation(interfaze, annotationClass);
if (null != annotation) {
return annotation;
}
}
Class<?> superClazz = clazz.getSuperclass();
if ((null != superClazz) && !superClazz.equals(Object.class)) {
return findAnnotation(superClazz, annotationClass);
}
return null;
}
/**
* Invokes the given method on the passed target.
*
* @param method
* Method to invoke per reflection.
* @param target
* Object on which to invoke the method.
* @return The result object.
* @throws InvocationTargetException
* If invocation target is invalid.
* @throws IllegalArgumentException
* If arguments are invalid.
* @throws IllegalAccessException
* If method cannot be accessed.
*/
public static Object invokeMethod(Method method, Object target) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return invokeMethod(method, target, new Object[0]);
}
/**
* Invokes the given method on the passed target with gieven arguments..
*
* @param method
* Method to invoke per reflection.
* @param target
* Object on which to invoke the method.
* @param args
* the method arguments
* @return The result object.
* @throws InvocationTargetException
* If invocation target is invalid.
* @throws IllegalArgumentException
* If arguments are invalid.
* @throws IllegalAccessException
* If method cannot be accessed.
*/
public static Object invokeMethod(Method method, Object target, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return method.invoke(target, args);
}
/**
* Visits all fields of a class which are annotated with a certain annotation. The method
* utilizes a {@link Visitor} to create the results. The following example shows how all fields,
* annotated with SessionVariable, are processed and wrapped in SessionVariableInjection
* objects.
* <p>
*
* <pre>
* {
* @code
* List<SessionVariableInjection> injections = ReflectionUtils.visitFieldsAnnotatedWith(SessionVariable.class, clazz, new Visitor<SessionVariable, Field, SessionVariableInjection>() {
* @Override
* public SessionVariableInjection visit(SessionVariable annotation, Field field) {
* return new SessionVariableInjection(annotation.name(), annotation.optional(), field);
* }
* });
* }
* </pre>
*
* @param annotation
* The type of the annotation.
* @param clazz
* The class to be checked.
* @param visitor
* The {@link Visitor} to process the annotated field.
* @param <A>
* The Annotation type
* @param <R>
* The result type of the {@link Visitor}
* @return List with elements of type <code>R</code> produced by the {@link Visitor}.
* @throws RuleDefinitionException
* If a rule definition annotation is invalid.
*/
public static <A extends Annotation, R> List<R> visitFieldsAnnotatedWith(Class<A> annotation, Class<?> clazz, Visitor<A, Field, R> visitor) throws RuleDefinitionException {
List<R> results = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(annotation)) {
R result = visitor.visit(field.getAnnotation(annotation), field);
results.add(checkNotNull(result, "Visitor must not return null values!"));
}
}
if (!Object.class.equals(clazz.getSuperclass())) {
results.addAll(visitFieldsAnnotatedWith(annotation, clazz.getSuperclass(), visitor));
}
return results;
}
/**
* Visits all methods of a class which are annotated with a certain annotation. The method
* utilizes a {@link Visitor} to create the results. The following example shows how all
* methods, annotated with Action, are processed and wrapped in ActionMethod objects.
* <p>
*
* <pre>
* {
* @code
* List<ActionMethod> actionMethods = ReflectionUtils.visitMethodsAnnotatedWith(Action.class, clazz, new Visitor<Action, Method, ActionMethod>() {
* @Override
* public ActionMethod visit(Action annotation, Method method) {
* return new ActionMethod(method, annotation.resultTag(), annotation.resultQuantity());
* }
* });
* }
* </pre>
*
* @param annotation
* The type of the annotation.
* @param clazz
* The class to be checked.
* @param visitor
* The {@link Visitor} to process the annotated field.
* @param <A>
* The Annotation type
* @param <R>
* The result type of the {@link Visitor}
* @return List with elements of type <code>R</code> produced by the {@link Visitor}.
* @throws RuleDefinitionException
* If a rule definition annotation is invalid.
*/
public static <A extends Annotation, R> List<R> visitMethodsAnnotatedWith(Class<A> annotation, Class<?> clazz, Visitor<A, Method, R> visitor) throws RuleDefinitionException {
List<R> results = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
R result = visitor.visit(method.getAnnotation(annotation), method);
results.add(checkNotNull(result, "Visitor must not return null values!"));
}
}
if (!Object.class.equals(clazz.getSuperclass())) {
results.addAll(visitMethodsAnnotatedWith(annotation, clazz.getSuperclass(), visitor));
}
return results;
}
}