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

com.github.anhem.testpopulator.PopulateFactory Maven / Gradle / Ivy

Go to download

Populate java classes with fixed or random data using reflection. Facilitates the creation of objects in tests.

The newest version!
package com.github.anhem.testpopulator;

import com.github.anhem.testpopulator.config.PopulateConfig;
import com.github.anhem.testpopulator.config.Strategy;
import com.github.anhem.testpopulator.exception.PopulateException;
import com.github.anhem.testpopulator.internal.carrier.ClassCarrier;
import com.github.anhem.testpopulator.internal.carrier.CollectionCarrier;
import com.github.anhem.testpopulator.internal.carrier.TypeCarrier;
import com.github.anhem.testpopulator.internal.object.ObjectFactory;
import com.github.anhem.testpopulator.internal.object.ObjectFactoryImpl;
import com.github.anhem.testpopulator.internal.object.ObjectFactoryVoid;
import com.github.anhem.testpopulator.internal.value.ValueFactory;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static com.github.anhem.testpopulator.config.BuilderPattern.IMMUTABLES;
import static com.github.anhem.testpopulator.config.BuilderPattern.LOMBOK;
import static com.github.anhem.testpopulator.config.Strategy.*;
import static com.github.anhem.testpopulator.internal.carrier.CollectionCarrier.initialize;
import static com.github.anhem.testpopulator.internal.util.ImmutablesUtil.getImmutablesGeneratedClass;
import static com.github.anhem.testpopulator.internal.util.ImmutablesUtil.getMethodsForImmutablesBuilder;
import static com.github.anhem.testpopulator.internal.util.LombokUtil.calculateExpectedChildren;
import static com.github.anhem.testpopulator.internal.util.LombokUtil.getMethodsForLombokBuilderGroupedByInvokeOrder;
import static com.github.anhem.testpopulator.internal.util.PopulateUtil.*;
import static java.lang.String.format;

/**
 * Factory for creating populated objects from classes
 */
public class PopulateFactory {

    static final String MISSING_COLLECTION_TYPE = "Failed to find type for collection %s";
    static final String NO_MATCHING_STRATEGY = "Unable to populate %s. No matching strategy found. Tried with %s. Try another strategy or override population for this class";
    static final String FAILED_TO_SET_FIELD = "Failed to set field %s in object of class %s";
    static final String FAILED_TO_CALL_METHOD = "Failed to call method %s in object of class %s";
    static final String FAILED_TO_CREATE_OBJECT = "Failed to create object of %s using %s strategy";
    static final String FAILED_TO_CREATE_COLLECTION = "Failed to create and populate collection %s";

    public static final String BUILD_METHOD = "build";
    public static final String BUILDER_METHOD = "builder";

    private final PopulateConfig populateConfig;
    private final ValueFactory valueFactory;

    /**
     * Create new instance of PopulateFactory with default configuration
     */
    public PopulateFactory() {
        this(PopulateConfig.builder().build());
    }

    /**
     * Create new instance of PopulateFactory
     *
     * @param populateConfig configuration properties for PopulateFactory
     */
    public PopulateFactory(PopulateConfig populateConfig) {
        this.populateConfig = populateConfig;
        this.valueFactory = new ValueFactory(populateConfig.useRandomValues(), populateConfig.getOverridePopulate());
    }

    /**
     * Call to create a fully populated object from a class
     *
     * @param clazz Class that should be populated
     * @return object of clazz
     */
    public  T populate(Class clazz) {
        ObjectFactory objectFactory = populateConfig.isObjectFactoryEnabled() ? new ObjectFactoryImpl(populateConfig) : new ObjectFactoryVoid();
        T t = populateWithOverrides(initialize(clazz, objectFactory));
        objectFactory.writeToFile();
        return t;
    }

