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

io.atleon.util.ConfigLoading Maven / Gradle / Ivy

There is a newer version: 0.28.4
Show newest version
package io.atleon.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Convenience methods for loading configurations from arbitrary String-value mappings
 */
public final class ConfigLoading {

    private ConfigLoading() {

    }

    public static  T
    loadConfiguredOrThrow(Map configs, String property, Class type) {
        return loadConfiguredWithPredefinedTypes(configs, property, type, typeName -> Optional.empty())
            .orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static Duration loadDurationOrThrow(Map configs, String property) {
        return loadDuration(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static boolean loadBooleanOrThrow(Map configs, String property) {
        return loadBoolean(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static int loadIntOrThrow(Map configs, String property) {
        return loadInt(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static long loadLongOrThrow(Map configs, String property) {
        return loadLong(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static String loadStringOrThrow(Map configs, String property) {
        return loadString(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static > T loadEnumOrThrow(Map configs, String property, Class enumType) {
        return loadEnum(configs, property, enumType).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static Class loadClassOrThrow(Map configs, String property) {
        return ConfigLoading.loadClass(configs, property).orElseThrow(supplyMissingConfigPropertyException(property));
    }

    public static  Optional loadConfiguredWithPredefinedTypes(
        Map configs,
        String property,
        Class type,
        Function> predefinedTypeInstantiator
    ) {
        Function instantiator = typeName ->
            predefinedTypeInstantiator.apply(typeName).orElseGet(() -> Instantiation.oneTyped(type, typeName));
        Optional result = load(configs, property, value -> coerce(value, type, instantiator));
        result.ifPresent(configurable -> configurable.configure(configs));
        return result;
    }

    public static Optional loadUri(Map configs, String property) {
        return loadParseable(configs, property, URI.class, URI::create);
    }

    public static Optional loadDuration(Map configs, String property) {
        return loadParseable(configs, property, Duration.class, Duration::parse);
    }

    public static Optional loadBoolean(Map configs, String property) {
        return loadParseable(configs, property, Boolean.class, Boolean::parseBoolean);
    }

    public static Optional loadInt(Map configs, String property) {
        return loadParseable(configs, property, Number.class, Integer::parseInt).map(Number::intValue);
    }

    public static Optional loadLong(Map configs, String property) {
        return loadParseable(configs, property, Number.class, Long::parseLong).map(Number::longValue);
    }

    public static Optional loadString(Map configs, String property) {
        return loadParseable(configs, property, String.class, Function.identity());
    }

    public static > Optional loadEnum(Map configs, String property, Class enumType) {
        return loadParseable(configs, property, enumType, value -> parseEnum(enumType, value));
    }

    public static Optional> loadClass(Map configs, String property) {
        return loadParseable(configs, property, Class.class, TypeResolution::classForQualifiedName)
            .map(type -> (Class) type);
    }

    public static  Optional
    loadParseable(Map configs, String property, Class type, Function parser) {
        return load(configs, property, value -> coerce(value, type, parser));
    }

    public static  List
    loadListOfConfiguredServices(Class type, Map configs) {
        ServiceLoader serviceLoader = ServiceLoader.load(type);
        return StreamSupport.stream(serviceLoader.spliterator(), false)
            .peek(configurable -> configurable.configure(configs))
            .collect(Collectors.toList());
    }

    public static  List loadListOfInstancesOrEmpty(Map configs, String property, Class type) {
        return loadListOfInstances(configs, property, type).orElse(Collections.emptyList());
    }

    public static Set loadSetOfStringOrEmpty(Map configs, String property) {
        return loadSetOfString(configs, property).orElse(Collections.emptySet());
    }

    public static  Optional> loadListOfConfiguredWithPredefinedTypes(
        Map configs,
        String property,
        Class type,
        Function>> predefinedTypeInstantiator
    ) {
        Optional> result =
            loadListOfInstancesWithPredefinedTypes(configs, property, type, predefinedTypeInstantiator);
        result.ifPresent(configurables -> configurables.forEach(configurable -> configurable.configure(configs)));
        return result;
    }

    public static  Optional> loadListOfInstances(Map configs, String property, Class type) {
        return loadListOfInstancesWithPredefinedTypes(configs, property, type, typeName -> Optional.empty());
    }

    public static  Optional> loadListOfInstancesWithPredefinedTypes(
        Map configs,
        String property,
        Class type,
        Function>> predefinedTypeInstantiator
    ) {
        return loadStream(configs, property, Function.identity())
            .map(stream -> coerceToList(stream, type, predefinedTypeInstantiator));
    }

    public static Optional> loadSetOfString(Map configs, String property) {
        return loadStream(configs, property, Objects::toString)
            .map(stream -> stream.collect(Collectors.toCollection(LinkedHashSet::new)));
    }

    private static > T parseEnum(Class enumType, String value) {
        try {
            Method method = enumType.getDeclaredMethod("valueOf", String.class);
            return enumType.cast(method.invoke(null, value));
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Could not get parsing method from enumType=" + enumType, e);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new IllegalStateException("Could not invoke parsing method from enumType=" + enumType, e);
        }
    }

    private static  Optional load(Map configs, String property, Function coercer) {
        return Optional.ofNullable(configs.get(property)).map(coercer);
    }

    private static  Optional> loadStream(Map configs, String property, Function coercer) {
        if (configs.containsKey(property)) {
            Object value = configs.get(property);
            return Optional.of(value == null ? Stream.empty() : convertToStream(value).map(coercer));
        } else {
            return Optional.empty();
        }
    }

    private static Stream convertToStream(Object value) {
        if (value instanceof Collection) {
            return Collection.class.cast(value).stream();
        } else if (value instanceof CharSequence) {
            return Stream.of(value.toString().split(","))
                .map(String::trim)
                .filter(string -> !string.isEmpty());
        } else {
            return Stream.of(value);
        }
    }

    private static  List coerceToList(
        Stream stream,
        Class type,
        Function>> predefinedTypeInstantiator
    ) {
        Function instantiator = typeName -> Instantiation.oneTyped(type, typeName);
        return stream.flatMap(object -> coerceToList(object, type, instantiator, predefinedTypeInstantiator).stream())
            .collect(Collectors.toList());
    }

    private static  List coerceToList(
        Object value,
        Class toType,
        Function parser,
        Function>> predefinedTypeInstantiator
    ) {
        Optional> instantiated = value instanceof CharSequence
            ? predefinedTypeInstantiator.apply(value.toString())
            : Optional.empty();
        return instantiated.orElseGet(() -> Collections.singletonList(coerce(value, toType, parser)));
    }

    private static  T coerce(Object value, Class toType, Function parser) {
        if (toType.isInstance(value)) {
            return toType.cast(value);
        } else if (value instanceof Class) {
            return Instantiation.oneTyped(toType, Class.class.cast(value));
        } else if (value instanceof CharSequence) {
            return parser.apply(value.toString());
        } else {
            throw new UnsupportedOperationException("Cannot coerce value=" + value + " to object of type=" + toType);
        }
    }

    private static Supplier supplyMissingConfigPropertyException(String property) {
        return () -> new IllegalArgumentException("Missing config: " + property);
    }
}