All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.sskorol.utils.ReflectionUtils Maven / Gradle / Ivy

package io.github.sskorol.utils;

import io.github.sskorol.core.DataSupplier;
import io.github.sskorol.data.FieldName;
import io.github.sskorol.data.Source;
import io.github.sskorol.model.TypeMappings;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.control.Try;
import one.util.streamex.StreamEx;
import org.reflections.Reflections;
import org.testng.ITestNGMethod;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.testng.internal.annotations.IDataProvidable;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.lang.ClassLoader.getSystemResource;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import static org.joor.Reflect.on;

/**
 * An utility class for internal DataSupplier management.
 */
@SuppressWarnings("FinalLocalVariable")
public final class ReflectionUtils {

    private ReflectionUtils() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }

    @SuppressWarnings("unchecked")
    public static Class getDataSupplierClass(final IDataProvidable annotation, final Class testClass,
                                                final Method testMethod) {
        return ofNullable(annotation.getDataProviderClass())
            .map(dataProviderClass -> (Class) dataProviderClass)
            .orElseGet(() -> findParentDataSupplierClass(testMethod, testClass));
    }

    public static Method getDataSupplierMethod(final Class targetClass, final String targetMethodName) {
        var methodMetaData = StreamEx.of(targetClass.getMethods())
            .map(method -> Tuple.of(method, method.getDeclaredAnnotation(DataSupplier.class)))
            .filter(hasDataSupplierMethod(targetMethodName))
            .map(metaData -> Tuple.of(metaData._1.getName(), metaData._1.getParameterTypes()))
            .findFirst()
            .orElseGet(() -> Tuple.of(targetMethodName, new Class[0]));

        return Try.of(() -> targetClass.getMethod(methodMetaData._1, methodMetaData._2)).get();
    }

    public static DataSupplier getDataSupplierAnnotation(final Class targetClass, final String targetMethodName) {
        return Try.of(() -> getDataSupplierMethod(targetClass, targetMethodName))
            .map(method -> method.getDeclaredAnnotation(DataSupplier.class))
            .filter(Objects::nonNull)
            .getOrElse((DataSupplier) null);
    }

    public static Object invokeDataSupplier(final Tuple2 methodMetaData) {
        return on(methodMetaData._1.getDeclaringClass())
            .create()
            .call(methodMetaData._1.getName(), methodMetaData._2)
            .get();
    }

    public static Method findDataSupplier(final ITestNGMethod testMethod) {
        var annotationMetaData = testMethod.isTest()
            ? getTestAnnotationMetaData(testMethod)
            : getFactoryAnnotationMetaData(testMethod);
        return getDataSupplierMethod(annotationMetaData._1, annotationMetaData._2);
    }

    @SuppressWarnings("unchecked")
    private static Tuple2, String> getTestAnnotationMetaData(final ITestNGMethod testMethod) {
        var declaringClass = testMethod.getConstructorOrMethod().getDeclaringClass();
        var parentClass = findParentDataSupplierClass(testMethod.getConstructorOrMethod().getMethod(), declaringClass);
        var testAnnotation = ofNullable(testMethod.getConstructorOrMethod()
            .getMethod()
            .getDeclaredAnnotation(Test.class))
            .orElseGet(() -> declaringClass.getDeclaredAnnotation(Test.class));
        var dataSupplierClass = ofNullable(testAnnotation)
            .map(Test::dataProviderClass)
            .filter(dp -> dp != Object.class)
            .orElse((Class) parentClass);

        return Tuple.of(dataSupplierClass, testAnnotation.dataProvider());
    }

    @SuppressWarnings("unchecked")
    public static Tuple2, String> getFactoryAnnotationMetaData(final ITestNGMethod testMethod) {
        var constructor = testMethod.getConstructorOrMethod().getConstructor();
        var method = testMethod.getConstructorOrMethod().getMethod();

        var factoryAnnotation = nonNull(method)
            ? ofNullable(method.getDeclaredAnnotation(Factory.class))
            : ofNullable(constructor.getDeclaredAnnotation(Factory.class));

        var dataProviderClass = factoryAnnotation
            .map(fa -> (Class) fa.dataProviderClass())
            .filter(cl -> cl != Object.class)
            .orElseGet(() -> testMethod.getConstructorOrMethod().getDeclaringClass());

        var dataProviderMethod = factoryAnnotation.map(Factory::dataProvider).orElse("");

        return Tuple.of(dataProviderClass, dataProviderMethod);
    }

    public static Class findParentDataSupplierClass(final Method testMethod, final Class testClass) {
        return ofNullable(testMethod)
            .map(m -> Tuple.of(m, new Reflections(m.getDeclaringClass().getPackage().getName())))
            .map(findParentDataSupplierClass())
            .orElse(testClass);
    }

    public static Function, Class> findParentDataSupplierClass() {
        return t -> StreamEx.of(t._2.getSubTypesOf(t._1.getDeclaringClass()))
            .findFirst(c -> c.isAnnotationPresent(Test.class))
            .map(c -> c.getDeclaredAnnotation(Test.class))
            .map(a -> (Class) a.dataProviderClass())
            .orElse(t._1.getDeclaringClass());
    }

    public static Predicate> hasDataSupplierMethod(final String targetMethodName) {
        return metaData -> nonNull(metaData._2)
            && (metaData._2.name().equals(targetMethodName) || metaData._1.getName().equals(targetMethodName));
    }

    @SuppressWarnings("unchecked")
    public static  Class castToArray(final Class entityClass) {
        return (Class) (entityClass.isArray() ? entityClass : Array.newInstance(entityClass, 1).getClass());
    }

    @SuppressWarnings("unchecked")
    public static  Class castToObject(final Class entityClass) {
        return (Class) (entityClass.isArray() ? entityClass.getComponentType() : entityClass);
    }

    public static String getFieldName(final Field field) {
        var fieldAnnotation = field.getDeclaredAnnotation(FieldName.class);
        return isNull(fieldAnnotation) ? field.getName() : fieldAnnotation.value();
    }

    public static  URL getSourcePath(final Class entity) throws IOException {
        return getSourcePath(ofNullable(entity.getDeclaredAnnotation(Source.class))
            .map(Source::path)
            .orElse(""));
    }

    public static URL getSourcePath(final String path) throws IOException {
        return ofNullable(Try.of(() -> new URL(path)).getOrElseGet(ex -> getSystemResource(path)))
            .orElseThrow(() -> new IOException("Unable to access resource specified by " + path + " path"));
    }

    public static  StreamEx streamOf(final T data) {
        if (isNull(data)) {
            throw new IllegalArgumentException("Nothing to return from data supplier. Test will be skipped.");
        }

        return StreamEx.of(TypeMappings.values())
            .findFirst(type -> type.isInstanceOf(data))
            .map(type -> type.streamOf(data))
            .orElseGet(() -> StreamEx.of(data));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy