ConfigLoader.java

/*
 * Copyright 2016,2017 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.providence.config;

import net.morimekta.providence.PMessage;
import net.morimekta.providence.config.impl.ProvidenceConfigSupplier;
import net.morimekta.providence.config.impl.ResourceConfigSupplier;
import net.morimekta.providence.config.parser.ConfigException;
import net.morimekta.providence.config.parser.ConfigParser;
import net.morimekta.providence.config.parser.ConfigWarning;
import net.morimekta.providence.config.util.FileContentResolver;
import net.morimekta.providence.config.util.ResourceContentResolver;
import net.morimekta.providence.types.TypeRegistry;
import net.morimekta.util.FileWatcher;

import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Clock;
import java.util.function.Consumer;

/**
 * Providence config loader. This loads providence configs.
 */
public class ConfigLoader implements AutoCloseable {
    /**
     * Make a non-strict config instance.
     *
     * @param registry The type registry used to find message and enum types.
     */
    public ConfigLoader(@Nonnull TypeRegistry registry) {
        this(registry,
             warning -> System.err.println(warning.displayString()));
    }

    /**
     * Make a non-strict config instance.
     *
     * @param registry       The type registry used to find message and enum types.
     * @param warningHandler Handle parse warnings.
     */
    public ConfigLoader(@Nonnull TypeRegistry registry,
                        @Nonnull Consumer<ConfigWarning> warningHandler) {
        this(registry, new FileWatcher(), warningHandler, false);
    }

    /**
     * Make a config instance.
     *
     * @param registry       The type registry used to find message and enum types.
     * @param watcher        File watcher used to detect config file updates.
     * @param warningHandler Handle parse warnings.
     * @param strict         If the config should be parsed and verified strictly.
     */
    public ConfigLoader(@Nonnull TypeRegistry registry,
                        @Nonnull FileWatcher watcher,
                        @Nonnull Consumer<ConfigWarning> warningHandler,
                        boolean strict) {
        this(registry, watcher, warningHandler, strict, Clock.systemUTC());
    }

    /**
     * Make a config instance.
     *
     * @param registry       The type registry used to find message and enum types.
     * @param watcher        File watcher used to detect config file updates.
     * @param warningHandler Handle parse warnings.
     * @param strict         If the config should be parsed strictly.
     * @param clock          The clock to use in timing config loads.
     */
    public ConfigLoader(@Nonnull TypeRegistry registry,
                        @Nonnull FileWatcher watcher,
                        @Nonnull Consumer<ConfigWarning> warningHandler,
                        boolean strict,
                        @Nonnull Clock clock) {
        this.watcher = watcher;
        this.clock = clock;
        this.resourceParser = new ConfigParser(registry, new ResourceContentResolver(), warningHandler, strict);
        this.fileParser = new ConfigParser(registry, new FileContentResolver(), warningHandler, strict);
    }

    /**
     * Load a config file overlaying another config.
     *
     * @param configFile   The file to resolve.
     * @param parentConfig The parent config supplier if any.
     * @param <M>          The message type.
     * @return The resolved config.
     * @throws ConfigException If parsing of config failed.
     */
    @Nonnull
    public <M extends PMessage<M>>
    ConfigSupplier<M> loadFile(@Nonnull Path configFile,
                               @Nonnull ConfigSupplier<M> parentConfig)
            throws ConfigException {
        return new ProvidenceConfigSupplier<>(
                configFile, parentConfig, watcher, fileParser, clock);
    }

    /**
     * Load a config file without parent config like on config file includes.
     *
     * @param configFile The file to resolve.
     * @param <M>        The message type.
     * @return The resolved config.
     * @throws ConfigException If parsing of config failed.
     */
    @Nonnull
    public <M extends PMessage<M>>
    ConfigSupplier<M> loadFile(@Nonnull Path configFile)
            throws ConfigException {
        return new ProvidenceConfigSupplier<>(
                configFile, null, watcher, fileParser, clock);
    }

    /**
     * Load a config resource without parent config like on config file includes.
     *
     * @param resourcePath The file to resolve.
     * @param <M>          The message type.
     * @return The resolved config.
     * @throws ConfigException If parsing of config failed.
     */
    public <M extends PMessage<M>>
    ConfigSupplier<M> loadResource(@Nonnull String resourcePath)
            throws ConfigException {
        return new ResourceConfigSupplier<>(
                Paths.get(resourcePath), resourceParser, clock);
    }

    /**
     * Get config for the given file.
     *
     * @param configFile The file to read config for.
     * @param <M>        The config message type.
     * @return The config message.
     * @throws ConfigException On config load failure.
     */
    @Nonnull
    public <M extends PMessage<M>>
    M getConfig(@Nonnull Path configFile) throws ConfigException {
        return fileParser.<M>parseConfig(configFile, null).first;
    }

    /**
     * Get config for the given with parent.
     *
     * @param configFile The file to read config for.
     * @param parent     The designated parent config.
     * @param <M>        The config message type.
     * @return The config message.
     * @throws ConfigException On config load failure.
     */
    @Nonnull
    public <M extends PMessage<M>>
    M getConfig(@Nonnull Path configFile, @Nonnull M parent) throws ConfigException {
        return fileParser.parseConfig(configFile, parent).first;
    }

    @Override
    public void close() {
        watcher.close();
    }

    private final FileWatcher  watcher;
    private final Clock        clock;

    private final ConfigParser resourceParser;
    private final ConfigParser fileParser;
}