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

com.uchuhimo.konf.source.DefaultLoaders.kt Maven / Gradle / Ivy

/*
 * Copyright 2017-2019 the original author or authors.
 *
 * Licensed 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 com.uchuhimo.konf.source

import com.uchuhimo.konf.Config
import com.uchuhimo.konf.Feature
import com.uchuhimo.konf.source.base.FlatSource
import com.uchuhimo.konf.source.base.KVSource
import com.uchuhimo.konf.source.base.MapSource
import com.uchuhimo.konf.source.env.EnvProvider
import com.uchuhimo.konf.source.json.JsonProvider
import com.uchuhimo.konf.source.properties.PropertiesProvider
import java.io.File
import java.net.URL
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers

/**
 * Default loaders for config.
 *
 * If [transform] is provided, source will be applied the given [transform] function when loaded.
 *
 * @param config parent config for loader
 * @param transform the given transformation function
 */
class DefaultLoaders(
    /**
     * Parent config for loader.
     */
    val config: Config,
    /**
     * The given transformation function.
     */
    private val transform: ((Source) -> Source)? = null
) {
    val optional = config.isEnabled(Feature.OPTIONAL_SOURCE_BY_DEFAULT)
    fun Provider.orMapped(): Provider =
        if (transform != null) this.map(transform) else this

    fun Source.orMapped(): Source = transform?.invoke(this) ?: this

    /**
     * Returns default loaders applied the given [transform] function.
     *
     * @param transform the given transformation function
     * @return the default loaders applied the given [transform] function
     */
    fun mapped(transform: (Source) -> Source): DefaultLoaders = DefaultLoaders(config) {
        transform(it.orMapped())
    }

    /**
     * Returns default loaders where sources have specified additional prefix.
     *
     * @param prefix additional prefix
     * @return the default loaders where sources have specified additional prefix
     */
    fun prefixed(prefix: String): DefaultLoaders = mapped { it.withPrefix(prefix) }

    /**
     * Returns default loaders where sources are scoped in specified path.
     *
     * @param path path that is the scope of sources
     * @return the default loaders where sources are scoped in specified path
     */
    fun scoped(path: String): DefaultLoaders = mapped { it[path] }

    fun enabled(feature: Feature): DefaultLoaders = mapped { it.enabled(feature) }

    fun disabled(feature: Feature): DefaultLoaders = mapped { it.disabled(feature) }

    /**
     * Loader for JSON source.
     */
    @JvmField
    val json = Loader(config, JsonProvider.orMapped())

    /**
     * Loader for properties source.
     */
    @JvmField
    val properties = Loader(config, PropertiesProvider.orMapped())

    /**
     * Loader for map source.
     */
    @JvmField
    val map = MapLoader(config, transform)

    /**
     * Loader for a source from the specified provider.
     *
     * @param provider the specified provider
     * @return a loader for a source from the specified provider
     */
    fun source(provider: Provider) = Loader(config, provider.orMapped())

    /**
     * Returns a child config containing values from system environment.
     *
     * @param nested whether to treat "AA_BB_CC" as nested format "AA.BB.CC" or not. True by default.
     * @return a child config containing values from system environment
     */
    @JvmOverloads
    fun env(nested: Boolean = true): Config = config.withSource(EnvProvider.env(nested).orMapped())

    /**
     * Returns a child config containing values from system properties.
     *
     * @return a child config containing values from system properties
     */
    fun systemProperties(): Config = config.withSource(PropertiesProvider.system().orMapped())

    /**
     * Returns corresponding loader based on extension.
     *
     * @param extension the file extension
     * @param source the source description for error message
     * @return the corresponding loader based on extension
     */
    fun dispatchExtension(extension: String, source: String = ""): Loader =
        Loader(config, Provider.of(extension)?.orMapped()
            ?: throw UnsupportedExtensionException(source))

    /**
     * Returns a child config containing values from specified file.
     *
     * Format of the file is auto-detected from the file extension.
     * Supported file formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the file extension is unsupported.
     *
     * @param file specified file
     * @param optional whether the source is optional
     * @return a child config containing values from specified file
     * @throws UnsupportedExtensionException
     */
    fun file(file: File, optional: Boolean = this.optional): Config = dispatchExtension(file.extension, file.name).file(file, optional)

    /**
     * Returns a child config containing values from specified file path.
     *
     * Format of the file is auto-detected from the file extension.
     * Supported file formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the file extension is unsupported.
     *
     * @param file specified file path
     * @param optional whether the source is optional
     * @return a child config containing values from specified file path
     * @throws UnsupportedExtensionException
     */
    fun file(file: String, optional: Boolean = this.optional): Config = file(File(file), optional)

    /**
     * Returns a child config containing values from specified file,
     * and reloads values when file content has been changed.
     *
     * Format of the file is auto-detected from the file extension.
     * Supported file formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the file extension is unsupported.
     *
     * @param file specified file
     * @param delayTime delay to observe between every check. The default value is 5.
     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].
     * @param context context of the coroutine. The default value is [Dispatchers.Default].
     * @param optional whether the source is optional
     * @return a child config containing values from watched file
     * @throws UnsupportedExtensionException
     */
    fun watchFile(
        file: File,
        delayTime: Long = 5,
        unit: TimeUnit = TimeUnit.SECONDS,
        context: CoroutineContext = Dispatchers.Default,
        optional: Boolean = this.optional
    ): Config = dispatchExtension(file.extension, file.name)
        .watchFile(file, delayTime, unit, context, optional)

    /**
     * Returns a child config containing values from specified file path,
     * and reloads values when file content has been changed.
     *
     * Format of the file is auto-detected from the file extension.
     * Supported file formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the file extension is unsupported.
     *
     * @param file specified file path
     * @param delayTime delay to observe between every check. The default value is 5.
     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].
     * @param context context of the coroutine. The default value is [Dispatchers.Default].
     * @param optional whether the source is optional
     * @return a child config containing values from watched file
     * @throws UnsupportedExtensionException
     */
    fun watchFile(
        file: String,
        delayTime: Long = 5,
        unit: TimeUnit = TimeUnit.SECONDS,
        context: CoroutineContext = Dispatchers.Default,
        optional: Boolean = this.optional
    ): Config = watchFile(File(file), delayTime, unit, context, optional)

    /**
     * Returns a child config containing values from specified url.
     *
     * Format of the url is auto-detected from the url extension.
     * Supported url formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the url extension is unsupported.
     *
     * @param url specified url
     * @param optional whether the source is optional
     * @return a child config containing values from specified url
     * @throws UnsupportedExtensionException
     */
    fun url(url: URL, optional: Boolean = this.optional): Config = dispatchExtension(File(url.path).extension, url.toString()).url(url, optional)

    /**
     * Returns a child config containing values from specified url string.
     *
     * Format of the url is auto-detected from the url extension.
     * Supported url formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the url extension is unsupported.
     *
     * @param url specified url string
     * @param optional whether the source is optional
     * @return a child config containing values from specified url string
     * @throws UnsupportedExtensionException
     */
    fun url(url: String, optional: Boolean = this.optional): Config = url(URL(url), optional)

    /**
     * Returns a child config containing values from specified url,
     * and reloads values periodically.
     *
     * Format of the url is auto-detected from the url extension.
     * Supported url formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the url extension is unsupported.
     *
     * @param url specified url
     * @param period reload period. The default value is 5.
     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].
     * @param context context of the coroutine. The default value is [Dispatchers.Default].
     * @param optional whether the source is optional
     * @return a child config containing values from specified url
     * @throws UnsupportedExtensionException
     */
    fun watchUrl(
        url: URL,
        period: Long = 5,
        unit: TimeUnit = TimeUnit.SECONDS,
        context: CoroutineContext = Dispatchers.Default,
        optional: Boolean = this.optional
    ): Config = dispatchExtension(File(url.path).extension, url.toString())
        .watchUrl(url, period, unit, context, optional)

    /**
     * Returns a child config containing values from specified url string,
     * and reloads values periodically.
     *
     * Format of the url is auto-detected from the url extension.
     * Supported url formats and the corresponding extensions:
     * - HOCON: conf
     * - JSON: json
     * - Properties: properties
     * - TOML: toml
     * - XML: xml
     * - YAML: yml, yaml
     *
     * Throws [UnsupportedExtensionException] if the url extension is unsupported.
     *
     * @param url specified url string
     * @param period reload period. The default value is 5.
     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].
     * @param context context of the coroutine. The default value is [Dispatchers.Default].
     * @param optional whether the source is optional
     * @return a child config containing values from specified url string
     * @throws UnsupportedExtensionException
     */
    fun watchUrl(
        url: String,
        period: Long = 5,
        unit: TimeUnit = TimeUnit.SECONDS,
        context: CoroutineContext = Dispatchers.Default,
        optional: Boolean = this.optional
    ): Config = watchUrl(URL(url), period, unit, context, optional)
}

/**
 * Loader to load source from map of variant formats.
 *
 * If [transform] is provided, source will be applied the given [transform] function when loaded.
 *
 * @param config parent config
 */
class MapLoader(
    /**
     * Parent config for all child configs loading source in this loader.
     */
    val config: Config,
    /**
     * The given transformation function.
     */
    private val transform: ((Source) -> Source)? = null
) {
    fun Source.orMapped(): Source = transform?.invoke(this) ?: this

    /**
     * Returns a child config containing values from specified hierarchical map.
     *
     * @param map a hierarchical map
     * @return a child config containing values from specified hierarchical map
     */
    fun hierarchical(map: Map): Config = config.withSource(MapSource(map).orMapped())

    /**
     * Returns a child config containing values from specified map in key-value format.
     *
     * @param map a map in key-value format
     * @return a child config containing values from specified map in key-value format
     */
    fun kv(map: Map): Config = config.withSource(KVSource(map).orMapped())

    /**
     * Returns a child config containing values from specified map in flat format.
     *
     * @param map a map in flat format
     * @return a child config containing values from specified map in flat format
     */
    fun flat(map: Map): Config = config.withSource(FlatSource(map).orMapped())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy