YamlUtils.java

package io.github.giulong.spectrum.utils;

import ch.qos.logback.classic.Level;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.github.giulong.spectrum.drivers.Driver;
import io.github.giulong.spectrum.internals.jackson.deserializers.*;
import io.github.giulong.spectrum.utils.environments.Environment;
import io.github.giulong.spectrum.utils.file_providers.ClientFileProvider;
import io.github.giulong.spectrum.utils.file_providers.FileProvider;
import io.github.giulong.spectrum.utils.file_providers.InternalFileProvider;
import io.github.giulong.spectrum.utils.reporters.FileReporter.HtmlSummaryReporter;
import io.github.giulong.spectrum.utils.reporters.FileReporter.HtmlTestBookReporter;
import io.github.giulong.spectrum.utils.reporters.FileReporter.TxtSummaryReporter;
import io.github.giulong.spectrum.utils.reporters.FileReporter.TxtTestBookReporter;
import io.github.giulong.spectrum.utils.reporters.LogReporter.LogSummaryReporter;
import io.github.giulong.spectrum.utils.reporters.LogReporter.LogTestBookReporter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.util.Random;

import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
import static lombok.AccessLevel.PRIVATE;

@Slf4j
@NoArgsConstructor(access = PRIVATE)
public final class YamlUtils {

    private static final YamlUtils INSTANCE = new YamlUtils();

    private final ClassLoader classLoader = YamlUtils.class.getClassLoader();
    private final FileProvider internalFileProvider = InternalFileProvider.builder().build();
    private final FileProvider clientFileProvider = ClientFileProvider.builder().build();

    private final ObjectMapper yamlMapper = YAMLMapper
            .builder()
            .defaultMergeable(true)
            .addModules(
                    new JavaTimeModule(),
                    buildModuleFor(Object.class, InterpolatedObjectDeserializer.getInstance()),
                    buildModuleFor(String.class, InterpolatedStringDeserializer.getInstance()),
                    buildModuleFor(boolean.class, InterpolatedBooleanDeserializer.getInstance()),
                    buildModuleFor(java.util.logging.Level.class, UtilLogLevelDeserializer.getInstance()),
                    buildModuleFor(Level.class, LogbackLogLevelDeserializer.getInstance()),
                    buildModuleFor(Duration.class, DurationDeserializer.getInstance()),
                    buildModuleFor(Driver.class, DriverDeserializer.getInstance()),
                    buildModuleFor(Environment.class, EnvironmentDeserializer.getInstance()),
                    buildModuleFor(Class.class, ClassDeserializer.getInstance()),
                    buildModuleFor(Random.class, RandomDeserializer.getInstance()),
                    buildDynamicModuleFor(LogTestBookReporter.class, "yaml/dynamic/testbook/logReporter.yaml"),
                    buildDynamicModuleFor(TxtTestBookReporter.class, "yaml/dynamic/testbook/txtReporter.yaml"),
                    buildDynamicModuleFor(HtmlTestBookReporter.class, "yaml/dynamic/testbook/htmlReporter.yaml"),
                    buildDynamicModuleFor(LogSummaryReporter.class, "yaml/dynamic/summary/logReporter.yaml"),
                    buildDynamicModuleFor(TxtSummaryReporter.class, "yaml/dynamic/summary/txtReporter.yaml"),
                    buildDynamicModuleFor(HtmlSummaryReporter.class, "yaml/dynamic/summary/htmlReporter.yaml"))
            .build();

    private final ObjectMapper dynamicConfYamlMapper = YAMLMapper
            .builder()
            .defaultMergeable(true)
            .addModules(
                    new JavaTimeModule(),
                    buildModuleFor(Object.class, InterpolatedObjectDeserializer.getInstance()),
                    buildModuleFor(String.class, InterpolatedStringDeserializer.getInstance()),
                    buildModuleFor(boolean.class, InterpolatedBooleanDeserializer.getInstance()),
                    buildModuleFor(java.util.logging.Level.class, UtilLogLevelDeserializer.getInstance()),
                    buildModuleFor(Level.class, LogbackLogLevelDeserializer.getInstance()),
                    buildModuleFor(Duration.class, DurationDeserializer.getInstance()))
            .build();

