Reflections.java

package io.github.giulong.spectrum.utils;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.Arrays.asList;

@Slf4j
@UtilityClass
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public final class Reflections {

    public static ParameterizedType getGenericSuperclassOf(Class<?> clazz, final Class<?> limit) {
        log.trace("Getting generic superclass of {} up to {}", clazz.getTypeName(), limit.getTypeName());

        while (clazz.getSuperclass() != limit) {
            clazz = clazz.getSuperclass();
            log.trace("Checking {}", clazz.getTypeName());
        }

        return (ParameterizedType) clazz.getGenericSuperclass();
    }

    public static List<Field> getFieldsOf(Class<?> clazz, final Class<?> limit) {
        log.trace("Getting fields of {}", clazz.getSimpleName());

        final List<Field> fields = new ArrayList<>(asList(clazz.getDeclaredFields()));

        while (clazz.getSuperclass() != limit) {
            clazz = clazz.getSuperclass();
            log.trace("Getting also fields of superclass {}", clazz.getSimpleName());
            fields.addAll(asList(clazz.getDeclaredFields()));
        }

        return fields;
    }

    @SneakyThrows
    public static Field getField(final String fieldName, final Object object) {
        log.trace("Getting field {}.{}", object.getClass().getSimpleName(), fieldName);

        Class<?> clazz = object.getClass();
        while (clazz != Object.class && Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).noneMatch(n -> n.equals(fieldName))) {
            clazz = clazz.getSuperclass();
            log.trace("Field {} not found. Looking into superclass {}", fieldName, clazz.getSimpleName());
        }

        final Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);

        return field;
    }

    @SneakyThrows
    public static Object getFieldValue(final String fieldName, final Object object) {
        log.trace("Getting value of field {}.{}", object.getClass().getSimpleName(), fieldName);
        return getField(fieldName, object).get(object);
    }

    public static <T> T getFieldValue(final String fieldName, final Object object, final Class<T> clazz) {
        return clazz.cast(getFieldValue(fieldName, object));
    }

    public static void setField(final String fieldName, final Object object, final Object value) {
        final Field field = getField(fieldName, object);
        setField(field, object, value);
    }

    @SneakyThrows
    public static void setField(final Field field, final Object object, final Object value) {
        log.trace("Setting field {}.{} to {}", object.getClass().getSimpleName(), field.getName(), value);
        field.setAccessible(true);
        field.set(object, value);
    }

    @SneakyThrows
    public static void copyField(final Field field, final Object source, final Object dest) {
        log.trace("Copying field {} from {} to {}", field.getName(), source.getClass().getSimpleName(), dest.getClass().getSimpleName());
        field.setAccessible(true);
        field.set(dest, field.get(source));
    }

    public static List<Field> getAnnotatedFields(final Class<?> clazz, final Class<? extends Annotation> annotation) {
        final String className = clazz.getTypeName();
        final String annotationName = annotation.getTypeName();

        return Arrays
                .stream(clazz.getDeclaredFields())
                .filter(f -> f.isAnnotationPresent(annotation))
                .peek(f -> log.debug("Field {}.{} is annotated with {}", className, f.getName(), annotationName))
                .peek(f -> f.setAccessible(true))
                .toList();
    }

    public static List<Field> getAnnotatedFields(final Object object, final Class<? extends Annotation> annotation) {
        return getAnnotatedFields(object.getClass(), annotation);
    }

    public static <T> List<T> getAnnotatedFieldsValues(final Object object, final Class<? extends Annotation> annotation, final Class<T> clazz) {
        return getAnnotatedFields(object, annotation)
                .stream()
                .map(f -> getValueOf(f, object))
                .map(clazz::cast)
                .toList();
    }

    @SneakyThrows
    static Object getValueOf(final Field field, final Object object) {
        return field.get(object);
    }
}