    private  T populateWithOverrides(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        if (valueFactory.hasType(clazz)) {
            T value = valueFactory.createValue(clazz);
            classCarrier.getObjectFactory().value(value);
            return value;
        }
        if (populateConfig.isNullOnCircularDependency() && !isJavaBaseClass(clazz) && !classCarrier.addVisited()) {
            classCarrier.getObjectFactory().nullValue(clazz);
            return null;
        }
        if (isCollectionCarrier(classCarrier)) {
            return continuePopulateForCollection((CollectionCarrier) classCarrier);
        }
        if (clazz.isArray()) {
            return continuePopulateForArray(classCarrier);
        }
        return continuePopulateWithStrategies(classCarrier);
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateForArray(ClassCarrier classCarrier) {
        Class componentType = classCarrier.getClazz().getComponentType();
        classCarrier.getObjectFactory().array(componentType);
        Object value = populateWithOverrides(classCarrier.toClassCarrier(componentType));
        Object array = Array.newInstance(componentType, 1);
        Array.set(array, 0, value);
        return (T) array;
    }

    private  T continuePopulateForCollection(CollectionCarrier classCarrier) {
        try {
            if (isMap(classCarrier.getClazz())) {
                return continuePopulateForMap(classCarrier);
            }
            if (isSet(classCarrier.getClazz())) {
                return continuePopulateForSet(classCarrier);
            }
            if (isCollection(classCarrier.getClazz())) {
                return continuePopulateForList(classCarrier);
            }
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_COLLECTION, classCarrier.getClazz().getTypeName()), e);
        }
        throw new PopulateException(format(MISSING_COLLECTION_TYPE, classCarrier.getClazz().getTypeName()));
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateForMap(CollectionCarrier classCarrier) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        if (hasConstructors(classCarrier)) {
            classCarrier.getObjectFactory().map(classCarrier.getClazz());
            Map map = (Map) classCarrier.getClazz().getConstructor().newInstance();
            Optional key = Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))));
            Optional value = Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(1))));
            key.ifPresent(k -> map.put(k, value.orElse(null)));
            return (T) map;
        } else {
            classCarrier.getObjectFactory().mapOf();
            Optional key = Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))));
            Optional value = Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(1))));
            if (key.isPresent() && value.isPresent()) {
                return (T) Map.of(key.get(), value.get());
            }
            return (T) Map.of();
        }
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateForSet(CollectionCarrier classCarrier) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        if (hasConstructors(classCarrier)) {
            classCarrier.getObjectFactory().set(classCarrier.getClazz());
            Set set = (Set) classCarrier.getClazz().getConstructor().newInstance();
            Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))))
                    .ifPresent(set::add);
            return (T) set;
        } else {
            classCarrier.getObjectFactory().setOf();
            return Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))))
                    .map(value -> (T) Set.of(value))
                    .orElseGet(() -> (T) Set.of());
        }
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateForList(CollectionCarrier classCarrier) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        if (hasConstructors(classCarrier)) {
            classCarrier.getObjectFactory().list(classCarrier.getClazz());
            List list = (List) classCarrier.getClazz().getConstructor().newInstance();
            Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))))
                    .ifPresent(list::add);
            return (T) list;
        } else {
            classCarrier.getObjectFactory().listOf();
            return Optional.ofNullable(continuePopulateWithType(classCarrier.toTypeCarrier(classCarrier.getArgumentTypes().get(0))))
                    .map(value -> (T) List.of(value))
                    .orElseGet(() -> (T) List.of());
        }
    }

    private Object continuePopulateWithType(TypeCarrier typeCarrier) {
        Type type = typeCarrier.getType();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return populateWithOverrides(typeCarrier.toCollectionCarrier(parameterizedType.getRawType(), parameterizedType.getActualTypeArguments()));
        }
        return populateWithOverrides(typeCarrier.toClassCarrier(type));
    }

    private  T continuePopulateWithStrategies(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        for (Strategy strategy : populateConfig.getStrategyOrder()) {
            if (isMatchingConstructorStrategy(strategy, clazz, populateConfig.canAccessNonPublicConstructors())) {
                return continuePopulateUsingConstructor(classCarrier);
            }
            if (isMatchingSetterStrategy(strategy, clazz, populateConfig.getSetterPrefix(), populateConfig.canAccessNonPublicConstructors())) {
                return continuePopulateUsingSetters(classCarrier);
            }
            if (isMatchingFieldStrategy(strategy, clazz, populateConfig.canAccessNonPublicConstructors())) {
                return continuePopulateUsingFields(classCarrier);
            }
            if (isMatchingBuilderStrategy(strategy, clazz, populateConfig.getBuilderPattern())) {
                return continuePopulateUsingBuilder(classCarrier);
            }
        }
        throw new PopulateException(format(NO_MATCHING_STRATEGY, clazz.getName(), populateConfig.getStrategyOrder()));
    }

    private  T continuePopulateUsingConstructor(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        try {
            Constructor constructor = getLargestConstructor(clazz, populateConfig.canAccessNonPublicConstructors());
            setAccessible(constructor, populateConfig.canAccessNonPublicConstructors());
            classCarrier.getObjectFactory().constructor(clazz, constructor.getParameterCount());
            Object[] arguments = IntStream.range(0, constructor.getParameterCount()).mapToObj(i -> {
                Parameter parameter = constructor.getParameters()[i];
                if (isCollection(parameter.getType())) {
                    return populateWithOverrides(classCarrier.toCollectionCarrier(parameter));
                } else {
                    return populateWithOverrides(classCarrier.toClassCarrier(parameter));
                }
            }).toArray();
            return constructor.newInstance(arguments);
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_OBJECT, clazz.getName(), CONSTRUCTOR), e);
        }
    }

    private  T continuePopulateUsingFields(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            setAccessible(constructor, populateConfig.canAccessNonPublicConstructors());
            T objectOfClass = constructor.newInstance();
            getDeclaredFields(clazz, populateConfig.getBlacklistedFields()).stream()
                    .filter(field -> !Modifier.isFinal(field.getModifiers()))
                    .forEach(field -> {
                        try {
                            setAccessible(field, objectOfClass);
                            if (isCollection(field.getType())) {
                                Type[] typeArguments = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
                                field.set(objectOfClass, populateWithOverrides(classCarrier.toCollectionCarrier(field.getType(), typeArguments)));
                            } else {
                                field.set(objectOfClass, populateWithOverrides(classCarrier.toClassCarrier(field.getType())));
                            }
                        } catch (Exception e) {
                            throw new PopulateException(format(FAILED_TO_SET_FIELD, field.getName(), objectOfClass.getClass().getName()), e);
                        }
                    });
            return objectOfClass;
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_OBJECT, clazz.getName(), FIELD), e);
        }
    }

    private  T continuePopulateUsingSetters(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            setAccessible(constructor, populateConfig.canAccessNonPublicConstructors());
            T objectOfClass = constructor.newInstance();
            List methods = getDeclaredMethods(clazz, populateConfig.getBlacklistedMethods()).stream()
                    .filter(method -> isSetterMethod(method, populateConfig.getSetterPrefix()))
                    .collect(Collectors.toList());
            classCarrier.getObjectFactory().setter(clazz, methods.size());
            methods.forEach(method -> continuePopulateForMethod(objectOfClass, method, classCarrier));
            return objectOfClass;
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_OBJECT, clazz.getName(), SETTER), e);
        }
    }

    private  T continuePopulateUsingBuilder(ClassCarrier classCarrier) {
        if (populateConfig.getBuilderPattern().equals(LOMBOK)) {
            return continuePopulateUsingLombokBuilder(classCarrier);
        }
        if (populateConfig.getBuilderPattern().equals(IMMUTABLES)) {
            return continuePopulateUsingImmutablesBuilder(classCarrier);
        }
        throw new PopulateException("");
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateUsingLombokBuilder(ClassCarrier classCarrier) {
        Class clazz = classCarrier.getClazz();
        try {
            Object builderObject = clazz.getDeclaredMethod(BUILDER_METHOD).invoke(null);
            Map> builderObjectMethodsGroupedByInvokeOrder = getMethodsForLombokBuilderGroupedByInvokeOrder(builderObject.getClass(), populateConfig.getBlacklistedMethods());
            classCarrier.getObjectFactory().builder(clazz, calculateExpectedChildren(builderObjectMethodsGroupedByInvokeOrder));
            Optional.ofNullable(builderObjectMethodsGroupedByInvokeOrder.get(1)).ifPresent(methods ->
                    methods.forEach(method -> continuePopulateForMethod(builderObject, method, classCarrier)));
            Optional.ofNullable(builderObjectMethodsGroupedByInvokeOrder.get(2)).ifPresent(methods ->
                    methods.forEach(method -> continuePopulateForMethod(builderObject, method, classCarrier)));
            Optional.ofNullable(builderObjectMethodsGroupedByInvokeOrder.get(3)).ifPresent(methods ->
                    methods.forEach(method -> continuePopulateForMethod(builderObject, method, classCarrier)));
            Method buildMethod = builderObject.getClass().getDeclaredMethod(BUILD_METHOD);
            setAccessible(buildMethod, builderObject);
            return (T) buildMethod.invoke(builderObject);
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_OBJECT, clazz.getName(), format("%s (%s)", BUILDER, LOMBOK)), e);
        }
    }

    @SuppressWarnings("unchecked")
    private  T continuePopulateUsingImmutablesBuilder(ClassCarrier classCarrier) {
        try {
            Class immutablesGeneratedClass = getImmutablesGeneratedClass(classCarrier.getClazz());
            Object builderObject = immutablesGeneratedClass.getDeclaredMethod(BUILDER_METHOD).invoke(null);
            List builderObjectMethods = getMethodsForImmutablesBuilder(immutablesGeneratedClass, builderObject, populateConfig.getBlacklistedMethods());
            classCarrier.getObjectFactory().builder(immutablesGeneratedClass, builderObjectMethods.size());
            builderObjectMethods.forEach(method -> continuePopulateForMethod(builderObject, method, classCarrier));
            Method buildMethod = builderObject.getClass().getDeclaredMethod(BUILD_METHOD);
            return (T) buildMethod.invoke(builderObject);
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CREATE_OBJECT, classCarrier.getClazz().getName(), format("%s (%s)", BUILDER, IMMUTABLES)), e);
        }
    }

    private  void continuePopulateForMethod(V objectOfClass, Method method, ClassCarrier classCarrier) {
        try {
            classCarrier.getObjectFactory().method(method.getName(), method.getParameters().length);
            method.invoke(objectOfClass, Stream.of(method.getParameters())
                    .map(parameter -> {
                        if (isCollection(parameter.getType())) {
                            return populateWithOverrides(classCarrier.toCollectionCarrier(parameter));
                        } else {
                            return populateWithOverrides(classCarrier.toClassCarrier(parameter));
                        }
                    }).toArray());
        } catch (Exception e) {
            throw new PopulateException(format(FAILED_TO_CALL_METHOD, method.getName(), objectOfClass.getClass().getName()), e);
        }
    }
}