    private final ObjectWriter writer = YAMLMapper
            .builder()
            .configure(FAIL_ON_EMPTY_BEANS, false)
            .addModules(new JavaTimeModule())
            .build()
            .writerWithDefaultPrettyPrinter();

    public static YamlUtils getInstance() {
        return INSTANCE;
    }

    public <T> T readClient(final String file, final Class<T> clazz) {
        return read(clientFileProvider, file, clazz);
    }

    public <T> T readInternal(final String file, final Class<T> clazz) {
        return read(internalFileProvider, file, clazz);
    }

    public <T> T readClientNode(final String node, final String file, final Class<T> clazz) {
        return readNode(clientFileProvider, node, file, clazz);
    }

    public <T> T readInternalNode(final String node, final String file, final Class<T> clazz) {
        return readNode(internalFileProvider, node, file, clazz);
    }

    public <T> void updateWithClientFile(final T t, final String file) {
        updateWithFile(t, file, clientFileProvider);
    }

    public <T> void updateWithInternalFile(final T t, final String file) {
        updateWithFile(t, file, internalFileProvider);
    }

    @SneakyThrows
    public String write(final Object object) {
        return writer.writeValueAsString(object);
    }

    @SneakyThrows
    public <T> T readDynamicDeserializable(final String configFile, final Class<T> clazz, final JsonNode jsonNode) {
        log.debug("Reading dynamic conf file '{}' onto an instance of {}", configFile, clazz.getSimpleName());
        final ObjectReader reader = dynamicConfYamlMapper.reader();
        final T t = read(reader, configFile, clazz);

        return reader
                .withValueToUpdate(t)
                .readValue(jsonNode);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    SimpleModule buildModuleFor(final Class<?> clazz, final JsonDeserializer jsonDeserializer) {
        return new SimpleModule(clazz.getSimpleName()).addDeserializer(clazz, jsonDeserializer);
    }

    SimpleModule buildDynamicModuleFor(final Class<?> clazz, final String file) {
        return buildModuleFor(clazz, new DynamicDeserializer<>(clazz, file));
    }

    @SneakyThrows
    <T> T read(final ObjectReader reader, final String file, final Class<T> clazz) {
        return reader.readValue(classLoader.getResource(file), clazz);
    }

    <T> T read(final FileProvider fileProvider, final String file, final Class<T> clazz) {
        final String fileFound = fileProvider.find(file);
        if (fileFound == null) {
            return null;
        }

        log.debug("Reading file '{}' onto an instance of {}", fileFound, clazz.getSimpleName());
        return read(fileProvider.augment(yamlMapper), fileFound, clazz);
    }

    @SneakyThrows
    <T> T readNode(final FileProvider fileProvider, final String node, final String file, final Class<T> clazz) {
        final String fileFound = fileProvider.find(file);
        if (fileFound == null) {
            return null;
        }

        log.debug("Reading node '{}' of file '{}' onto an instance of {}", node, fileFound, clazz.getSimpleName());
        final JsonNode root = fileProvider
                .augment(yamlMapper)
                .readTree(classLoader.getResourceAsStream(fileFound));

        return yamlMapper.convertValue(root.at(node), clazz);
    }

    @SneakyThrows
    <T> void updateWithFile(final T t, final String file, final FileProvider fileProvider) {
        final String fileFound = fileProvider.find(file);
        if (fileFound == null) {
            log.warn("File not found. Skipping update of the instance of {}", t.getClass().getSimpleName());
            return;
        }

        log.debug("Updating the instance of {} with file '{}'", t.getClass().getSimpleName(), fileFound);
        fileProvider
                .augment(yamlMapper)
                .withValueToUpdate(t)
                .readValue(classLoader.getResource(fileFound));
    }
}