[
  {
    "path": ".gitattributes",
    "content": "# text stuff\n* text=lf\n*.bat text eol=crlf\n*.cmd text eol=crlf\n*.java text eol=lf\n*.kt text eol=lf\n*.md text eol=lf\n*.properties text eol=lf\n*.scala text eol=lf\n*.sh text eol=lf\n.gitattributes text eol=lf\n.gitignore text eol=lf\nbuild.gradle text eol=lf\ngradlew text eol=lf\ngradlew.bat text eol=crlf\ngradle/wrapper/gradle-wrapper.properties text eol=lf\n\n#binary\n*.jar binary\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Configuration for probot-stale - https://github.com/probot/stale\n\n# Number of days of inactivity before an Issue or Pull Request becomes stale\ndaysUntilStale: 90\n# Number of days of inactivity before a stale Issue or Pull Request is closed\ndaysUntilClose: false\n# Issues or Pull Requests with these labels will never be considered stale\nexemptLabels:\n- bug\n- Announcement\n- help wanted\n- To investigate\n# Label to use when marking as stale\nstaleLabel: stale\n# Comment to post when marking as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed after 30 days if no further activity\n  occurs, but feel free to re-open a closed issue if needed."
  },
  {
    "path": ".github/workflows/gradle.yml",
    "content": "name: Konf CI\n\non: [push]\n\njobs:\n  build:\n    name: Build on JDK ${{ matrix.java_version }} and ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        java_version: [8, 11, 16]\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up JDK ${{ matrix.java_version }}\n      uses: actions/setup-java@v1\n      with:\n        java-version: ${{ matrix.java_version }}\n    - name: Build with Gradle\n      run: ./gradlew build\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### Java template\n*.class\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.ear\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n### Eclipse template\n*.pydevproject\n.metadata\n.gradle\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n\n# Eclipse Core\n.project\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# CDT-specific\n.cproject\n\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n\n# Java annotation processor (APT)\n.factorypath\n\n# PDT-specific\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# TeXlipse plugin\n.texlipse\n### JetBrains template\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio\n\n*.iml\n\n## Directory-based project format:\n.idea/\n# if you remove the above rule, at least ignore the following:\n\n# User-specific stuff:\n# .idea/workspace.xml\n# .idea/tasks.xml\n# .idea/dictionaries\n\n# Sensitive or high-churn files:\n# .idea/dataSources.ids\n# .idea/dataSources.xml\n# .idea/sqlDataSources.xml\n# .idea/dynamic.xml\n# .idea/uiDesigner.xml\n\n# Gradle:\n# .idea/gradle.xml\n# .idea/libraries\n\n# Mongo Explorer plugin:\n# .idea/mongoSettings.xml\n\n## File-based project format:\n*.ipr\n*.iws\n\n## Plugin-specific files:\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\n### Windows template\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n### Vim template\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n*.un~\nSession.vim\n.netrwhist\n*~\n### SublimeText template\n# cache files for sublime text\n*.tmlanguage.cache\n*.tmPreferences.cache\n*.stTheme.cache\n\n# workspace files are user-specific\n*.sublime-workspace\n\n# project files should be checked into the repository, unless a significant\n# proportion of contributors will probably not be using SublimeText\n# *.sublime-project\n\n# sftp configuration file\nsftp-config.json\n### OSX template\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n\n\n### Gradle template\n.gradle\nbuild/\nbuild-eclipse/\n*/gradle*\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle/wrapper/gradle-wrapper.jar\n\nprivate.properties\n.vscode\n.java-version\nact\n*.log"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\n\njdk:\n  - oraclejdk11\n  - openjdk8\n  - openjdk11\n  - openjdk15\n\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n\nbefore_cache:\n  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock\n  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/\ncache:\n  directories:\n    - $HOME/.gradle/caches/\n    - $HOME/.gradle/wrapper/\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Konf\n\n[![Java 8+](https://img.shields.io/badge/Java-8+-4c7e9f.svg)](http://java.oracle.com)\n[![Maven metadata URL](https://img.shields.io/maven-central/v/com.uchuhimo/konf)](https://search.maven.org/artifact/com.uchuhimo/konf)\n[![JitPack](https://img.shields.io/jitpack/v/github/uchuhimo/konf)](https://jitpack.io/#uchuhimo/konf)\n[![Build Status](https://travis-ci.org/uchuhimo/konf.svg?branch=master)](https://travis-ci.org/uchuhimo/konf)\n[![codecov](https://codecov.io/gh/uchuhimo/konf/branch/master/graph/badge.svg)](https://codecov.io/gh/uchuhimo/konf)\n[![codebeat badge](https://codebeat.co/badges/f69a1574-9d4c-4da5-be73-56fa7b180d2d)](https://codebeat.co/projects/github-com-uchuhimo-konf-master)\n[![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)\n\nA type-safe cascading configuration library for Kotlin/Java/Android, supporting most configuration formats.\n\n## Features\n\n- **Type-safe**. Get/set value in config with type-safe APIs.\n- **Thread-safe**. All APIs for config is thread-safe.\n- **Batteries included**. Support sources from JSON, XML, YAML, [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md), [TOML](https://github.com/toml-lang/toml), properties, map, command line and system environment out of box.\n- **Cascading**. Config can fork from another config by adding a new layer on it. Each layer of config can be updated independently. This feature is powerful enough to support complicated situation such as configs with different values share common fallback config, which is automatically updated when configuration file changes.\n- **Self-documenting**. Document config item with type, default value and description when declaring.\n- **Extensible**. Easy to customize new sources for config or expose items in config.\n\n## Contents\n\n- [Konf](#konf)\n  - [Features](#features)\n  - [Contents](#contents)\n  - [Prerequisites](#prerequisites)\n  - [Use in your projects](#use-in-your-projects)\n    - [Maven](#maven)\n    - [Gradle](#gradle)\n    - [Gradle Kotlin DSL](#gradle-kotlin-dsl)\n    - [Maven (master snapshot)](#maven-master-snapshot)\n    - [Gradle (master snapshot)](#gradle-master-snapshot)\n    - [Gradle Kotlin DSL (master snapshot)](#gradle-kotlin-dsl-master-snapshot)\n  - [Quick start](#quick-start)\n  - [Define items](#define-items)\n  - [Use config](#use-config)\n    - [Create config](#create-config)\n    - [Add config spec](#add-config-spec)\n    - [Retrieve value from config](#retrieve-value-from-config)\n    - [Cast config to value](#cast-config-to-value)\n    - [Check whether an item exists in config or not](#check-whether-an-item-exists-in-config-or-not)\n    - [Modify value in config](#modify-value-in-config)\n    - [Subscribe the update event](#subscribe-the-update-event)\n    - [Export value in config as property](#export-value-in-config-as-property)\n    - [Fork from another config](#fork-from-another-config)\n  - [Load values from source](#load-values-from-source)\n    - [Subscribe the update event for load operation](#subscribe-the-update-event-for-load-operation)\n    - [Strict parsing when loading](#strict-parsing-when-loading)\n    - [Path substitution](#path-substitution)\n  - [Prefix/Merge operations for source/config/config spec](#prefixmerge-operations-for-sourceconfigconfig-spec)\n  - [Export/Reload values in config](#exportreload-values-in-config)\n  - [Supported item types](#supported-item-types)\n  - [Optional features](#optional-features)\n  - [Build from source](#build-from-source)\n  - [Breaking Changes](#breaking-changes)\n    - [v0.19.0](#v0190)\n    - [v0.17.0](#v0170)\n    - [v0.15](#v015)\n    - [v0.10](#v010)\n- [License](#license)\n\n## Prerequisites\n\n- JDK 8 or higher\n- tested on Android SDK 23 or higher\n\n## Use in your projects\n\nThis library has been published to [Maven Central](https://search.maven.org/artifact/com.uchuhimo/konf) and [JitPack](https://jitpack.io/#uchuhimo/konf).\n\nKonf is modular, you can use different modules for different sources:\n\n- `konf-core`: for built-in sources (JSON, properties, map, command line and system environment)\n- `konf-hocon`: for built-in + [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md) sources\n- `konf-toml`: for built-in + [TOML](https://github.com/toml-lang/toml) sources\n- `konf-xml`: for built-in + XML sources\n- `konf-yaml`: for built-in + YAML sources\n- `konf-git`: for built-in + Git sources\n- `konf`: for all sources mentioned above\n- `konf-js`: for built-in + JavaScript (use GraalVM JavaScript) sources\n\n### Maven\n\n```xml\n<dependency>\n  <groupId>com.uchuhimo</groupId>\n  <artifactId>konf</artifactId>\n  <version>1.1.2</version>\n</dependency>\n```\n\n### Gradle\n\n```groovy\ncompile 'com.uchuhimo:konf:1.1.2'\n```\n\n### Gradle Kotlin DSL\n\n```kotlin\ncompile(group = \"com.uchuhimo\", name = \"konf\", version = \"1.1.2\")\n```\n\n### Maven (master snapshot)\n\nAdd JitPack repository to `<repositories>` section:\n\n```xml\n<repository>\n    <id>jitpack.io</id>\n    <url>https://jitpack.io</url>\n</repository>\n```\n\nAdd dependencies:\n\n```xml\n<dependency>\n    <groupId>com.github.uchuhimo</groupId>\n    <artifactId>konf</artifactId>\n    <version>master-SNAPSHOT</version>\n</dependency>\n```\n\n### Gradle (master snapshot)\n\nAdd JitPack repository:\n\n```groovy\nrepositories {\n    maven { url 'https://jitpack.io' }\n}\n```\n\nAdd dependencies:\n\n```groovy\ncompile 'com.github.uchuhimo.konf:konf:master-SNAPSHOT'\n```\n\n### Gradle Kotlin DSL (master snapshot)\n\nAdd JitPack repository:\n\n```kotlin\nrepositories {\n    maven(url = \"https://jitpack.io\")\n}\n```\n\nAdd dependencies:\n\n```kotlin\ncompile(group = \"com.github.uchuhimo.konf\", name = \"konf\", version = \"master-SNAPSHOT\")\n```\n\n## Quick start\n\n1. Define items in config spec:\n\n    ```kotlin\n    object ServerSpec : ConfigSpec() {\n        val host by optional(\"0.0.0.0\")\n        val tcpPort by required<Int>()\n    }\n    ```\n\n2. Construct config with items in config spec and values from multiple sources:\n\n    ```kotlin\n    val config = Config { addSpec(ServerSpec) }\n            .from.yaml.file(\"server.yml\")\n            .from.json.resource(\"server.json\")\n            .from.env()\n            .from.systemProperties()\n    ```\n   \n   or:\n\n    ```kotlin\n    val config = Config { addSpec(ServerSpec) }.withSource(\n        Source.from.yaml.file(\"server.yml\") +\n        Source.from.json.resource(\"server.json\") +\n        Source.from.env() +\n        Source.from.systemProperties()\n    )\n    ```\n\n    This config contains all items defined in `ServerSpec`, and load values from 4 different sources. Values in resource file `server.json` will override those in file `server.yml`, values from system environment will override those in `server.json`, and values from system properties will override those from system environment.\n\n    If you want to watch file `server.yml` and reload values when file content is changed, you can use `watchFile` instead of `file`:\n\n    ```kotlin\n    val config = Config { addSpec(ServerSpec) }\n            .from.yaml.watchFile(\"server.yml\")\n            .from.json.resource(\"server.json\")\n            .from.env()\n            .from.systemProperties()\n    ```\n\n3. Define values in source. You can define in any of these sources:\n    - in `server.yml`:\n        ```yaml\n        server:\n            host: 0.0.0.0\n            tcp_port: 8080\n        ```\n    - in `server.json`:\n        ```json\n        {\n            \"server\": {\n                \"host\": \"0.0.0.0\",\n                \"tcp_port\": 8080\n            }\n        }\n        ```\n    - in system environment:\n        ```bash\n        SERVER_HOST=0.0.0.0\n        SERVER_TCPPORT=8080\n        ```\n    - in command line for system properties:\n        ```bash\n        -Dserver.host=0.0.0.0 -Dserver.tcp_port=8080\n        ```\n\n4. Retrieve values from config with type-safe APIs:\n    ```kotlin\n    data class Server(val host: String, val tcpPort: Int) {\n        fun start() {}\n    }\n    \n    val server = Server(config[ServerSpec.host], config[ServerSpec.tcpPort])\n    server.start()\n    ```\n\n5. Retrieve values from multiple sources without using config spec:\n\n    ```kotlin\n    val server = Config()\n            .from.yaml.file(\"server.yml\")\n            .from.json.resource(\"server.json\")\n            .from.env()\n            .from.systemProperties()\n            .at(\"server\")\n            .toValue<Server>()\n    server.start()\n    ```\n\n## Define items\n\nConfig items is declared in config spec, added to config by `Config#addSpec`. All items in same config spec have same prefix. Define a config spec with prefix `local.server`:\n\n```kotlin\nobject ServerSpec : ConfigSpec(\"server\") {\n}\n```\n\nIf the config spec is binding with single class, you can declare config spec as companion object of the class:\n\n```kotlin\nclass Server {\n    companion object : ConfigSpec(\"server\") {\n        val host by optional(\"0.0.0.0\")\n        val tcpPort by required<Int>()\n    }\n}\n```\n\nThe config spec prefix can be automatically inferred from the class name, leading to further simplification like:\n\n```kotlin\nobject ServerSpec : ConfigSpec() {\n}\n```\n\nor\n\n```kotlin\nclass Server {\n    companion object : ConfigSpec() {\n    }\n}\n```\n\nHere are some examples showing the inference convention: `Uppercase` to `uppercase`, `lowercase` to `lowercase`, `SuffixSpec` to `suffix`, `TCPService` to `tcpService`.\n\nThe config spec can also be nested. For example, the path of `Service.Backend.Login.user` in the following example will be inferred as \"service.backend.login.user\":\n\n```kotlin\nobject Service : ConfigSpec() {\n    object Backend : ConfigSpec() {\n        object Login : ConfigSpec() {\n            val user by optional(\"admin\")\n        }\n    }\n}\n```\n\nThere are three kinds of item:\n\n- Required item. Required item doesn't have default value, thus must be set with value before retrieved in config. Define a required item with description:\n    ```kotlin\n    val tcpPort by required<Int>(description = \"port of server\")\n    ```\n    Or omit the description:\n    ```kotlin\n    val tcpPort by required<Int>()\n    ```\n- Optional item. Optional item has default value, thus can be safely retrieved before setting. Define an optional item:\n    ```kotlin\n    val host by optional(\"0.0.0.0\", description = \"host IP of server\")\n    ```\n    Description can be omitted.\n- Lazy item. Lazy item also has default value, however, the default value is not a constant, it is evaluated from thunk every time when retrieved. Define a lazy item:\n    ```kotlin\n    val nextPort by lazy { config -> config[tcpPort] + 1 }\n    ```\n\nYou can also define config spec in Java, with a more verbose API (compared to Kotlin version in \"quick start\"):\n\n```java\npublic class ServerSpec {\n  public static final ConfigSpec spec = new ConfigSpec(\"server\");\n\n  public static final OptionalItem<String> host =\n      new OptionalItem<String>(spec, \"host\", \"0.0.0.0\") {};\n\n  public static final RequiredItem<Integer> tcpPort = new RequiredItem<Integer>(spec, \"tcpPort\") {};\n}\n```\n\nNotice that the `{}` part in item declaration is necessary to avoid type erasure of item's type information.\n\n## Use config\n\n### Create config\n\nCreate an empty new config:\n\n```kotlin\nval config = Config()\n```\n\nOr an new config with some initial actions:\n\n```kotlin\nval config = Config { addSpec(Server) }\n```\n\n### Add config spec\n\nAdd multiple config specs into config:\n\n```kotlin\nconfig.addSpec(Server)\nconfig.addSpec(Client)\n```\n\n### Retrieve value from config\n\nRetrieve associated value with item (type-safe API):\n\n```kotlin\nval host = config[Server.host]\n```\n\nRetrieve associated value with item name (unsafe API):\n\n```kotlin\nval host = config.get<String>(\"server.host\")\n```\n\nor:\n\n```kotlin\nval host = config<String>(\"server.host\")\n```\n\n### Cast config to value\n\nCast config to a value with the target type:\n\n```kotlin\nval server = config.toValue<Server>()\n```\n\n### Check whether an item exists in config or not\n\nCheck whether an item exists in config or not:\n\n```kotlin\nconfig.contains(Server.host)\n// or\nServer.host in config\n```\n\nCheck whether an item name exists in config or not:\n\n```kotlin\nconfig.contains(\"server.host\")\n// or\n\"server.host\" in config\n```\n\nCheck whether all values of required items exist in config or not:\n\n```kotlin\nconfig.containsRequired()\n```\n\nThrow exception if some required items in config don't have values:\n\n```kotlin\nconfig.validateRequired()\n```\n\n### Modify value in config\n\nAssociate item with value (type-safe API):\n\n```kotlin\nconfig[Server.tcpPort] = 80\n```\n\nFind item with specified name, and associate it with value (unsafe API):\n\n```kotlin\nconfig[\"server.tcpPort\"] = 80\n```\n\nDiscard associated value of item:\n\n```kotlin\nconfig.unset(Server.tcpPort)\n```\n\nDiscard associated value of item with specified name:\n\n```kotlin\nconfig.unset(\"server.tcpPort\")\n```\n\nAssociate item with lazy thunk (type-safe API):\n\n```kotlin\nconfig.lazySet(Server.tcpPort) { it[basePort] + 1 }\n```\n\nFind item with specified name, and associate it with lazy thunk (unsafe API):\n\n```kotlin\nconfig.lazySet(\"server.tcpPort\") { it[basePort] + 1 }\n```\n\n### Subscribe the update event\n\nSubscribe the update event of an item:\n\n```kotlin\nval handler = Server.host.onSet { value -> println(\"the host has changed to $value\") }\n```\n\nSubscribe the update event before every set operation:\n\n```kotlin\nval handler = Server.host.beforeSet { config, value -> println(\"the host will change to $value\") }\n```\n\nor\n\n```kotlin\nval handler = config.beforeSet { item, value -> println(\"${item.name} will change to $value\") }\n```\n\nSubscribe the update event after every set operation:\n\n```kotlin\nval handler = Server.host.afterSet { config, value -> println(\"the host has changed to $value\") }\n```\n\nor\n\n```kotlin\nval handler = config.afterSet { item, value -> println(\"${item.name} has changed to $value\") }\n```\n\nCancel the subscription:\n\n```kotlin\nhandler.cancel()\n```\n\n### Export value in config as property\n\nExport a read-write property from value in config:\n\n```kotlin\nvar port by config.property(Server.tcpPort)\nport = 9090\ncheck(port == 9090)\n```\n\nExport a read-only property from value in config:\n\n```kotlin\nval port by config.property(Server.tcpPort)\ncheck(port == 9090)\n```\n\n### Fork from another config\n\n```kotlin\nval config = Config { addSpec(Server) }\nconfig[Server.tcpPort] = 1000\n// fork from parent config\nval childConfig = config.withLayer(\"child\")\n// child config inherit values from parent config\ncheck(childConfig[Server.tcpPort] == 1000)\n// modifications in parent config affect values in child config\nconfig[Server.tcpPort] = 2000\ncheck(config[Server.tcpPort] == 2000)\ncheck(childConfig[Server.tcpPort] == 2000)\n// modifications in child config don't affect values in parent config\nchildConfig[Server.tcpPort] = 3000\ncheck(config[Server.tcpPort] == 2000)\ncheck(childConfig[Server.tcpPort] == 3000)\n```\n\n## Load values from source\n\nUse `from` to load values from source doesn't affect values in config, it will return a new child config by loading all values into new layer in child config:\n\n```kotlin\nval config = Config { addSpec(Server) }\n// values in source is loaded into new layer in child config\nval childConfig = config.from.env()\ncheck(childConfig.parent === config)\n```\n\nAll out-of-box supported sources are declared in [`DefaultLoaders`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt), shown below (the corresponding config spec for these samples is [`ConfigForLoad`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/kotlin/com/uchuhimo/konf/source/ConfigForLoad.kt)):\n\n| Type                                                         | Usage                            | Provider                                                     | Sample                                                       |\n| ------------------------------------------------------------ | -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md) | `config.from.hocon`              | [`HoconProvider`](https://github.com/uchuhimo/konf/blob/master/konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/hocon/HoconProvider.kt) | [`source.conf`](https://github.com/uchuhimo/konf/blob/master/konf-hocon/src/test/resources/source/source.conf) |\n| JSON                                                         | `config.from.json`               | [`JsonProvider`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/json/JsonProvider.kt) | [`source.json`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/resources/source/source.json) |\n| properties                                                   | `config.from.properties`         | [`PropertiesProvider`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/properties/PropertiesProvider.kt) | [`source.properties`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/resources/source/source.properties) |\n| [TOML](https://github.com/toml-lang/toml)                    | `config.from.toml`               | [`TomlProvider`](https://github.com/uchuhimo/konf/blob/master/konf-toml/src/main/kotlin/com/uchuhimo/konf/source/toml/TomlProvider.kt) | [`source.toml`](https://github.com/uchuhimo/konf/blob/master/konf-toml/src/test/resources/source/source.toml) |\n| XML                                                          | `config.from.xml`                | [`XmlProvider`](https://github.com/uchuhimo/konf/blob/master/konf-xml/src/main/kotlin/com/uchuhimo/konf/source/xml/XmlProvider.kt) | [`source.xml`](https://github.com/uchuhimo/konf/blob/master/konf-xml/src/test/resources/source/source.xml) |\n| YAML                                                         | `config.from.yaml`               | [`YamlProvider`](https://github.com/uchuhimo/konf/blob/master/konf-yaml/src/main/kotlin/com/uchuhimo/konf/source/yaml/YamlProvider.kt) | [`source.yaml`](https://github.com/uchuhimo/konf/blob/master/konf-yaml/src/test/resources/source/source.yaml) |\n| JavaScript                                                   | `config.from.js`                 | [`JsProvider`](https://github.com/uchuhimo/konf/blob/master/konf-js/src/main/kotlin/com/uchuhimo/konf/source/js/JsProvider.kt) | [`source.js`](https://github.com/uchuhimo/konf/blob/master/konf-js/src/test/resources/source/source.js) |\n| hierarchical map                                             | `config.from.map.hierarchical`   | -                                                            | [`MapSourceLoadSpec`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/MapSourceLoadSpec.kt) |\n| map in key-value format                                      | `config.from.map.kv`             | -                                                            | [`KVSourceSpec`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/KVSourceSpec.kt) |\n| map in flat format                                           | `config.from.map.flat`           | -                                                            | [`FlatSourceLoadSpec`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/FlatSourceLoadSpec.kt) |\n| system environment                                           | `config.from.env()`              | [`EnvProvider`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt) | -                                                            |\n| system properties                                            | `config.from.systemProperties()` | [`PropertiesProvider`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/properties/PropertiesProvider.kt) | -                                                            |\n\nThese sources can also be manually created using their provider, and then loaded into config by `config.withSource(source)`.\n\nAll `from` APIs also have their standalone version that return sources without loading them into the config, shown below:\n\n| Type                                                         | Usage                            |\n| ------------------------------------------------------------ | -------------------------------- |\n| [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md) | `Source.from.hocon`              |\n| JSON                                                         | `Source.from.json`               |\n| properties                                                   | `Source.from.properties`         |\n| [TOML](https://github.com/toml-lang/toml)                    | `Source.from.toml`               |\n| XML                                                          | `Source.from.xml`                |\n| YAML                                                         | `Source.from.yaml`               |\n| JavaScript                                                   | `Source.from.js`                 |\n| hierarchical map                                             | `Source.from.map.hierarchical`   |\n| map in key-value format                                      | `Source.from.map.kv`             |\n| map in flat format                                           | `Source.from.map.flat`           |\n| system environment                                           | `Source.from.env()`              |\n| system properties                                            | `Source.from.systemProperties()` |\n\nFormat of system properties source is same with that of properties source. System environment source follows the same mapping convention with properties source, but with the following name convention:\n\n- All letters in name are in uppercase\n- `.` in name is replaced with `_`\n\nHOCON/JSON/properties/TOML/XML/YAML/JavaScript source can be loaded from a variety of input format. Use properties source as example:\n\n- From file: `config.from.properties.file(\"/path/to/file\")`\n- From watched file: `config.from.properties.watchFile(\"/path/to/file\", 100, TimeUnit.MILLISECONDS)`\n  - You can re-trigger the setup process every time the updated file is loaded by `watchFile(\"/path/to/file\") { config, source -> setup(config) }`\n- From string: `config.from.properties.string(\"server.port = 8080\")`\n- From URL: `config.from.properties.url(\"http://localhost:8080/source.properties\")`\n- From watched URL: `config.from.properties.watchUrl(\"http://localhost:8080/source.properties\", 1, TimeUnit.MINUTES)`\n  - You can re-trigger the setup process every time the URL is loaded by `watchUrl(\"http://localhost:8080/source.properties\") { config, source -> setup(config) }`\n- From Git repository: `config.from.properties.git(\"https://github.com/uchuhimo/konf.git\", \"/path/to/source.properties\", branch = \"dev\")`\n- From watched Git repository: `config.from.properties.watchGit(\"https://github.com/uchuhimo/konf.git\", \"/path/to/source.properties\", period = 1, unit = TimeUnit.MINUTES)`\n  - You can re-trigger the setup process every time the Git file is loaded by `watchGit(\"https://github.com/uchuhimo/konf.git\", \"/path/to/source.properties\") { config, source -> setup(config) }`\n- From resource: `config.from.properties.resource(\"source.properties\")`\n- From reader: `config.from.properties.reader(reader)`\n- From input stream: `config.from.properties.inputStream(inputStream)`\n- From byte array: `config.from.properties.bytes(bytes)`\n- From portion of byte array: `config.from.properties.bytes(bytes, 1, 12)`\n\nIf source is from file, file extension can be auto detected. Thus, you can use `config.from.file(\"/path/to/source.json\")` instead of `config.from.json.file(\"/path/to/source.json\")`, or use `config.from.watchFile(\"/path/to/source.json\")` instead of `config.from.json.watchFile(\"/path/to/source.json\")`. Source from URL also support auto-detected extension (use `config.from.url` or `config.from.watchUrl`). The following file extensions can be supported:\n\n| Type       | Extension  |\n| ---------- | ---------- |\n| HOCON      | conf       |\n| JSON       | json       |\n| Properties | properties |\n| TOML       | toml       |\n| XML        | xml        |\n| YAML       | yml, yaml  |\n| JavaScript | js         |\n\nYou can also implement [`Source`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt) to customize your new source, which can be loaded into config by `config.withSource(source)`.\n\n### Subscribe the update event for load operation\n\nSubscribe the update event before every load operation:\n\n```kotlin\nval handler = config.beforeLoad { source -> println(\"$source will be loaded\") }\n```\n\nYou can re-trigger the setup process by subscribing the update event after every load operation:\n\n```kotlin\nval handler = config.afterLoad { source -> setup(config) }\n```\n\nCancel the subscription:\n\n```kotlin\nhandler.cancel()\n```\n\n### Strict parsing when loading\n\nBy default, Konf extracts desired paths from sources and ignores other unknown paths in sources. If you want Konf to throws exception when unknown paths are found, you can enable `FAIL_ON_UNKNOWN_PATH` feature:\n\n```kotlin\nconfig.enable(Feature.FAIL_ON_UNKNOWN_PATH)\n    .from.properties.file(\"server.properties\")\n    .from.json.resource(\"server.json\")\n```\n\nThen `config` will validate paths from both the properties file and the JSON resource. Furthermore, If you want to validate the properties file only, you can use:\n\n```kotlin\nconfig.from.enable(Feature.FAIL_ON_UNKNOWN_PATH).properties.file(\"/path/to/file\")\n    .from.json.resource(\"server.json\")\n```\n\n### Path substitution\n\nPath substitution is a feature that path references in source will be substituted by their values.\n\nPath substitution rules are shown below:\n\n- Only quoted string value will be substituted. It means that Konf's path substitutions will not conflict with HOCON's substitutions.\n- The definition of a path variable is `${path}`, e.g., `${java.version}`.\n- The path variable is resolved in the context of the current source.\n- If the string value only contains the path variable, it will be replaced by the whole sub-tree in the path; otherwise, it will be replaced by the string value in the path.\n- Use `${path:-default}` to provide a default value when the path is unresolved, e.g., `${java.version:-8}`.\n- Use `$${path}` to escape the path variable, e.g., `$${java.version}` will be resolved to `${java.version}` instead of the value in `java.version`.\n- Path substitution works in a recursive way, so nested path variables like `${jre-${java.specification.version}}` are allowed.\n- Konf also supports all key prefix of [StringSubstitutor](https://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/StringSubstitutor.html)'s default interpolator. \n\nBy default, Konf will perform path substitution for every source (except system environment source) when loading them into the config.\nYou can disable this behaviour by using `config.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)` for the config \nor `source.disabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)` for a single source.\n\nBy default, Konf will throw exception when some path variables are unresolved. You can use `source.substituted(false)` manually to ignore these unresolved variables.\n\nTo resolve path variables refer to other sources, you can merge these sources before loading them into the config.\nFor example, if we have two sources `source1.json` and `source2.properties`,\n`source1.json` is:\n\n```json\n{ \n  \"base\" : { \"user\" : \"konf\" , \"password\" : \"passwd\" }\n}\n```\n\n`source2.properties` is:\n\n```properties\nconnection.jdbc=mysql://${base.user}:${base.password}@server:port\n```\n\nuse:\n\n```kotlin\nconfig.withSource(\n    Source.from.file(\"source1.json\") +\n    Source.from.file(\"source2.properties\")\n)\n```\n\nWe can resolve `mysql://${base.user}:${base.password}@server:port` as `mysql://konf:passwd@server:port`.\n\n## Prefix/Merge operations for source/config/config spec\n\nAll of source/config/config spec support add prefix operation, remove prefix operation and merge operation as shown below:\n\n| Type     | Add Prefix                                                   | Remove Prefix                                               | Merge                                                  |\n| -------- | ------------------------------------------------------------ | ----------------------------------------------------------- | ------------------------------------------------------ |\n| `Source` | `source.withPrefix(prefix)` or `Prefix(prefix) + source` or `config.from.prefixed(prefix).file(file)` | `source[prefix]` or `config.from.scoped(prefix).file(file)` | `fallback + facade` or `facade.withFallback(fallback)` |\n| `Config` | `config.withPrefix(prefix)` or `Prefix(prefix) + config`     | `config.at(prefix)`                                         | `fallback + facade` or `facade.withFallback(fallback)` |\n| `Spec`   | `spec.withPrefix(prefix)` or `Prefix(prefix) + spec`         | `spec[prefix]`                                              | `fallback + facade` or `facade.withFallback(fallback)` |\n\n## Export/Reload values in config\n\nExport all values in config as a tree:\n\n```kotlin\nval tree = config.toTree()\n```\n\nExport all values in config to map in key-value format:\n\n```kotlin\nval map = config.toMap()\n```\n\nExport all values in config to hierarchical map:\n\n```kotlin\nval map = config.toHierarchicalMap()\n```\n\nExport all values in config to map in flat format:\n\n```kotlin\nval map = config.toFlatMap()\n```\n\nExport all values in config to JSON:\n\n```kotlin\nval file = createTempFile(suffix = \".json\")\nconfig.toJson.toFile(file)\n```\n\nReload values from JSON:\n\n```kotlin\nval newConfig = Config {\n    addSpec(Server)\n}.from.json.file(file)\ncheck(config == newConfig)\n```\n\nConfig can be saved to a variety of output format in HOCON/JSON/properties/TOML/XML/YAML/JavaScript. Use JSON as example:\n\n- To file: `config.toJson.toFile(\"/path/to/file\")`\n- To string: `config.toJson.toText()`\n- To writer: `config.toJson.toWriter(writer)`\n- To output stream: `config.toJson.toOutputStream(outputStream)`\n- To byte array: `config.toJson.toBytes()`\n\nYou can also implement [`Writer`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Writer.kt) to customize your new writer (see [`JsonWriter`](https://github.com/uchuhimo/konf/blob/master/konf-core/src/main/kotlin/com/uchuhimo/konf/source/json/JsonWriter.kt) for how to integrate your writer with config).\n\n## Supported item types\n\nSupported item types include:\n\n- All primitive types\n- All primitive array types\n- `BigInteger`\n- `BigDecimal`\n- `String`\n- Date and Time\n    - `java.util.Date`\n    - `OffsetTime`\n    - `OffsetDateTime`\n    - `ZonedDateTime`\n    - `LocalDate`\n    - `LocalTime`\n    - `LocalDateTime`\n    - `Year`\n    - `YearMonth`\n    - `Instant`\n    - `Duration`\n- `SizeInBytes`\n- Enum\n- Array\n- Collection\n    - `List`\n    - `Set`\n    - `SortedSet`\n    - `Map`\n    - `SortedMap`\n- Kotlin Built-in classes\n    - `Pair`\n    - `Triple`\n    - `IntRange`\n    - `CharRange`\n    - `LongRange`\n- Data classes\n- POJOs supported by Jackson core modules\n\nKonf supports size in bytes format described in [HOCON document](https://github.com/typesafehub/config/blob/master/HOCON.md#size-in-bytes-format) with class `SizeInBytes`.\n\nKonf supports both [ISO-8601 duration format](https://en.wikipedia.org/wiki/ISO_8601#Durations) and [HOCON duration format](https://github.com/typesafehub/config/blob/master/HOCON.md#duration-format) for `Duration`.\n\nKonf uses [Jackson](https://github.com/FasterXML/jackson) to support Kotlin Built-in classes, Data classes and POJOs. You can use `config.mapper` to access `ObjectMapper` instance used by config, and configure it to support more types from third-party Jackson modules. Default modules registered by Konf include:\n\n- Jackson core modules\n- `JavaTimeModule` in [jackson-modules-java8](https://github.com/FasterXML/jackson-modules-java8)\n- [jackson-module-kotlin](https://github.com/FasterXML/jackson-module-kotlin)\n\n## Optional features\n\nThere are some optional features that you can enable/disable in the config scope or the source scope by `Config#enable(Feature)`/`Config#disable(Feature)` or `Source#enabled(Feature)`/`Source#disable(Feature)`. You can use `Config#isEnabled()` or `Source#isEnabled()` to check whether a feature is enabled.\n\nThese features include:\n\n- `FAIL_ON_UNKNOWN_PATH`: feature that determines what happens when unknown paths appear in the source. If enabled, an exception is thrown when loading from the source to indicate it contains unknown paths. This feature is disabled by default.\n- `LOAD_KEYS_CASE_INSENSITIVELY`: feature that determines whether loading keys from sources case-insensitively. This feature is disabled by default except for system environment.\n- `LOAD_KEYS_AS_LITTLE_CAMEL_CASE`: feature that determines whether loading keys from sources as little camel case. This feature is enabled by default.\n- `OPTIONAL_SOURCE_BY_DEFAULT`: feature that determines whether sources are optional by default. This feature is disabled by default.\n- `SUBSTITUTE_SOURCE_BEFORE_LOADED`: feature that determines whether sources should be substituted before loaded into config. This feature is enabled by default.\n\n## Build from source\n\nBuild library with Gradle using the following command:\n\n```\n./gradlew clean assemble\n```\n\nTest library with Gradle using the following command:\n\n```\n./gradlew clean test\n```\n\nSince Gradle has excellent incremental build support, you can usually omit executing the `clean` task.\n\nInstall library in a local Maven repository for consumption in other projects via the following command:\n\n```\n./gradlew clean install\n```\n\n## Breaking Changes\n\n### v0.19.0\n\nSince all sources are substituted before loaded into config by default, all path variables will be substituted now. You can use `config.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)` to disable this change.\n\n### v0.17.0\n\nAfter migrated to tree-based source APIs, many deprecated APIs are removed, including:\n\n- `Source`: all `isXXX` and `toXXX` APIs\n- `Config`: `layer`, `addSource` and `withSourceFrom`\n\n### v0.15\n\nAfter modularized Konf, `hocon`/`toml`/`xml`/`yaml`/`git`/`watchGit` in `DefaultLoaders` become extension properties/functions and should be imported explicitly.\nFor example, you should import `com.uchuhimo.konf.source.hocon` before using `config.from.hocon`; in Java, `config.from().hocon` is unavailable, please use `config.from().source(HoconProvider.INSTANCE)` instead.\n\nIf you use JitPack, you should use `com.github.uchuhimo.konf:konf:<version>` instead of `com.github.uchuhimo:konf:<version>` now.\n\n### v0.10\n\nAPIs in `ConfigSpec` have been updated to support item name's auto-detection, please migrate to new APIs. Here are some examples:\n\n- `val host = optional(\"host\", \"0.0.0.0\")` to `val host by optional(\"0.0.0.0\")`\n- `val port = required<Int>(\"port\")` to `val port by required<Int>()`\n- `val nextPort = lazy(\"nextPort\") { config -> config[port] + 1 }` to `val nextPort by lazy { config -> config[port] + 1 }`\n\n# License\n\n© uchuhimo, 2017-2019. Licensed under an [Apache 2.0](./LICENSE) license.\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\nimport java.net.URL\nimport java.util.Properties\n\nval ossUserToken by extra { getPrivateProperty(\"ossUserToken\") }\nval ossUserPassword by extra { getPrivateProperty(\"ossUserPassword\") }\nval signPublications by extra { getPrivateProperty(\"signPublications\") }\nval useAliyun by extra { shouldUseAliyun() }\n\ntasks.named<Wrapper>(\"wrapper\") {\n    gradleVersion = \"7.0\"\n    distributionType = Wrapper.DistributionType.ALL\n}\n\nbuildscript {\n    repositories {\n        if (shouldUseAliyun()) {\n            aliyunMaven()\n        } else {\n            mavenCentral()\n        }\n    }\n}\n\nplugins {\n    java\n    `java-test-fixtures`\n    jacoco\n    `maven-publish`\n    signing\n    kotlin(\"jvm\") version Versions.kotlin\n    kotlin(\"plugin.allopen\") version Versions.kotlin\n    id(\"com.dorongold.task-tree\") version Versions.taskTree\n    id(\"me.champeau.gradle.jmh\") version Versions.jmhPlugin\n    id(\"com.diffplug.spotless\") version Versions.spotless\n    id(\"com.github.ben-manes.versions\") version Versions.dependencyUpdate\n    id(\"org.jetbrains.dokka\") version Versions.dokka\n}\n\nallprojects {\n    apply(plugin = \"java\")\n    apply(plugin = \"java-test-fixtures\")\n    apply(plugin = \"jacoco\")\n    apply(plugin = \"maven-publish\")\n    apply(plugin = \"signing\")\n    apply(plugin = \"org.jetbrains.kotlin.jvm\")\n    apply(plugin = \"kotlin-allopen\")\n    apply(plugin = \"com.dorongold.task-tree\")\n    apply(plugin = \"me.champeau.gradle.jmh\")\n    apply(plugin = \"com.diffplug.spotless\")\n    apply(plugin = \"com.github.ben-manes.versions\")\n    apply(plugin = \"org.jetbrains.dokka\")\n\n    group = \"com.uchuhimo\"\n    version = \"1.1.2\"\n\n    repositories {\n        if (useAliyun) {\n            aliyunMaven()\n        } else {\n            mavenCentral()\n        }\n        maven {\n            url=uri(\"https://kotlin.bintray.com/kotlinx\")\n        }\n    }\n\n    val dependencyUpdates by tasks.existing(DependencyUpdatesTask::class)\n    dependencyUpdates {\n        revision = \"release\"\n        outputFormatter = \"plain\"\n        resolutionStrategy {\n            componentSelection {\n                all {\n                    val rejected = listOf(\"alpha\", \"beta\", \"rc\", \"cr\", \"m\", \"preview\", \"b\", \"ea\", \"eap\", \"pr\", \"dev\", \"mt\")\n                        .map { qualifier -> Regex(\"(?i).*[.-]$qualifier[.\\\\d-+]*\") }\n                        .any { it.matches(candidate.version) }\n                    if (rejected) {\n                        reject(\"Release candidate\")\n                    }\n                }\n            }\n        }\n    }\n}\n\nsubprojects {\n    configurations.testFixturesImplementation.get().extendsFrom(configurations.implementation.get())\n    configurations.testImplementation.get().extendsFrom(configurations.testFixturesImplementation.get())\n\n    dependencies {\n        api(kotlin(\"stdlib-jdk8\", Versions.kotlin))\n        api(\"org.jetbrains.kotlinx\", \"kotlinx-coroutines-core\", Versions.coroutines)\n        implementation(kotlin(\"reflect\", Versions.kotlin))\n        implementation(\"org.reflections\", \"reflections\", Versions.reflections)\n        implementation(\"org.apache.commons\", \"commons-text\", Versions.commonsText)\n        arrayOf(\"core\", \"annotations\", \"databind\").forEach { name ->\n            api(jacksonCore(name, Versions.jackson))\n        }\n        implementation(jackson(\"module\", \"kotlin\", Versions.jackson))\n        implementation(jackson(\"datatype\", \"jsr310\", Versions.jackson))\n\n        testFixturesImplementation(kotlin(\"test\", Versions.kotlin))\n        testFixturesImplementation(\"com.natpryce\", \"hamkrest\", Versions.hamkrest)\n        testFixturesImplementation(\"org.hamcrest\", \"hamcrest-all\", Versions.hamcrest)\n        testImplementation(junit(\"jupiter\", \"api\", Versions.junit))\n        testImplementation(\"com.sparkjava\", \"spark-core\", Versions.spark)\n        arrayOf(\"api\", \"data-driven-extension\", \"subject-extension\").forEach { name ->\n            testFixturesImplementation(spek(name, Versions.spek))\n        }\n\n        testRuntimeOnly(junit(\"platform\", \"launcher\", Versions.junitPlatform))\n        testRuntimeOnly(junit(\"jupiter\", \"engine\", Versions.junit))\n        testRuntimeOnly(spek(\"junit-platform-engine\", Versions.spek))\n        testRuntimeOnly(\"org.slf4j\", \"slf4j-simple\", Versions.slf4j)\n    }\n\n    java {\n        sourceCompatibility = Versions.java\n        targetCompatibility = Versions.java\n    }\n\n    val test by tasks.existing(Test::class)\n    test {\n        useJUnitPlatform()\n        testLogging.apply {\n            showStandardStreams = true\n            showExceptions = true\n            showCauses = true\n            showStackTraces = true\n        }\n        systemProperties[\"org.slf4j.simpleLogger.defaultLogLevel\"] = \"warn\"\n        systemProperties[\"junit.jupiter.execution.parallel.enabled\"] = true\n        systemProperties[\"junit.jupiter.execution.parallel.mode.default\"] = \"concurrent\"\n        maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).takeIf { it > 0 } ?: 1\n        val properties = Properties()\n        properties.load(rootProject.file(\"konf-core/src/test/kotlin/com/uchuhimo/konf/source/env/env.properties\").inputStream())\n        properties.forEach { key, value ->\n            environment(key as String, value)\n        }\n    }\n\n    tasks.withType<JavaCompile> {\n        options.encoding = \"UTF-8\"\n    }\n\n    tasks.withType<KotlinCompile> {\n        kotlinOptions {\n            jvmTarget = Versions.java.toString()\n            apiVersion = Versions.kotlinApi\n            languageVersion = Versions.kotlinLanguage\n        }\n    }\n\n    allOpen {\n        annotation(\"org.openjdk.jmh.annotations.BenchmarkMode\")\n        annotation(\"org.openjdk.jmh.annotations.State\")\n    }\n\n    jmh {\n        //jvmArgs = [\"-Djmh.separateClasspathJAR=true\"]\n        iterations = 10 // Number of measurement iterations to do.\n        //benchmarkMode = [\"thrpt\"] // Benchmark mode. Available modes are: [Throughput/thrpt, AverageTime/avgt, SampleTime/sample, SingleShotTime/ss, All/all]\n        batchSize = 1\n        // Batch size: number of benchmark method calls per operation. (some benchmark modes can ignore this setting)\n        fork = 1 // How many times to forks a single benchmark. Use 0 to disable forking altogether\n        //operationsPerInvocation = 1 // Operations per invocation.\n        timeOnIteration = \"1s\" // Time to spend at each measurement iteration.\n        threads = 4 // Number of worker threads to run with.\n        timeout = \"10s\" // Timeout for benchmark iteration.\n        //timeUnit = \"ns\" // Output time unit. Available time units are: [m, s, ms, us, ns].\n        verbosity = \"NORMAL\" // Verbosity mode. Available modes are: [SILENT, NORMAL, EXTRA]\n        warmup = \"1s\" // Time to spend at each warmup iteration.\n        warmupBatchSize = 1 // Warmup batch size: number of benchmark method calls per operation.\n        //warmupForks = 0 // How many warmup forks to make for a single benchmark. 0 to disable warmup forks.\n        warmupIterations = 10 // Number of warmup iterations to do.\n        isZip64 = false // Use ZIP64 format for bigger archives\n        jmhVersion = Versions.jmh // Specifies JMH version\n    }\n\n    spotless {\n        java {\n            googleJavaFormat(Versions.googleJavaFormat)\n            trimTrailingWhitespace()\n            endWithNewline()\n            licenseHeaderFile(rootProject.file(\"config/spotless/apache-license-2.0.java\"))\n        }\n        kotlin {\n            ktlint(Versions.ktlint)\n            trimTrailingWhitespace()\n            endWithNewline()\n            licenseHeaderFile(rootProject.file(\"config/spotless/apache-license-2.0.kt\"))\n        }\n    }\n\n    jacoco {\n        toolVersion = Versions.jacoco\n    }\n\n    val jacocoTestReport by tasks.existing(JacocoReport::class) {\n        reports {\n            xml.isEnabled = true\n            html.isEnabled = true\n        }\n    }\n\n    val check by tasks.existing {\n        dependsOn(jacocoTestReport)\n    }\n\n    tasks.dokkaHtml {\n        outputDirectory.set(tasks.javadoc.get().destinationDir)\n        dokkaSourceSets {\n            configureEach {\n                jdkVersion.set(9)\n                reportUndocumented.set(false)\n                sourceLink {\n                    localDirectory.set(file(\"./\"))\n                    remoteUrl.set(URL(\"https://github.com/uchuhimo/konf/blob/v${project.version}/\"))\n                    remoteLineSuffix.set(\"#L\")\n                }\n            }\n        }\n    }\n\n    val sourcesJar by tasks.registering(Jar::class) {\n        archiveClassifier.set(\"sources\")\n        from(sourceSets.main.get().allSource)\n    }\n\n    val javadocJar by tasks.registering(Jar::class) {\n        archiveClassifier.set(\"javadoc\")\n        from(tasks.dokkaHtml)\n    }\n\n    val projectDescription = \"A type-safe cascading configuration library for Kotlin/Java, \" +\n        \"supporting most configuration formats\"\n    val projectGroup = project.group as String\n    val projectName = if (project.name == \"konf-all\") \"konf\" else project.name\n    val projectVersion = project.version as String\n    val projectUrl = \"https://github.com/uchuhimo/konf\"\n\n    publishing {\n        publications {\n            create<MavenPublication>(\"maven\") {\n                from(components[\"java\"])\n                artifact(sourcesJar.get())\n                artifact(javadocJar.get())\n\n                groupId = projectGroup\n                artifactId = projectName\n                version = projectVersion\n\n                suppressPomMetadataWarningsFor(\"testFixturesApiElements\")\n                suppressPomMetadataWarningsFor(\"testFixturesRuntimeElements\")\n                pom {\n                    name.set(rootProject.name)\n                    description.set(projectDescription)\n                    url.set(projectUrl)\n                    licenses {\n                        license {\n                            name.set(\"The Apache Software License, Version 2.0\")\n                            url.set(\"http://www.apache.org/licenses/LICENSE-2.0.txt\")\n                        }\n                    }\n                    developers {\n                        developer {\n                            id.set(\"uchuhimo\")\n                            name.set(\"uchuhimo\")\n                            email.set(\"uchuhimo@outlook.com\")\n                        }\n                    }\n                    scm {\n                        url.set(projectUrl)\n                    }\n                }\n            }\n        }\n        repositories {\n            maven {\n                url = uri(\"https://oss.sonatype.org/service/local/staging/deploy/maven2\")\n                credentials {\n                    username = ossUserToken\n                    password = ossUserPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        setRequired({ signPublications == \"true\" })\n        sign(publishing.publications[\"maven\"])\n    }\n\n    tasks {\n        val install by registering\n        afterEvaluate {\n            val publishToMavenLocal by existing\n            val publish by existing\n            install.configure { dependsOn(publishToMavenLocal) }\n            publish { dependsOn(check, install) }\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "content": "plugins {\n    `kotlin-dsl`\n}\n\nrepositories {\n    mavenCentral()\n}"
  },
  {
    "path": "buildSrc/settings.gradle.kts",
    "content": ""
  },
  {
    "path": "buildSrc/src/main/kotlin/Dependencies.kt",
    "content": "import org.gradle.api.JavaVersion\n\nobject Versions {\n    val java = JavaVersion.VERSION_1_8\n    const val commonsText = \"1.9\"\n    const val coroutines = \"1.4.3\"\n    const val dependencyUpdate = \"0.38.0\"\n    const val dokka = \"1.4.30\"\n    const val dom4j = \"2.1.3\"\n    const val graal = \"21.0.0.2\"\n    const val hamcrest = \"1.3\"\n    const val hamkrest = \"1.8.0.1\"\n    const val hocon = \"1.4.1\"\n    const val jacksonMinor = \"2.12\"\n    const val jackson = \"$jacksonMinor.2\"\n    const val jacoco = \"0.8.6\"\n    const val jaxen = \"1.2.0\"\n    const val jgit = \"5.11.0.202103091610-r\"\n    const val jmh = \"1.25.2\"\n    const val jmhPlugin = \"0.5.3\"\n    const val junit = \"5.7.1\"\n    const val junitPlatform = \"1.7.1\"\n    const val kotlin = \"1.4.32\"\n    const val kotlinApi = \"1.4\"\n    const val kotlinLanguage = \"1.4\"\n    const val reflections = \"0.9.12\"\n    const val slf4j = \"1.7.30\"\n    const val spark = \"2.9.3\"\n    const val spek = \"1.1.5\"\n    const val spotless = \"5.11.1\"\n    const val taskTree = \"1.5\"\n    const val toml4j = \"0.7.2\"\n    const val yaml = \"1.28\"\n\n    // Since 1.8, the minimum supported runtime version is JDK 11.\n    const val googleJavaFormat = \"1.7\"\n    const val ktlint = \"0.41.0\"\n}\n\n\nfun String?.withColon() = this?.let { \":$this\" } ?: \"\"\n\nfun kotlin(module: String, version: String? = null) =\n    \"org.jetbrains.kotlin:kotlin-$module${version.withColon()}\"\n\nfun spek(module: String, version: String? = null) =\n    \"org.jetbrains.spek:spek-$module${version.withColon()}\"\n\nfun jackson(scope: String, module: String, version: String? = null) =\n    \"com.fasterxml.jackson.$scope:jackson-$scope-$module${version.withColon()}\"\n\nfun jacksonCore(module: String = \"core\", version: String? = null) =\n    \"com.fasterxml.jackson.core:jackson-$module${version.withColon()}\"\n\nfun junit(scope: String, module: String, version: String? = null) =\n    \"org.junit.$scope:junit-$scope-$module${version.withColon()}\"\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/Utils.kt",
    "content": "import org.gradle.api.Project\nimport org.gradle.api.artifacts.dsl.RepositoryHandler\nimport org.gradle.kotlin.dsl.maven\nimport org.w3c.dom.Element\nimport java.util.Properties\n\nfun Project.getPrivateProperty(key: String): String {\n    return if (file(\"private.properties\").exists()) {\n        val properties = Properties()\n        properties.load(file(\"private.properties\").inputStream())\n        properties.getProperty(key)\n    } else {\n        \"\"\n    }\n}\n\nfun Project.shouldUseAliyun(): Boolean = if (file(\"private.properties\").exists()) {\n    val properties = Properties()\n    properties.load(file(\"private.properties\").inputStream())\n    properties.getProperty(\"useAliyun\")?.toBoolean() ?: false\n} else {\n    false\n}\n\nfun RepositoryHandler.aliyunMaven() = maven(url = \"https://maven.aliyun.com/repository/central\")\n\nfun RepositoryHandler.aliyunGradlePluginPortal() = maven(url = \"https://maven.aliyun.com/repository/gradle-plugin\")\n\nfun Element.appendNode(key: String, action: Element.() -> Unit): Element {\n    return apply {\n        appendChild(ownerDocument.createElement(key).apply {\n            action()\n        })\n    }\n}\n\nfun Element.appendNode(key: String, value: String): Element {\n    return appendNode(key) {\n        appendChild(ownerDocument.createTextNode(value))\n    }\n}\n"
  },
  {
    "path": "config/spotless/apache-license-2.0.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n"
  },
  {
    "path": "config/spotless/apache-license-2.0.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.0-all.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en_US\norg.gradle.caching=true\norg.gradle.vfs.watch=true\n#org.gradle.parallel=true\n#org.gradle.configureondemand=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "konf-all/build.gradle.kts",
    "content": "sourceSets {\n    register(\"snippet\")\n}\n\nval snippetImplementation by configurations\nsnippetImplementation.extendsFrom(configurations.implementation.get())\n\ndependencies {\n    for (name in listOf(\n        \":konf-core\",\n        \":konf-git\",\n        \":konf-hocon\",\n        \":konf-toml\",\n        \":konf-xml\",\n        \":konf-yaml\"\n    )) {\n        api(project(name))\n        testImplementation(testFixtures(project(name)))\n    }\n\n    snippetImplementation(sourceSets.main.get().output)\n    val snippet by sourceSets\n    testImplementation(snippet.output)\n}\n"
  },
  {
    "path": "konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerInJava.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet;\n\nimport com.uchuhimo.konf.Config;\n\npublic class ServerInJava {\n  private String host;\n  private Integer tcpPort;\n\n  public ServerInJava(String host, Integer tcpPort) {\n    this.host = host;\n    this.tcpPort = tcpPort;\n  }\n\n  public ServerInJava(Config config) {\n    this(config.get(ServerSpecInJava.host), config.get(ServerSpecInJava.tcpPort));\n  }\n\n  public String getHost() {\n    return host;\n  }\n\n  public Integer getTcpPort() {\n    return tcpPort;\n  }\n}\n"
  },
  {
    "path": "konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerSpecInJava.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet;\n\nimport com.uchuhimo.konf.ConfigSpec;\nimport com.uchuhimo.konf.OptionalItem;\nimport com.uchuhimo.konf.RequiredItem;\n\npublic class ServerSpecInJava {\n  public static final ConfigSpec spec = new ConfigSpec(\"server\");\n\n  public static final OptionalItem<String> host =\n      new OptionalItem<String>(spec, \"host\", \"0.0.0.0\") {};\n\n  public static final RequiredItem<Integer> tcpPort = new RequiredItem<Integer>(spec, \"tcpPort\") {};\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Config.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\n\nfun main(args: Array<String>) {\n    val config = Config()\n    config.addSpec(Server)\n    run {\n        val host = config[Server.host]\n    }\n    run {\n        val host = config.get<String>(\"server.host\")\n    }\n    run {\n        val host = config<String>(\"server.host\")\n    }\n    config.contains(Server.host)\n    // or\n    Server.host in config\n    config.contains(\"server.host\")\n    // or\n    \"server.host\" in config\n    config[Server.tcpPort] = 80\n    config[\"server.tcpPort\"] = 80\n    config.containsRequired()\n    config.validateRequired()\n    config.unset(Server.tcpPort)\n    config.unset(\"server.tcpPort\")\n    val basePort by ConfigSpec(\"server\").required<Int>()\n    config.lazySet(Server.tcpPort) { it[basePort] + 1 }\n    config.lazySet(\"server.tcpPort\") { it[basePort] + 1 }\n    run {\n        val handler = Server.host.onSet { value -> println(\"the host has changed to $value\") }\n        handler.cancel()\n    }\n    run {\n        val handler = Server.host.beforeSet { config, value -> println(\"the host will change to $value\") }\n        handler.cancel()\n    }\n    run {\n        val handler = config.beforeSet { item, value -> println(\"${item.name} will change to $value\") }\n        handler.cancel()\n    }\n    run {\n        val handler = Server.host.afterSet { config, value -> println(\"the host has changed to $value\") }\n        handler.cancel()\n    }\n    run {\n        val handler = config.afterSet { item, value -> println(\"${item.name} has changed to $value\") }\n        handler.cancel()\n    }\n    run {\n        var port by config.property(Server.tcpPort)\n        port = 9090\n        check(port == 9090)\n    }\n    run {\n        val port by config.property(Server.tcpPort)\n        check(port == 9090)\n    }\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Export.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.base.toFlatMap\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport com.uchuhimo.konf.source.json.toJson\nimport com.uchuhimo.konf.tempFile\n\nfun main(args: Array<String>) {\n    val config = Config { addSpec(Server) }\n    config[Server.tcpPort] = 1000\n    run {\n        val map = config.toMap()\n    }\n    run {\n        val map = config.toHierarchicalMap()\n    }\n    run {\n        val map = config.toFlatMap()\n    }\n    val file = tempFile(suffix = \".json\")\n    config.toJson.toFile(file)\n    val newConfig = Config {\n        addSpec(Server)\n    }.from.json.file(file)\n    check(config.toMap() == newConfig.toMap())\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Fork.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\n\nfun main(args: Array<String>) {\n    val config = Config { addSpec(Server) }\n    config[Server.tcpPort] = 1000\n    // fork from parent config\n    val childConfig = config.withLayer(\"child\")\n    // child config inherit values from parent config\n    check(childConfig[Server.tcpPort] == 1000)\n    // modifications in parent config affect values in child config\n    config[Server.tcpPort] = 2000\n    check(config[Server.tcpPort] == 2000)\n    check(childConfig[Server.tcpPort] == 2000)\n    // modifications in child config don't affect values in parent config\n    childConfig[Server.tcpPort] = 3000\n    check(config[Server.tcpPort] == 2000)\n    check(childConfig[Server.tcpPort] == 3000)\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Load.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\n\nfun main(args: Array<String>) {\n    val config = Config { addSpec(Server) }\n    // values in source is loaded into new layer in child config\n    val childConfig = config.from.env()\n    check(childConfig.parent === config)\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/QuickStart.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.toValue\nimport com.uchuhimo.konf.source.yaml\nimport com.uchuhimo.konf.toValue\nimport java.io.File\n\nobject ServerSpec : ConfigSpec() {\n    val host by optional(\"0.0.0.0\")\n    val tcpPort by required<Int>()\n}\n\nfun main(args: Array<String>) {\n    val file = File(\"server.yml\")\n    //language=YAML\n    file.writeText(\n        \"\"\"\n        server:\n            host: 127.0.0.1\n            tcp_port: 8080\n        \"\"\".trimIndent()\n    )\n    file.deleteOnExit()\n    val config = Config { addSpec(ServerSpec) }\n        .from.yaml.file(\"server.yml\")\n        .from.json.resource(\"server.json\")\n        .from.env()\n        .from.systemProperties()\n    run {\n        val config = Config { addSpec(ServerSpec) }.withSource(\n            Source.from.yaml.file(\"server.yml\") +\n                Source.from.json.resource(\"server.json\") +\n                Source.from.env() +\n                Source.from.systemProperties()\n        )\n    }\n    run {\n        val config = Config { addSpec(ServerSpec) }\n            .from.yaml.watchFile(\"server.yml\")\n            .from.json.resource(\"server.json\")\n            .from.env()\n            .from.systemProperties()\n    }\n    val server = Server(config[ServerSpec.host], config[ServerSpec.tcpPort])\n    server.start()\n    run {\n        val server = Config()\n            .from.yaml.file(\"server.yml\")\n            .from.json.resource(\"server.json\")\n            .from.env()\n            .from.systemProperties()\n            .at(\"server\")\n            .toValue<Server>()\n        server.start()\n    }\n    run {\n        val server = (\n            Source.from.yaml.file(\"server.yml\") +\n                Source.from.json.resource(\"server.json\") +\n                Source.from.env() +\n                Source.from.systemProperties()\n            )[\"server\"]\n            .toValue<Server>()\n        server.start()\n    }\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Serialize.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFile\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\n\nfun main(args: Array<String>) {\n    val config = Config { addSpec(Server) }\n    config[Server.tcpPort] = 1000\n    val map = config.toMap()\n    val newMap = tempFile().run {\n        ObjectOutputStream(outputStream()).use {\n            it.writeObject(map)\n        }\n        ObjectInputStream(inputStream()).use {\n            @Suppress(\"UNCHECKED_CAST\")\n            it.readObject() as Map<String, Any>\n        }\n    }\n    val newConfig = Config {\n        addSpec(Server)\n    }.from.map.kv(newMap)\n    check(config.toMap() == newConfig.toMap())\n}\n"
  },
  {
    "path": "konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Server.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.snippet\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\n\ndata class Server(val host: String, val tcpPort: Int) {\n    constructor(config: Config) : this(config[Server.host], config[Server.tcpPort])\n\n    fun start() {}\n\n    companion object : ConfigSpec(\"server\") {\n        val host by optional(\"0.0.0.0\", description = \"host IP of server\")\n        val tcpPort by required<Int>(description = \"port of server\")\n        val nextPort by lazy { config -> config[tcpPort] + 1 }\n    }\n}\n"
  },
  {
    "path": "konf-all/src/snippet/resources/server.json",
    "content": "{\n  \"server\": {\n    \"host\": \"127.0.0.1\",\n    \"tcp_port\": 8080\n  }\n}"
  },
  {
    "path": "konf-all/src/test/kotlin/com/uchuhimo/konf/source/MergeSourcesWithDifferentFeaturesSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject MergeSourcesWithDifferentFeaturesSpec : Spek({\n    on(\"load from merged sources\") {\n        val config = Config {\n            addSpec(ServicingConfig)\n        }.withSource(\n            Source.from.hocon.string(content) + Source.from.env()\n        )\n        it(\"should contain the item\") {\n            assertThat(config[ServicingConfig.baseURL], equalTo(\"https://service/api\"))\n            assertThat(config[ServicingConfig.url], equalTo(\"https://service/api/index.html\"))\n        }\n    }\n})\n\nobject ServicingConfig : ConfigSpec(\"servicing\") {\n    val baseURL by required<String>()\n    val url by required<String>()\n}\n\nval content = \"\"\"\n    servicing {\n      baseURL = \"https://service/api\"\n      url = \"${'$'}{servicing.baseURL}/index.html\"\n    }\n\"\"\".trimIndent()\n"
  },
  {
    "path": "konf-all/src/test/kotlin/com/uchuhimo/konf/source/MultiLayerConfigToValueSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.toValue\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject MultiLayerConfigToValueSpec : Spek({\n    val yamlContent = \"\"\"\ndb:\n  driverClassName: org.h2.Driver\n  url: 'jdbc:h2:mem:db;DB_CLOSE_DELAY=-1'\n    \"\"\".trimIndent()\n\n    val map = mapOf(\n        \"driverClassName\" to \"org.h2.Driver\",\n        \"url\" to \"jdbc:h2:mem:db;DB_CLOSE_DELAY=-1\"\n    )\n    on(\"load from multiple sources\") {\n        val config = Config {\n            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)\n        }\n            .from.yaml.string(yamlContent)\n            .from.yaml.file(\n                System.getenv(\"SERVICE_CONFIG\")\n                    ?: \"/opt/legacy-event-service/conf/legacy-event-service.yml\",\n                true\n            )\n            .from.systemProperties()\n            .from.env()\n        it(\"should cast to value correctly\") {\n            val db = config.toValue<ConfigTestReport>()\n            assertThat(db.db, equalTo(map))\n        }\n    }\n})\n\ndata class ConfigTestReport(val db: Map<String, String>)\n"
  },
  {
    "path": "konf-all/src/test/kotlin/com/uchuhimo/konf/source/MultipleDefaultLoadersSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject MultipleDefaultLoadersSpec : Spek({\n    on(\"load from multiple sources\") {\n        val config = Config {\n            addSpec(DefaultLoadersConfig)\n        }\n        val item = DefaultLoadersConfig.type\n        val afterLoadEnv = config.from.env()\n        System.setProperty(config.nameOf(DefaultLoadersConfig.type), \"system\")\n        val afterLoadSystemProperties = afterLoadEnv.from.systemProperties()\n        val afterLoadHocon = afterLoadSystemProperties.from.hocon.string(hoconContent)\n        val afterLoadJson = afterLoadHocon.from.json.string(jsonContent)\n        val afterLoadProperties = afterLoadJson.from.properties.string(propertiesContent)\n        val afterLoadToml = afterLoadProperties.from.toml.string(tomlContent)\n        val afterLoadXml = afterLoadToml.from.xml.string(xmlContent)\n        val afterLoadYaml = afterLoadXml.from.yaml.string(yamlContent)\n        val afterLoadFlat = afterLoadYaml.from.map.flat(mapOf(\"source.test.type\" to \"flat\"))\n        val afterLoadKv = afterLoadFlat.from.map.kv(mapOf(\"source.test.type\" to \"kv\"))\n        val afterLoadHierarchical = afterLoadKv.from.map.hierarchical(\n            mapOf(\n                \"source\" to\n                    mapOf(\n                        \"test\" to\n                            mapOf(\"type\" to \"hierarchical\")\n                    )\n            )\n        )\n        it(\"should load the corresponding value in each layer\") {\n            assertThat(afterLoadEnv[item], equalTo(\"env\"))\n            assertThat(afterLoadSystemProperties[item], equalTo(\"system\"))\n            assertThat(afterLoadHocon[item], equalTo(\"conf\"))\n            assertThat(afterLoadJson[item], equalTo(\"json\"))\n            assertThat(afterLoadProperties[item], equalTo(\"properties\"))\n            assertThat(afterLoadToml[item], equalTo(\"toml\"))\n            assertThat(afterLoadXml[item], equalTo(\"xml\"))\n            assertThat(afterLoadYaml[item], equalTo(\"yaml\"))\n            assertThat(afterLoadFlat[item], equalTo(\"flat\"))\n            assertThat(afterLoadKv[item], equalTo(\"kv\"))\n            assertThat(afterLoadHierarchical[item], equalTo(\"hierarchical\"))\n        }\n    }\n})\n\n//language=Json\nconst val jsonContent =\n    \"\"\"\n{\n  \"source\": {\n    \"test\": {\n      \"type\": \"json\"\n    }\n  }\n}\n\"\"\"\n"
  },
  {
    "path": "konf-all/src/test/kotlin/com/uchuhimo/konf/source/QuickStartSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.snippet.Server\nimport com.uchuhimo.konf.snippet.ServerSpec\nimport com.uchuhimo.konf.toValue\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.io.File\n\nobject QuickStartSpec : Spek({\n    on(\"use default loaders\") {\n        val config = useFile {\n            Config { addSpec(ServerSpec) }\n                .from.yaml.file(\"server.yml\")\n                .from.json.resource(\"server.json\")\n                .from.env()\n                .from.systemProperties()\n        }\n        it(\"should load all values\") {\n            assertThat(\n                config.toMap(),\n                equalTo(mapOf(\"server.host\" to \"127.0.0.1\", \"server.tcpPort\" to 8080))\n            )\n        }\n    }\n    on(\"use default providers\") {\n        val config = useFile {\n            Config { addSpec(ServerSpec) }.withSource(\n                Source.from.yaml.file(\"server.yml\") +\n                    Source.from.json.resource(\"server.json\") +\n                    Source.from.env() +\n                    Source.from.systemProperties()\n            )\n        }\n        it(\"should load all values\") {\n            assertThat(\n                config.toMap(),\n                equalTo(mapOf(\"server.host\" to \"127.0.0.1\", \"server.tcpPort\" to 8080))\n            )\n        }\n    }\n    on(\"watch file\") {\n        val config = useFile {\n            Config { addSpec(ServerSpec) }\n                .from.yaml.watchFile(\"server.yml\")\n                .from.json.resource(\"server.json\")\n                .from.env()\n                .from.systemProperties()\n        }\n        it(\"should load all values\") {\n            assertThat(\n                config.toMap(),\n                equalTo(mapOf(\"server.host\" to \"127.0.0.1\", \"server.tcpPort\" to 8080))\n            )\n        }\n    }\n    on(\"cast config to value\") {\n        val config = useFile {\n            Config()\n                .from.yaml.file(\"server.yml\")\n                .from.json.resource(\"server.json\")\n                .from.env()\n                .from.systemProperties()\n                .at(\"server\")\n        }\n        val server = config.toValue<Server>()\n        it(\"should load all values\") {\n            assertThat(server, equalTo(Server(host = \"127.0.0.1\", tcpPort = 8080)))\n        }\n    }\n    on(\"cast source to value\") {\n        val source = useFile {\n            (\n                Source.from.yaml.file(\"server.yml\") +\n                    Source.from.json.resource(\"server.json\") +\n                    Source.from.env() +\n                    Source.from.systemProperties()\n                )[\"server\"]\n        }\n        val server = source.toValue<Server>()\n        it(\"should load all values\") {\n            assertThat(server, equalTo(Server(host = \"127.0.0.1\", tcpPort = 8080)))\n        }\n    }\n})\n\nprivate fun <T> useFile(block: () -> T): T {\n    val file = File(\"server.yml\")\n    //language=YAML\n    file.writeText(\n        \"\"\"\n        server:\n            host: 127.0.0.1\n            tcp_port: 8080\n        \"\"\".trimIndent()\n    )\n    try {\n        return block()\n    } finally {\n        file.delete()\n    }\n}\n"
  },
  {
    "path": "konf-core/build.gradle.kts",
    "content": "dependencies {\n    jmhImplementation(kotlin(\"stdlib\", Versions.kotlin))\n}\n"
  },
  {
    "path": "konf-core/src/jmh/kotlin/com/uchuhimo/konf/ConfigBenchmark.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport org.openjdk.jmh.annotations.Benchmark\nimport org.openjdk.jmh.annotations.BenchmarkMode\nimport org.openjdk.jmh.annotations.Mode.AverageTime\nimport org.openjdk.jmh.annotations.OutputTimeUnit\nimport org.openjdk.jmh.annotations.Scope\nimport org.openjdk.jmh.annotations.State\nimport java.util.concurrent.TimeUnit.NANOSECONDS\n\nclass Buffer {\n    companion object : ConfigSpec(\"network.buffer\") {\n        val name by optional(\"buffer\", description = \"name of buffer\")\n    }\n}\n\n@BenchmarkMode(AverageTime)\n@OutputTimeUnit(NANOSECONDS)\nclass ConfigBenchmark {\n\n    @State(Scope.Thread)\n    class ConfigState {\n        val config = Config { addSpec(Buffer) }\n        val path = Buffer.qualify(Buffer.name)\n    }\n\n    @State(Scope.Benchmark)\n    class MultiThreadConfigState {\n        val config = Config { addSpec(Buffer) }\n        val path = Buffer.qualify(Buffer.name)\n    }\n\n    @Benchmark\n    fun getWithItem(state: ConfigState) = state.config[Buffer.name]\n\n    @Benchmark\n    fun getWithItemFromMultiThread(state: MultiThreadConfigState) = state.config[Buffer.name]\n\n    @Benchmark\n    fun setWithItem(state: ConfigState) {\n        state.config[Buffer.name] = \"newName\"\n    }\n\n    @Benchmark\n    fun setWithItemFromMultiThread(state: MultiThreadConfigState) {\n        state.config[Buffer.name] = \"newName\"\n    }\n\n    @Benchmark\n    fun getWithPath(state: ConfigState) = state.config<String>(state.path)\n\n    @Benchmark\n    fun getWithPathFromMultiThread(state: MultiThreadConfigState) = state.config<String>(state.path)\n\n    @Benchmark\n    fun setWithPath(state: ConfigState) {\n        state.config[state.path] = \"newName\"\n    }\n\n    @Benchmark\n    fun setWithPathFromMultiThread(state: MultiThreadConfigState) {\n        state.config[state.path] = \"newName\"\n    }\n}\n\n@BenchmarkMode(AverageTime)\n@OutputTimeUnit(NANOSECONDS)\nclass MultiLevelConfigBenchmark {\n\n    @State(Scope.Thread)\n    class ConfigState {\n        val config = Config { addSpec(Buffer) }.withLayer().withLayer().withLayer().withLayer()\n        val path = Buffer.qualify(Buffer.name)\n    }\n\n    @State(Scope.Benchmark)\n    class MultiThreadConfigState {\n        val config = Config { addSpec(Buffer) }.withLayer().withLayer().withLayer().withLayer()\n        val path = Buffer.qualify(Buffer.name)\n    }\n\n    @Benchmark\n    fun getWithItem(state: ConfigState) = state.config[Buffer.name]\n\n    @Benchmark\n    fun getWithItemFromMultiThread(state: MultiThreadConfigState) = state.config[Buffer.name]\n\n    @Benchmark\n    fun setWithItem(state: ConfigState) {\n        state.config[Buffer.name] = \"newName\"\n    }\n\n    @Benchmark\n    fun setWithItemFromMultiThread(state: MultiThreadConfigState) {\n        state.config[Buffer.name] = \"newName\"\n    }\n\n    @Benchmark\n    fun getWithPath(state: ConfigState) = state.config<String>(state.path)\n\n    @Benchmark\n    fun getWithPathFromMultiThread(state: MultiThreadConfigState) = state.config<String>(state.path)\n\n    @Benchmark\n    fun setWithPath(state: ConfigState) {\n        state.config[state.path] = \"newName\"\n    }\n\n    @Benchmark\n    fun setWithPathFromMultiThread(state: MultiThreadConfigState) {\n        state.config[state.path] = \"newName\"\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/java/com/uchuhimo/konf/Configs.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\nimport java.util.function.Consumer;\n\n/** Helper class for {@link com.uchuhimo.konf.Config Config}. */\npublic final class Configs {\n  private Configs() {}\n\n  /**\n   * Create a new root config.\n   *\n   * @return a new root config\n   */\n  public static Config create() {\n    return Config.Companion.invoke();\n  }\n\n  /**\n   * Create a new root config and initiate it.\n   *\n   * @param init initial action\n   * @return a new root config\n   */\n  public static Config create(Consumer<Config> init) {\n    final Config config = create();\n    init.accept(config);\n    return config;\n  }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/BaseConfig.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.databind.SerializationFeature\nimport com.fasterxml.jackson.databind.cfg.CoercionAction\nimport com.fasterxml.jackson.databind.cfg.CoercionInputShape\nimport com.fasterxml.jackson.databind.module.SimpleModule\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule\nimport com.fasterxml.jackson.module.kotlin.jacksonObjectMapper\nimport com.uchuhimo.konf.source.MergedSource\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.EmptyMapSource\nimport com.uchuhimo.konf.source.deserializer.DurationDeserializer\nimport com.uchuhimo.konf.source.deserializer.EmptyStringToCollectionDeserializerModifier\nimport com.uchuhimo.konf.source.deserializer.OffsetDateTimeDeserializer\nimport com.uchuhimo.konf.source.deserializer.StringDeserializer\nimport com.uchuhimo.konf.source.deserializer.ZoneDateTimeDeserializer\nimport com.uchuhimo.konf.source.load\nimport com.uchuhimo.konf.source.loadItem\nimport com.uchuhimo.konf.source.toCompatibleValue\nimport java.time.Duration\nimport java.time.OffsetDateTime\nimport java.time.ZonedDateTime\nimport java.util.WeakHashMap\nimport java.util.concurrent.locks.ReentrantReadWriteLock\nimport kotlin.concurrent.read\nimport kotlin.concurrent.write\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\n/**\n * The default implementation for [Config].\n */\nopen class BaseConfig(\n    override val name: String = \"\",\n    override val parent: BaseConfig? = null,\n    override val mapper: ObjectMapper = createDefaultMapper(),\n    private val specsInLayer: MutableList<Spec> = mutableListOf(),\n    private val featuresInLayer: MutableMap<Feature, Boolean> = mutableMapOf(),\n    private val nodeByItem: MutableMap<Item<*>, ItemNode> = mutableMapOf(),\n    private val tree: TreeNode = ContainerNode.placeHolder(),\n    private val hasChildren: Value<Boolean> = Value(false),\n    private val beforeSetFunctions: MutableList<(item: Item<*>, value: Any?) -> Unit> = mutableListOf(),\n    private val afterSetFunctions: MutableList<(item: Item<*>, value: Any?) -> Unit> = mutableListOf(),\n    private val beforeLoadFunctions: MutableList<(source: Source) -> Unit> = mutableListOf(),\n    private val afterLoadFunctions: MutableList<(source: Source) -> Unit> = mutableListOf(),\n    private val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()\n) : Config {\n    private val _source: Value<Source> = Value(EmptyMapSource())\n    open val source: Source get() = _source.value\n    private val nameByItem: WeakHashMap<Item<*>, String> = WeakHashMap()\n\n    override fun <T> lock(action: () -> T): T = lock.write(action)\n\n    override fun at(path: String): Config {\n        if (path.isEmpty()) {\n            return this\n        } else {\n            val originalConfig = this\n            return object : BaseConfig(\n                name = name,\n                parent = parent?.at(path) as BaseConfig?,\n                mapper = mapper,\n                specsInLayer = specsInLayer,\n                featuresInLayer = featuresInLayer,\n                nodeByItem = nodeByItem,\n                tree = tree.getOrNull(path) ?: ContainerNode.placeHolder().also {\n                    lock.write { tree[path] = it }\n                },\n                hasChildren = hasChildren,\n                beforeSetFunctions = beforeSetFunctions,\n                afterSetFunctions = afterSetFunctions,\n                beforeLoadFunctions = beforeLoadFunctions,\n                afterLoadFunctions = afterLoadFunctions,\n                lock = lock\n            ) {\n                override val source: Source\n                    get() {\n                        if (path !in originalConfig.source) {\n                            originalConfig.source.tree[path] = ContainerNode.placeHolder()\n                        }\n                        return originalConfig.source[path]\n                    }\n            }\n        }\n    }\n\n    override fun withPrefix(prefix: String): Config {\n        if (prefix.isEmpty()) {\n            return this\n        } else {\n            val originalConfig = this\n            return object : BaseConfig(\n                name = name,\n                parent = parent?.withPrefix(prefix) as BaseConfig?,\n                mapper = mapper,\n                specsInLayer = specsInLayer,\n                featuresInLayer = featuresInLayer,\n                nodeByItem = nodeByItem,\n                tree = if (prefix.isEmpty()) tree\n                else ContainerNode.empty().apply {\n                    set(prefix, tree)\n                },\n                hasChildren = hasChildren,\n                beforeSetFunctions = beforeSetFunctions,\n                afterSetFunctions = afterSetFunctions,\n                beforeLoadFunctions = beforeLoadFunctions,\n                afterLoadFunctions = afterLoadFunctions,\n                lock = lock\n            ) {\n                override val source: Source get() = originalConfig.source.withPrefix(prefix)\n            }\n        }\n    }\n\n    override fun iterator(): Iterator<Item<*>> {\n        return if (parent != null) {\n            (nodeByItem.keys.iterator().asSequence() + parent!!.iterator().asSequence()).iterator()\n        } else {\n            nodeByItem.keys.iterator()\n        }\n    }\n\n    override val itemWithNames: List<Pair<Item<*>, String>>\n        get() = lock.read { tree.leafByPath }.map { (name, node) ->\n            (node as ItemNode).item to name\n        } + (parent?.itemWithNames ?: listOf())\n\n    override fun toMap(): Map<String, Any> {\n        return lock.read {\n            itemWithNames.map { (item, name) ->\n                name to try {\n                    getOrNull(item, errorWhenNotFound = true).toCompatibleValue(mapper)\n                } catch (_: UnsetValueException) {\n                    ValueState.Unset\n                }\n            }.filter { (_, value) -> value != ValueState.Unset }.toMap()\n        }\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T> get(item: Item<T>): T = getOrNull(item, errorWhenNotFound = true) as T\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T> get(name: String): T = getOrNull(name, errorWhenNotFound = true) as T\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T> getOrNull(item: Item<T>): T? = getOrNull(item, errorWhenNotFound = false) as T?\n\n    private fun setState(item: Item<*>, state: ValueState) {\n        if (item in nodeByItem) {\n            nodeByItem[item]!!.value = state\n        } else {\n            nodeByItem[item] = ItemNode(state, item)\n        }\n    }\n\n    open fun getOrNull(\n        item: Item<*>,\n        errorWhenNotFound: Boolean,\n        errorWhenGetDefault: Boolean = false,\n        lazyContext: ItemContainer = this\n    ): Any? {\n        val valueState = lock.read { nodeByItem[item]?.value }\n        if (valueState != null) {\n            @Suppress(\"UNCHECKED_CAST\")\n            when (valueState) {\n                is ValueState.Unset ->\n                    if (errorWhenNotFound) {\n                        throw UnsetValueException(item)\n                    } else {\n                        return null\n                    }\n                is ValueState.Null -> return null\n                is ValueState.Value -> return valueState.value\n                is ValueState.Default -> {\n                    if (errorWhenGetDefault) {\n                        throw GetDefaultValueException(item)\n                    } else {\n                        return valueState.value\n                    }\n                }\n                is ValueState.Lazy<*> -> {\n                    val value = try {\n                        valueState.thunk(lazyContext)\n                    } catch (exception: ConfigException) {\n                        when (exception) {\n                            is UnsetValueException, is NoSuchItemException -> {\n                                if (errorWhenNotFound) {\n                                    throw exception\n                                } else {\n                                    return null\n                                }\n                            }\n                            else -> throw exception\n                        }\n                    }\n                    if (value == null) {\n                        if (item.nullable) {\n                            return null\n                        } else {\n                            throw InvalidLazySetException(\n                                \"fail to cast null to ${item.type.rawClass}\" +\n                                    \" when getting item ${item.name} in config\"\n                            )\n                        }\n                    } else {\n                        if (item.type.rawClass.isInstance(value)) {\n                            return value\n                        } else {\n                            throw InvalidLazySetException(\n                                \"fail to cast $value with ${value::class} to ${item.type.rawClass}\" +\n                                    \" when getting item ${item.name} in config\"\n                            )\n                        }\n                    }\n                }\n            }\n        } else {\n            if (parent != null) {\n                return parent!!.getOrNull(item, errorWhenNotFound, errorWhenGetDefault, lazyContext)\n            } else {\n                if (errorWhenNotFound) {\n                    throw NoSuchItemException(item)\n                } else {\n                    return null\n                }\n            }\n        }\n    }\n\n    open fun getItemOrNull(name: String): Item<*>? {\n        val trimmedName = name.trim()\n        val item = getItemInLayerOrNull(trimmedName)\n        return item ?: parent?.getItemOrNull(trimmedName)\n    }\n\n    private fun getItemInLayerOrNull(name: String): Item<*>? {\n        return lock.read {\n            (tree.getOrNull(name) as? ItemNode)?.item\n        }\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T> getOrNull(name: String): T? = getOrNull(name, errorWhenNotFound = false) as T?\n\n    private fun getOrNull(name: String, errorWhenNotFound: Boolean): Any? {\n        val item = getItemOrNull(name)\n        return if (item != null) {\n            getOrNull(item, errorWhenNotFound)\n        } else {\n            if (errorWhenNotFound) {\n                throw NoSuchItemException(name)\n            } else {\n                null\n            }\n        }\n    }\n\n    private fun containsInLayer(item: Item<*>) = lock.read { nodeByItem.containsKey(item) }\n\n    override fun contains(item: Item<*>): Boolean {\n        return if (containsInLayer(item)) {\n            true\n        } else {\n            parent?.contains(item) ?: false\n        }\n    }\n\n    private fun containsInLayer(name: String): Boolean {\n        return containsInLayer(name.toPath())\n    }\n\n    override fun contains(name: String): Boolean {\n        return if (containsInLayer(name)) {\n            true\n        } else {\n            parent?.contains(name) ?: false\n        }\n    }\n\n    private fun TreeNode.partialMatch(path: Path): Boolean {\n        return if (this is LeafNode) {\n            true\n        } else if (path.isEmpty()) {\n            !isEmpty()\n        } else {\n            val key = path.first()\n            val rest = path.drop(1)\n            val result = children[key]\n            if (result != null) {\n                return result.partialMatch(rest)\n            } else {\n                return false\n            }\n        }\n    }\n\n    private fun containsInLayer(path: Path): Boolean {\n        return lock.read {\n            tree.partialMatch(path)\n        }\n    }\n\n    override fun contains(path: Path): Boolean =\n        containsInLayer(path) || (parent?.contains(path) ?: false)\n\n    override fun nameOf(item: Item<*>): String {\n        return nameByItem[item] ?: {\n            val name = lock.read { tree.firstPath { it is ItemNode && it.item == item } }?.name\n            if (name != null) {\n                nameByItem[item] = name\n                name\n            } else {\n                parent?.nameOf(item) ?: throw NoSuchItemException(item)\n            }\n        }()\n    }\n\n    open fun addBeforeSetFunction(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        beforeSetFunctions += beforeSetFunction\n        parent?.addBeforeSetFunction(beforeSetFunction)\n    }\n\n    open fun removeBeforeSetFunction(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        beforeSetFunctions.remove(beforeSetFunction)\n        parent?.removeBeforeSetFunction(beforeSetFunction)\n    }\n\n    override fun beforeSet(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit): Handler {\n        addBeforeSetFunction(beforeSetFunction)\n        return object : Handler {\n            override fun cancel() {\n                removeBeforeSetFunction(beforeSetFunction)\n            }\n        }\n    }\n\n    private fun notifyBeforeSet(item: Item<*>, value: Any?) {\n        for (beforeSetFunction in beforeSetFunctions) {\n            beforeSetFunction(item, value)\n        }\n    }\n\n    open fun addAfterSetFunction(afterSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        afterSetFunctions += afterSetFunction\n        parent?.addAfterSetFunction(afterSetFunction)\n    }\n\n    open fun removeAfterSetFunction(afterSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        afterSetFunctions.remove(afterSetFunction)\n        parent?.removeAfterSetFunction(afterSetFunction)\n    }\n\n    override fun afterSet(afterSetFunction: (item: Item<*>, value: Any?) -> Unit): Handler {\n        addAfterSetFunction(afterSetFunction)\n        return object : Handler {\n            override fun cancel() {\n                removeAfterSetFunction(afterSetFunction)\n            }\n        }\n    }\n\n    private fun notifyAfterSet(item: Item<*>, value: Any?) {\n        for (afterSetFunction in afterSetFunctions) {\n            afterSetFunction(item, value)\n        }\n    }\n\n    override fun rawSet(item: Item<*>, value: Any?) {\n        if (item in this) {\n            if (value == null) {\n                if (item.nullable) {\n                    item.notifySet(null)\n                    item.notifyBeforeSet(this, value)\n                    notifyBeforeSet(item, value)\n                    lock.write {\n                        setState(item, ValueState.Null)\n                    }\n                    notifyAfterSet(item, value)\n                    item.notifyAfterSet(this, value)\n                } else {\n                    throw ClassCastException(\n                        \"fail to cast null to ${item.type.rawClass}\" +\n                            \" when setting item ${item.name} in config\"\n                    )\n                }\n            } else {\n                if (item.type.rawClass.isInstance(value)) {\n                    item.notifySet(value)\n                    item.notifyBeforeSet(this, value)\n                    notifyBeforeSet(item, value)\n                    lock.write {\n                        setState(item, ValueState.Value(value))\n                    }\n                    notifyAfterSet(item, value)\n                    item.notifyAfterSet(this, value)\n                } else {\n                    throw ClassCastException(\n                        \"fail to cast $value with ${value::class} to ${item.type.rawClass}\" +\n                            \" when setting item ${item.name} in config\"\n                    )\n                }\n            }\n        } else {\n            throw NoSuchItemException(item)\n        }\n    }\n\n    override fun <T> set(item: Item<T>, value: T) {\n        rawSet(item, value)\n    }\n\n    override fun <T> set(name: String, value: T) {\n        val item = getItemOrNull(name)\n        if (item != null) {\n            @Suppress(\"UNCHECKED_CAST\")\n            set(item as Item<T>, value)\n        } else {\n            throw NoSuchItemException(name)\n        }\n    }\n\n    override fun <T> lazySet(item: Item<T>, thunk: (config: ItemContainer) -> T) {\n        if (item in this) {\n            lock.write {\n                setState(item, ValueState.Lazy(thunk))\n            }\n        } else {\n            throw NoSuchItemException(item)\n        }\n    }\n\n    override fun <T> lazySet(name: String, thunk: (config: ItemContainer) -> T) {\n        val item = getItemOrNull(name)\n        if (item != null) {\n            @Suppress(\"UNCHECKED_CAST\")\n            lazySet(item as Item<T>, thunk)\n        } else {\n            throw NoSuchItemException(name)\n        }\n    }\n\n    override fun unset(item: Item<*>) {\n        if (item in this) {\n            lock.write {\n                setState(item, ValueState.Unset)\n            }\n        } else {\n            throw NoSuchItemException(item)\n        }\n    }\n\n    override fun unset(name: String) {\n        val item = getItemOrNull(name)\n        if (item != null) {\n            unset(item)\n        } else {\n            throw NoSuchItemException(name)\n        }\n    }\n\n    override fun clear() {\n        lock.write {\n            nodeByItem.clear()\n            tree.children.clear()\n            if (tree is MapNode) {\n                tree.isPlaceHolder = true\n            }\n        }\n    }\n\n    override fun clearAll() {\n        clear()\n        parent?.clearAll()\n    }\n\n    override fun containsRequired(): Boolean = try {\n        validateRequired()\n        true\n    } catch (ex: UnsetValueException) {\n        false\n    }\n\n    override fun validateRequired(): Config {\n        for (item in this) {\n            if (item is RequiredItem) {\n                getOrNull(item, errorWhenNotFound = true)\n            }\n        }\n        return this\n    }\n\n    override fun plus(config: Config): Config {\n        return when (config) {\n            is BaseConfig -> MergedConfig(this, config)\n            else -> config.withFallback(this)\n        }\n    }\n\n    override fun withFallback(config: Config): Config {\n        return config + this\n    }\n\n    override fun <T> property(item: Item<T>): ReadWriteProperty<Any?, T> {\n        if (!contains(item)) {\n            throw NoSuchItemException(item)\n        }\n        return object : ReadWriteProperty<Any?, T> {\n            override fun getValue(thisRef: Any?, property: KProperty<*>): T = get(item)\n\n            override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =\n                set(item, value)\n        }\n    }\n\n    override fun <T> property(name: String): ReadWriteProperty<Any?, T> {\n        if (!contains(name)) {\n            throw NoSuchItemException(name)\n        }\n        return object : ReadWriteProperty<Any?, T> {\n            override fun getValue(thisRef: Any?, property: KProperty<*>): T = get(name)\n\n            override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =\n                set(name, value)\n        }\n    }\n\n    override val specs: List<Spec> get() = lock.read { specsInLayer + (parent?.specs ?: listOf()) }\n\n    override val sources: List<Source>\n        get() {\n            return lock.read { mutableListOf(source) }.apply {\n                for (source in parent?.sources ?: listOf()) {\n                    add(source)\n                }\n            }\n        }\n\n    override fun enable(feature: Feature): Config {\n        return apply {\n            lock.write {\n                featuresInLayer[feature] = true\n            }\n        }\n    }\n\n    override fun disable(feature: Feature): Config {\n        return apply {\n            lock.write {\n                featuresInLayer[feature] = false\n            }\n        }\n    }\n\n    override fun isEnabled(feature: Feature): Boolean {\n        return lock.read {\n            featuresInLayer[feature] ?: parent?.isEnabled(feature) ?: feature.enabledByDefault\n        }\n    }\n\n    override fun addItem(item: Item<*>, prefix: String) {\n        lock.write {\n            if (hasChildren.value) {\n                throw LayerFrozenException(this)\n            }\n            val path = prefix.toPath() + item.name.toPath()\n            val name = path.name\n            if (item !in this) {\n                if (path in this) {\n                    throw NameConflictException(\"item $name cannot be added\")\n                }\n                val node = ItemNode(\n                    when (item) {\n                        is OptionalItem -> ValueState.Default(item.default)\n                        is RequiredItem -> ValueState.Unset\n                        is LazyItem -> ValueState.Lazy(item.thunk)\n                    },\n                    item\n                )\n                tree[name] = node\n                nodeByItem[item] = node\n                val sources = this.sources\n                val mergedSource = if (sources.isNotEmpty()) {\n                    sources.reduceRight { source, acc -> MergedSource(source, acc) }\n                } else {\n                    null\n                }\n                mergedSource?.let { loadItem(item, path, it) }\n            } else {\n                throw RepeatedItemException(name)\n            }\n        }\n    }\n\n    override fun addSpec(spec: Spec) {\n        lock.write {\n            if (hasChildren.value) {\n                throw LayerFrozenException(this)\n            }\n            val sources = this.sources\n            val mergedSource = if (sources.isNotEmpty()) {\n                sources.reduceRight { source, acc -> MergedSource(source, acc) }\n            } else {\n                null\n            }\n            spec.items.forEach { item ->\n                val name = spec.qualify(item)\n                if (item !in this) {\n                    val path = name.toPath()\n                    if (path in this) {\n                        throw NameConflictException(\"item $name cannot be added\")\n                    }\n                    val node = ItemNode(\n                        when (item) {\n                            is OptionalItem -> ValueState.Default(item.default)\n                            is RequiredItem -> ValueState.Unset\n                            is LazyItem -> ValueState.Lazy(item.thunk)\n                        },\n                        item\n                    )\n                    tree[name] = node\n                    nodeByItem[item] = node\n                    mergedSource?.let { loadItem(item, path, it) }\n                } else {\n                    throw RepeatedItemException(name)\n                }\n            }\n            spec.innerSpecs.forEach { innerSpec ->\n                addSpec(innerSpec.withPrefix(spec.prefix))\n            }\n            specsInLayer += spec\n        }\n    }\n\n    override fun withLayer(name: String): BaseConfig {\n        lock.write { hasChildren.value = true }\n        return BaseConfig(name, this, mapper)\n    }\n\n    open fun addBeforeLoadFunction(beforeLoadFunction: (source: Source) -> Unit) {\n        beforeLoadFunctions += beforeLoadFunction\n        parent?.addBeforeLoadFunction(beforeLoadFunction)\n    }\n\n    open fun removeBeforeLoadFunction(beforeLoadFunction: (source: Source) -> Unit) {\n        beforeLoadFunctions.remove(beforeLoadFunction)\n        parent?.removeBeforeLoadFunction(beforeLoadFunction)\n    }\n\n    override fun beforeLoad(beforeLoadFunction: (source: Source) -> Unit): Handler {\n        addBeforeLoadFunction(beforeLoadFunction)\n        return object : Handler {\n            override fun cancel() {\n                removeBeforeLoadFunction(beforeLoadFunction)\n            }\n        }\n    }\n\n    private fun notifyBeforeLoad(source: Source) {\n        for (beforeLoadFunction in beforeLoadFunctions) {\n            beforeLoadFunction(source)\n        }\n    }\n\n    open fun addAfterLoadFunction(afterLoadFunction: (source: Source) -> Unit) {\n        afterLoadFunctions += afterLoadFunction\n        parent?.addAfterLoadFunction(afterLoadFunction)\n    }\n\n    open fun removeAfterLoadFunction(afterLoadFunction: (source: Source) -> Unit) {\n        afterLoadFunctions.remove(afterLoadFunction)\n        parent?.removeAfterLoadFunction(afterLoadFunction)\n    }\n\n    override fun afterLoad(afterLoadFunction: (source: Source) -> Unit): Handler {\n        addAfterLoadFunction(afterLoadFunction)\n        return object : Handler {\n            override fun cancel() {\n                removeAfterLoadFunction(afterLoadFunction)\n            }\n        }\n    }\n\n    private fun notifyAfterLoad(source: Source) {\n        for (afterLoadFunction in afterLoadFunctions) {\n            afterLoadFunction(source)\n        }\n    }\n\n    override fun withSource(source: Source): Config {\n        return withLayer(\"source: ${source.description}\").also { config ->\n            config.lock.write {\n                config._source.value = load(config, source)\n            }\n        }\n    }\n\n    override fun withLoadTrigger(\n        description: String,\n        trigger: (\n            config: Config,\n            load: (source: Source) -> Unit\n        ) -> Unit\n    ): Config {\n        return withLayer(\"trigger: $description\").apply {\n            trigger(this) { source ->\n                notifyBeforeLoad(source)\n                lock.write {\n                    this._source.value = load(this, source)\n                }\n                notifyAfterLoad(source)\n            }\n        }\n    }\n\n    override fun toString(): String {\n        return \"Config(items=${toMap()})\"\n    }\n\n    class ItemNode(override var value: ValueState, val item: Item<*>) : ValueNode\n\n    data class Value<T>(var value: T)\n\n    sealed class ValueState {\n        object Unset : ValueState()\n        object Null : ValueState()\n        data class Lazy<T>(val thunk: (config: ItemContainer) -> T) : ValueState()\n        data class Value(val value: Any) : ValueState()\n        data class Default(val value: Any?) : ValueState()\n    }\n}\n\n/**\n * Returns a new default object mapper for config.\n */\nfun createDefaultMapper(): ObjectMapper = jacksonObjectMapper()\n    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)\n    .disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)\n    .enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID)\n    .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)\n    .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)\n    .apply {\n        coercionConfigDefaults().setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty)\n    }\n    .registerModules(\n        SimpleModule()\n            .addDeserializer(String::class.java, StringDeserializer)\n            .setDeserializerModifier(EmptyStringToCollectionDeserializerModifier),\n        JavaTimeModule()\n            .addDeserializer(Duration::class.java, DurationDeserializer)\n            .addDeserializer(OffsetDateTime::class.java, OffsetDateTimeDeserializer)\n            .addDeserializer(ZonedDateTime::class.java, ZoneDateTimeDeserializer)\n    )\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Config.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.databind.JavaType\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.databind.type.TypeFactory\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.DefaultLoaders\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.kvToTree\nimport kotlin.properties.PropertyDelegateProvider\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\n/**\n * Config containing items and associated values.\n *\n * Config contains items, which can be loaded with [addSpec].\n * Config contains values, each of which is associated with corresponding item.\n * Values can be loaded from [source][Source] with [withSource] or [from].\n *\n * Config contains read-write access operations for item.\n * Items in config is in one of three states:\n * - Unset. Item has not associated value in this state.\n *   Use [unset] to change item to this state.\n * - Unevaluated. Item is lazy and the associated value will be evaluated when accessing.\n *   Use [lazySet] to change item to this state.\n * - Evaluated.  Item has associated value which is evaluated.\n *   Use [set] to change item to this state.\n *\n * Config is cascading.\n * Config can fork from another config by adding a new layer on it.\n * The forked config is called child config, and the original config is called parent config.\n * A config without parent config is called root config. The new layer added by child config\n * is called facade layer.\n * Config with ancestor configs has multiple layers. All set operation is executed in facade layer\n * of config.\n * Descendant config inherits items and values in ancestor configs, and can override values for\n * items in ancestor configs. Overridden values in config will affect itself and its descendant\n * configs, without affecting its ancestor configs. Loading items in config will not affect its\n * ancestor configs too. [invoke] can be used to create a root config, and [withLayer] can be used\n * to create a child config from specified config.\n *\n * All methods in Config is thread-safe.\n */\ninterface Config : ItemContainer {\n    /**\n     * Associate item with specified value without type checking.\n     *\n     * @param item config item\n     * @param value associated value\n     */\n    fun rawSet(item: Item<*>, value: Any?)\n\n    /**\n     * Associate item with specified value.\n     *\n     * @param item config item\n     * @param value associated value\n     */\n    operator fun <T> set(item: Item<T>, value: T)\n\n    /**\n     * Find item with specified name, and associate it with specified value.\n     *\n     * @param name item name\n     * @param value associated value\n     */\n    operator fun <T> set(name: String, value: T)\n\n    /**\n     * Associate item with specified thunk, which can be used to evaluate value for the item.\n     *\n     * @param item config item\n     * @param thunk thunk used to evaluate value for the item\n     */\n    fun <T> lazySet(item: Item<T>, thunk: (config: ItemContainer) -> T)\n\n    /**\n     * Find item with specified name, and associate item with specified thunk,\n     * which can be used to evaluate value for the item.\n     *\n     * @param name item name\n     * @param thunk thunk used to evaluate value for the item\n     */\n    fun <T> lazySet(name: String, thunk: (config: ItemContainer) -> T)\n\n    /**\n     * Discard associated value of specified item.\n     *\n     * @param item config item\n     */\n    fun unset(item: Item<*>)\n\n    /**\n     * Discard associated value of item with specified name.\n     *\n     * @param name item name\n     */\n    fun unset(name: String)\n\n    /**\n     * Subscribe the update event before every set operation.\n     *\n     * @param beforeSetFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun beforeSet(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit): Handler\n\n    /**\n     * Subscribe the update event after every set operation.\n     *\n     * @param afterSetFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun afterSet(afterSetFunction: (item: Item<*>, value: Any?) -> Unit): Handler\n\n    /**\n     * Remove all values from the facade layer of this config.\n     */\n    fun clear()\n\n    /**\n     * Remove all values from all layers of this config.\n     */\n    fun clearAll()\n\n    /**\n     * Whether all required items have values or not.\n     *\n     * @return `true` if all required items have values, `false` otherwise\n     */\n    fun containsRequired(): Boolean\n\n    /**\n     * Validate whether all required items have values or not. If not, throws [UnsetValueException].\n     *\n     * @return the current config\n     */\n    fun validateRequired(): Config\n\n    /**\n     * Returns a property that can read/set associated value for specified item.\n     *\n     * @param item config item\n     * @return a property that can read/set associated value for specified item\n     */\n    fun <T> property(item: Item<T>): ReadWriteProperty<Any?, T>\n\n    /**\n     * Returns a property that can read/set associated value for item with specified name.\n     *\n     * @param name item name\n     * @return a property that can read/set associated value for item with specified name\n     */\n    fun <T> property(name: String): ReadWriteProperty<Any?, T>\n\n    /**\n     * Name of facade layer of config.\n     *\n     * Layer name provides information for facade layer in a cascading config.\n     */\n    val name: String\n\n    /**\n     * Returns parent of this config, or `null` if this config is a root config.\n     */\n    val parent: Config?\n\n    /**\n     * List of config specs from all layers of this config.\n     */\n    val specs: List<Spec>\n\n    /**\n     * List of sources from all layers of this config.\n     */\n    val sources: List<Source>\n\n    /**\n     * Returns a config overlapped by the specified facade config.\n     *\n     * All operations will be applied to the facade config first,\n     * and then fall back to this config when necessary.\n     *\n     * @param config the facade config\n     * @return a config overlapped by the specified facade config\n     */\n    operator fun plus(config: Config): Config\n\n    /**\n     * Returns a config backing by the specified fallback config.\n     *\n     * All operations will be applied to this config first,\n     * and then fall back to the fallback config when necessary.\n     *\n     * @param config the fallback config\n     * @return a config backing by the specified fallback config\n     */\n    fun withFallback(config: Config): Config\n\n    /**\n     * Returns sub-config in the specified path.\n     *\n     * @param path the specified path\n     * @return sub-config in the specified path\n     */\n    fun at(path: String): Config\n\n    /**\n     * Returns config with the specified additional prefix.\n     *\n     * @param prefix additional prefix\n     * @return config with the specified additional prefix\n     */\n    fun withPrefix(prefix: String): Config\n\n    /**\n     * Load item into facade layer with the specified prefix.\n     *\n     * Same item cannot be added twice.\n     * The item cannot have same qualified name with existed items in config.\n     *\n     * @param item config item\n     * @param prefix prefix for the config item\n     */\n    fun addItem(item: Item<*>, prefix: String = \"\")\n\n    /**\n     * Load items in specified config spec into facade layer.\n     *\n     * Same config spec cannot be added twice.\n     * All items in specified config spec cannot have same qualified name with existed items in config.\n     *\n     * @param spec config spec\n     */\n    fun addSpec(spec: Spec)\n\n    /**\n     * Executes the given [action] after locking the facade layer of this config.\n     *\n     * @param action the given action\n     * @return the return value of the action.\n     */\n    fun <T> lock(action: () -> T): T\n\n    /**\n     * Returns a child config of this config with specified name.\n     *\n     * @param name name of facade layer in child config\n     * @return a child config\n     */\n    fun withLayer(name: String = \"\"): Config\n\n    /**\n     * Returns a child config containing values from specified source.\n     *\n     * Values from specified source will be loaded into facade layer of the returned child config\n     * without affecting this config.\n     *\n     * @param source config source\n     * @return a child config containing value from specified source\n     */\n    fun withSource(source: Source): Config\n\n    /**\n     * Returns a child config containing values loaded by specified trigger.\n     *\n     * Values loaded by specified trigger will be loaded into facade layer of\n     * the returned child config without affecting this config.\n     *\n     * @param description trigger description\n     * @param trigger load trigger\n     * @return a child config containing value loaded by specified trigger\n     */\n    fun withLoadTrigger(\n        description: String,\n        trigger: (\n            config: Config,\n            load: (source: Source) -> Unit\n        ) -> Unit\n    ): Config\n\n    /**\n     * Subscribe the update event before every load operation.\n     *\n     * @param beforeLoadFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun beforeLoad(beforeLoadFunction: (source: Source) -> Unit): Handler\n\n    /**\n     * Subscribe the update event after every load operation.\n     *\n     * @param afterLoadFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun afterLoad(afterLoadFunction: (source: Source) -> Unit): Handler\n\n    /**\n     * Returns default loaders for this config.\n     *\n     * It is a fluent API for loading source from default loaders.\n     *\n     * @return default loaders for this config\n     */\n    @JavaApi\n    fun from(): DefaultLoaders = from\n\n    /**\n     * Returns default loaders for this config.\n     *\n     * It is a fluent API for loading source from default loaders.\n     */\n    val from: DefaultLoaders get() = DefaultLoaders(this)\n\n    /**\n     * Returns [ObjectMapper] using to map from source to value in config.\n     */\n    val mapper: ObjectMapper\n\n    /**\n     * Returns a map in key-value format for this config.\n     *\n     * The returned map contains all items in this config, with item name as key and\n     * associated value as value.\n     * This map can be loaded into config as [com.uchuhimo.konf.source.base.KVSource] using\n     * `config.from.map.kv(map)`.\n     */\n    fun toMap(): Map<String, Any>\n\n    /**\n     * Enables the specified feature and returns this config.\n     *\n     * @param feature the specified feature\n     * @return this config\n     */\n    fun enable(feature: Feature): Config\n\n    /**\n     * Disables the specified feature and returns this config.\n     *\n     * @param feature the specified feature\n     * @return this config\n     */\n    fun disable(feature: Feature): Config\n\n    /**\n     * Check whether the specified feature is enabled or not.\n     *\n     * @param feature the specified feature\n     * @return whether the specified feature is enabled or not\n     */\n    fun isEnabled(feature: Feature): Boolean\n\n    companion object {\n        /**\n         * Create a new root config.\n         *\n         * @return a new root config\n         */\n        operator fun invoke(): Config = BaseConfig()\n\n        /**\n         * Create a new root config and initiate it.\n         *\n         * @param init initial action\n         * @return a new root config\n         */\n        operator fun invoke(init: Config.() -> Unit): Config = Config().apply(init)\n    }\n}\n\n/**\n * Returns a property that can read/set associated value casted from config.\n *\n * @return a property that can read/set associated value casted from config\n */\ninline fun <reified T> Config.cast() =\n    object : RequiredConfigProperty<T>(this.withPrefix(\"root\").withLayer(), name = \"root\") {}\n\n/**\n * Returns a value casted from config.\n *\n * @return a value casted from config\n */\ninline fun <reified T> Config.toValue(): T {\n    val value by cast<T>()\n    return value\n}\n\n/**\n * Returns a property that can read/set associated value for specified required item.\n *\n * @param prefix prefix for the config item\n * @param name item name without prefix\n * @param description description for this item\n * @return a property that can read/set associated value for specified required item\n */\ninline fun <reified T> Config.required(\n    prefix: String = \"\",\n    name: String? = null,\n    description: String = \"\"\n) =\n    object : RequiredConfigProperty<T>(this, prefix, name, description, null is T) {}\n\nopen class RequiredConfigProperty<T>(\n    private val config: Config,\n    private val prefix: String = \"\",\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadWriteProperty<Any?, T> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(RequiredConfigProperty::class.java).bindings.typeParameters[0]\n        val item = object : RequiredItem<T>(\n            Spec.dummy,\n            name\n                ?: property.name,\n            description,\n            type,\n            nullable\n        ) {}\n        config.addItem(item, prefix)\n        return config.property(item)\n    }\n}\n\n/**\n * Returns a property that can read/set associated value for specified optional item.\n *\n * @param default default value returned before associating this item with specified value\n * @param prefix prefix for the config item\n * @param name item name without prefix\n * @param description description for this item\n * @return a property that can read/set associated value for specified optional item\n */\ninline fun <reified T> Config.optional(\n    default: T,\n    prefix: String = \"\",\n    name: String? = null,\n    description: String = \"\"\n) =\n    object : OptionalConfigProperty<T>(this, default, prefix, name, description, null is T) {}\n\nopen class OptionalConfigProperty<T>(\n    private val config: Config,\n    private val default: T,\n    private val prefix: String = \"\",\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadWriteProperty<Any?, T> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(OptionalConfigProperty::class.java).bindings.typeParameters[0]\n        val item = object : OptionalItem<T>(\n            Spec.dummy,\n            name\n                ?: property.name,\n            default,\n            description,\n            type,\n            nullable\n        ) {}\n        config.addItem(item, prefix)\n        return config.property(item)\n    }\n}\n\n/**\n * Returns a property that can read/set associated value for specified lazy item.\n *\n * @param prefix prefix for the config item\n * @param name item name without prefix\n * @param description description for this item\n * @param thunk thunk used to evaluate value for this item\n * @return a property that can read/set associated value for specified lazy item\n */\ninline fun <reified T> Config.lazy(\n    prefix: String = \"\",\n    name: String? = null,\n    description: String = \"\",\n    noinline thunk: (config: ItemContainer) -> T\n) =\n    object : LazyConfigProperty<T>(this, thunk, prefix, name, description, null is T) {}\n\nopen class LazyConfigProperty<T>(\n    private val config: Config,\n    private val thunk: (config: ItemContainer) -> T,\n    private val prefix: String = \"\",\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadWriteProperty<Any?, T> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(LazyConfigProperty::class.java).bindings.typeParameters[0]\n        val item = object : LazyItem<T>(\n            Spec.dummy,\n            name\n                ?: property.name,\n            thunk,\n            description,\n            type,\n            nullable\n        ) {}\n        config.addItem(item, prefix)\n        return config.property(item)\n    }\n}\n\n/**\n * Convert the config to a tree node.\n *\n * @return a tree node\n */\nfun Config.toTree(): TreeNode {\n    return toMap().kvToTree()\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/ConfigException.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\n/**\n * Exception for config.\n */\nopen class ConfigException : RuntimeException {\n    constructor(message: String) : super(message)\n    constructor(message: String, cause: Throwable) : super(message, cause)\n}\n\n/**\n * Exception indicates that there is existed item with same name in config.\n */\nclass RepeatedItemException(val name: String) : ConfigException(\"item $name has been added\")\n\n/**\n * Exception indicates that there is existed inner spec in config.\n */\nclass RepeatedInnerSpecException(val spec: Spec) :\n    ConfigException(\"spec ${spec.javaClass.simpleName}(prefix=\\\"${spec.prefix}\\\") has been added\")\n\n/**\n * Exception indicates that there is existed item with conflicted name in config.\n */\nclass NameConflictException(message: String) : ConfigException(message)\n\n/**\n * Exception indicates that the evaluated result of lazy thunk is invalid.\n */\nclass InvalidLazySetException(message: String) : ConfigException(message)\n\nval Item<*>.asName: String get() = \"item $name\"\n\n/**\n * Exception indicates that the specified item is in unset state.\n */\nclass UnsetValueException(val name: String) : ConfigException(\"$name is unset\") {\n    constructor(item: Item<*>) : this(item.asName)\n}\n\n/**\n * Exception indicates that the specified item has default value.\n */\nclass GetDefaultValueException(val name: String) : ConfigException(\"$name has default value\") {\n    constructor(item: Item<*>) : this(item.asName)\n}\n\n/**\n * Exception indicates that the specified item is not in this config.\n */\nclass NoSuchItemException(val name: String) : ConfigException(\"cannot find $name in config\") {\n    constructor(item: Item<*>) : this(item.asName)\n}\n\n/**\n * Exception indicates that item cannot be added to this config because it has child layer.\n */\nclass LayerFrozenException(val config: Config) :\n    ConfigException(\"config ${config.name} has child layer, cannot add new item\")\n\n/**\n * Exception indicates that expected value in specified path is not existed in the source.\n */\nclass NoSuchPathException(val path: String) :\n    ConfigException(\"cannot find path \\\"$path\\\" in config spec\")\n\n/**\n * Exception indicates that the specified path is invalid.\n */\nclass InvalidPathException(val path: String) :\n    ConfigException(\"\\\"$path\\\" is not a valid path\")\n\n/**\n * Exception indicates that the specified path conflicts with existed paths in the tree node.\n */\nclass PathConflictException(val path: String) :\n    ConfigException(\"\\\"$path\\\" conflicts with existed paths in the tree node\")\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/ConfigSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.module.kotlin.isKotlinClass\n\n/**\n * The default implementation for [Spec].\n *\n * @param prefix common prefix for items in this config spec\n */\nopen class ConfigSpec @JvmOverloads constructor(\n    prefix: String? = null,\n    items: Set<Item<*>> = mutableSetOf(),\n    innerSpecs: Set<Spec> = mutableSetOf()\n) : Spec {\n    final override val prefix: String = prefix ?: {\n        if (javaClass == ConfigSpec::class.java || javaClass.isAnonymousClass) {\n            \"\"\n        } else {\n            javaClass.let { clazz ->\n                if (this::class.isCompanion) clazz.declaringClass else clazz\n            }.simpleName.let { name ->\n                if (name == null || name.contains('$')) {\n                    \"\"\n                } else {\n                    name.toLittleCase()\n                }\n            }.let { name ->\n                if (name.endsWith(\"Spec\")) {\n                    name.removeSuffix(\"Spec\")\n                } else {\n                    name\n                }\n            }\n        }\n    }()\n\n    init {\n        checkPath(this.prefix)\n    }\n\n    private val _items = items as? MutableSet<Item<*>> ?: items.toMutableSet()\n\n    override val items: Set<Item<*>> = _items\n\n    override fun addItem(item: Item<*>) {\n        if (item !in _items) {\n            _items += item\n        } else {\n            throw RepeatedItemException(item.name)\n        }\n    }\n\n    private val _innerSpecs = innerSpecs as? MutableSet<Spec> ?: innerSpecs.toMutableSet()\n\n    override val innerSpecs: Set<Spec> = _innerSpecs\n\n    override fun addInnerSpec(spec: Spec) {\n        if (spec !in _innerSpecs) {\n            _innerSpecs += spec\n        } else {\n            throw RepeatedInnerSpecException(spec)\n        }\n    }\n\n    init {\n        if (javaClass.isKotlinClass()) {\n            javaClass.kotlin.nestedClasses.map {\n                it.objectInstance\n            }.filterIsInstance<Spec>().forEach { spec ->\n                addInnerSpec(spec)\n            }\n        }\n    }\n\n    /**\n     * Specify a required item in this config spec.\n     *\n     * @param name item name without prefix\n     * @param description description for this item\n     * @return a property of a required item with prefix of this config spec\n     */\n    inline fun <reified T> required(name: String? = null, description: String = \"\") =\n        object : RequiredProperty<T>(this, name, description, null is T) {}\n\n    /**\n     * Specify an optional item in this config spec.\n     *\n     * @param default default value returned before associating this item with specified value\n     * @param name item name without prefix\n     * @param description description for this item\n     *\n     * @return a property of an optional item with prefix of this config spec\n     */\n    inline fun <reified T> optional(default: T, name: String? = null, description: String = \"\") =\n        object : OptionalProperty<T>(this, default, name, description, null is T) {}\n\n    /**\n     * Specify a lazy item in this config spec.\n     *\n     * @param name item name without prefix\n     * @param description description for this item\n     * @param thunk thunk used to evaluate value for this item\n     * @return a property of a lazy item with prefix of this config spec\n     */\n    inline fun <reified T> lazy(\n        name: String? = null,\n        description: String = \"\",\n        noinline thunk: (config: ItemContainer) -> T\n    ) =\n        object : LazyProperty<T>(this, thunk, name, description, null is T) {}\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Feature.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\n/**\n * Enumeration that defines simple on/off features.\n */\nenum class Feature(val enabledByDefault: Boolean) {\n    /**\n     * Feature that determines what happens when unknown paths appear in the source.\n     * If enabled, an exception is thrown when loading from the source\n     * to indicate it contains unknown paths.\n     *\n     * Feature is disabled by default.\n     */\n    FAIL_ON_UNKNOWN_PATH(false),\n    /**\n     * Feature that determines whether loading keys from sources case-insensitively.\n     *\n     * Feature is disabled by default.\n     */\n    LOAD_KEYS_CASE_INSENSITIVELY(false),\n    /**\n     * Feature that determines whether loading keys from sources as little camel case.\n     *\n     * Feature is enabled by default.\n     */\n    LOAD_KEYS_AS_LITTLE_CAMEL_CASE(true),\n    /**\n     * Feature that determines whether sources are optional by default.\n     *\n     * Feature is disabled by default.\n     */\n    OPTIONAL_SOURCE_BY_DEFAULT(false),\n    /**\n     * Feature that determines whether sources should be substituted before loaded into config.\n     *\n     * Feature is enabled by default.\n     */\n    SUBSTITUTE_SOURCE_BEFORE_LOADED(true)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Item.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.databind.JavaType\nimport com.fasterxml.jackson.databind.type.TypeFactory\n\n/**\n * Item that can be contained by config.\n *\n * Item can be associated with value in config, containing metadata for the value.\n * The metadata for value includes name, path, type, description and so on.\n * Item can be used as key to operate value in config, guaranteeing type safety.\n * There are three kinds of item: [required item][RequiredItem], [optional item][OptionalItem]\n * and [lazy item][LazyItem].\n *\n * @param T type of value that can be associated with this item.\n * @param spec config spec that contains this item\n * @param name item name without prefix\n * @param description description for this item\n * @see Config\n */\nsealed class Item<T>(\n    /**\n     * Config spec that contains this item.\n     */\n    val spec: Spec,\n    /**\n     * Item name without prefix.\n     */\n    val name: String,\n    /**\n     * Description for this item.\n     */\n    val description: String = \"\",\n    type: JavaType? = null,\n    val nullable: Boolean = false\n) {\n    init {\n        checkPath(name)\n        @Suppress(\"LeakingThis\")\n        spec.addItem(this)\n    }\n\n    /**\n     * Item path without prefix.\n     */\n    val path: Path = this.name.toPath()\n\n    /**\n     * Type of value that can be associated with this item.\n     */\n    @Suppress(\"LeakingThis\")\n    val type: JavaType = type ?: TypeFactory.defaultInstance().constructType(this::class.java)\n        .findSuperType(Item::class.java).bindings.typeParameters[0]\n\n    /**\n     * Whether this is a required item or not.\n     */\n    open val isRequired: Boolean get() = false\n\n    /**\n     * Whether this is an optional item or not.\n     */\n    open val isOptional: Boolean get() = false\n\n    /**\n     * Whether this is a lazy item or not.\n     */\n    open val isLazy: Boolean get() = false\n\n    /**\n     * Cast this item to a required item.\n     */\n    val asRequiredItem: RequiredItem<T> get() = this as RequiredItem<T>\n\n    /**\n     * Cast this item to an optional item.\n     */\n    val asOptionalItem: OptionalItem<T> get() = this as OptionalItem<T>\n\n    /**\n     * Cast this item to a lazy item.\n     */\n    val asLazyItem: LazyItem<T> get() = this as LazyItem<T>\n\n    private val onSetFunctions: MutableList<(value: T) -> Unit> = mutableListOf()\n    private val beforeSetFunctions: MutableList<(config: Config, value: T) -> Unit> = mutableListOf()\n    private val afterSetFunctions: MutableList<(config: Config, value: T) -> Unit> = mutableListOf()\n\n    /**\n     * Subscribe the update event of this item.\n     *\n     * @param onSetFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun onSet(onSetFunction: (value: T) -> Unit): Handler {\n        onSetFunctions += onSetFunction\n        return object : Handler {\n            override fun cancel() {\n                onSetFunctions.remove(onSetFunction)\n            }\n        }\n    }\n\n    /**\n     * Subscribe the update event of this item before every set operation.\n     *\n     * @param beforeSetFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun beforeSet(beforeSetFunction: (config: Config, value: T) -> Unit): Handler {\n        beforeSetFunctions += beforeSetFunction\n        return object : Handler {\n            override fun cancel() {\n                beforeSetFunctions.remove(beforeSetFunction)\n            }\n        }\n    }\n\n    /**\n     * Subscribe the update event of this item after every set operation.\n     *\n     * @param afterSetFunction the subscription function\n     * @return the handler to cancel this subscription\n     */\n    fun afterSet(afterSetFunction: (config: Config, value: T) -> Unit): Handler {\n        afterSetFunctions += afterSetFunction\n        return object : Handler {\n            override fun cancel() {\n                afterSetFunctions.remove(afterSetFunction)\n            }\n        }\n    }\n\n    fun notifySet(value: Any?) {\n        for (onSetFunction in onSetFunctions) {\n            @Suppress(\"UNCHECKED_CAST\")\n            onSetFunction(value as T)\n        }\n    }\n\n    fun notifyBeforeSet(config: Config, value: Any?) {\n        for (beforeSetFunction in beforeSetFunctions) {\n            @Suppress(\"UNCHECKED_CAST\")\n            beforeSetFunction(config, value as T)\n        }\n    }\n\n    fun notifyAfterSet(config: Config, value: Any?) {\n        for (afterSetFunction in afterSetFunctions) {\n            @Suppress(\"UNCHECKED_CAST\")\n            afterSetFunction(config, value as T)\n        }\n    }\n}\n\ninterface Handler : AutoCloseable {\n    fun cancel()\n\n    override fun close() {\n        cancel()\n    }\n}\n\n/**\n * Type of Item path.\n */\ntypealias Path = List<String>\n\n/**\n * Returns corresponding item name of the item path.\n *\n * @receiver item path\n * @return item name\n */\nval Path.name: String get() = joinToString(\".\")\n\n/**\n * Returns corresponding item path of the item name.\n *\n * @receiver item name\n * @return item path\n */\nfun String.toPath(): Path {\n    val name = this.trim()\n    return if (name.isEmpty()) {\n        listOf()\n    } else {\n        val path = name.split('.')\n        if (\"\" in path) {\n            throw InvalidPathException(this)\n        }\n        path\n    }\n}\n\nfun checkPath(path: String) {\n    val trimmedPath = path.trim()\n    if (trimmedPath.isNotEmpty()) {\n        if (\"\" in trimmedPath.split('.')) {\n            throw InvalidPathException(path)\n        }\n    }\n}\n\n/**\n * Required item without default value.\n *\n * Required item must be set with value before retrieved in config.\n */\nopen class RequiredItem<T> @JvmOverloads constructor(\n    spec: Spec,\n    name: String,\n    description: String = \"\",\n    type: JavaType? = null,\n    nullable: Boolean = false\n) : Item<T>(spec, name, description, type, nullable) {\n    override val isRequired: Boolean = true\n}\n\n/**\n * Optional item with default value.\n *\n * Before associated with specified value, default value will be returned when accessing.\n * After associated with specified value, the specified value will be returned when accessing.\n */\nopen class OptionalItem<T> @JvmOverloads constructor(\n    spec: Spec,\n    name: String,\n    /**\n     * Default value returned before associating this item with specified value.\n     */\n    val default: T,\n    description: String = \"\",\n    type: JavaType? = null,\n    nullable: Boolean = false\n) : Item<T>(spec, name, description, type, nullable) {\n    init {\n        if (!nullable) {\n            requireNotNull<Any>(default)\n        }\n    }\n\n    override val isOptional: Boolean = true\n}\n\n/**\n * Lazy item evaluated value every time from thunk before associated with specified value.\n *\n * Before associated with specified value, value evaluated from thunk will be returned when accessing.\n * After associated with specified value, the specified value will be returned when accessing.\n * Returned value of the thunk will not be cached. The thunk will be evaluated every time\n * when needed to reflect modifying of other values in config.\n */\nopen class LazyItem<T> @JvmOverloads constructor(\n    spec: Spec,\n    name: String,\n    /**\n     * Thunk used to evaluate value for this item.\n     *\n     * [ItemContainer] is provided as evaluation environment to avoid unexpected modification\n     * to config.\n     * Thunk will be evaluated every time when needed to reflect modifying of other values in config.\n     */\n    val thunk: (config: ItemContainer) -> T,\n    description: String = \"\",\n    type: JavaType? = null,\n    nullable: Boolean = false\n) : Item<T>(spec, name, description, type, nullable) {\n    override val isLazy: Boolean = true\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/ItemContainer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\n/**\n * Container of items.\n *\n * Item container contains read-only access operations for item.\n *\n * @see Config\n */\ninterface ItemContainer : Iterable<Item<*>> {\n    /**\n     * Get associated value with specified item.\n     *\n     * @param item config item\n     * @return associated value\n     */\n    operator fun <T> get(item: Item<T>): T\n\n    /**\n     * Get associated value with specified item name.\n     *\n     * @param name item name\n     * @return associated value\n     */\n    operator fun <T> get(name: String): T\n\n    /**\n     * Returns associated value if specified item exists, `null` otherwise.\n     *\n     * @param item config item\n     * @return associated value if specified item exists, `null` otherwise\n     */\n    fun <T> getOrNull(item: Item<T>): T?\n\n    /**\n     * Returns associated value if specified item name exists, `null` otherwise.\n     *\n     * @param name item name\n     * @return associated value if specified item name exists, `null` otherwise\n     */\n    fun <T> getOrNull(name: String): T?\n\n    /**\n     * Get associated value with specified item name.\n     *\n     * @param name item name\n     * @return associated value\n     */\n    operator fun <T> invoke(name: String): T = get(name)\n\n    /**\n     * Returns iterator of items in this item container.\n     *\n     * @return iterator of items in this item container\n     */\n    override operator fun iterator(): Iterator<Item<*>>\n\n    /**\n     * Whether this item container contains specified item or not.\n     *\n     * @param item config item\n     * @return `true` if this item container contains specified item, `false` otherwise\n     */\n    operator fun contains(item: Item<*>): Boolean\n\n    /**\n     * Whether this item container contains item with specified name or not.\n     *\n     * @param name item name\n     * @return `true` if this item container contains item with specified name, `false` otherwise\n     */\n    operator fun contains(name: String): Boolean\n\n    /**\n     * Whether this item container contains the specified path or not.\n     *\n     * @param path the specified path\n     * @return `true` if this item container contains the specified path, `false` otherwise\n     */\n    operator fun contains(path: Path): Boolean\n\n    /**\n     * Returns the qualified name of the specified item.\n     *\n     * @param item the specified item\n     * @return the qualified name of the specified item\n     */\n    fun nameOf(item: Item<*>): String\n\n    /**\n     * Returns the qualified path of the specified item.\n     *\n     * @param item the specified item\n     * @return the qualified path of the specified item\n     */\n    fun pathOf(item: Item<*>): Path = nameOf(item).toPath()\n\n    /**\n     * List of items in this item container.\n     */\n    val items: List<Item<*>>\n        get() = mutableListOf<Item<*>>().apply {\n            addAll(this@ItemContainer.iterator().asSequence())\n        }\n\n    /**\n     * List of qualified names of items in this item container.\n     */\n    val nameOfItems: List<String> get() = itemWithNames.map { it.second }\n\n    /**\n     * List of items with the corresponding qualified names in this item container.\n     */\n    val itemWithNames: List<Pair<Item<*>, String>>\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/MergedConfig.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.uchuhimo.konf.source.Source\n\n/**\n * Config that merge [fallback] and [facade].\n *\n * All operations will be applied to [facade] first, and then fall back to [facade] when necessary.\n */\nopen class MergedConfig(val fallback: BaseConfig, val facade: BaseConfig) :\n    BaseConfig(\"merged(facade=${facade.name.notEmptyOr(\"\\\"\\\"\")}, fallback=${fallback.name.notEmptyOr(\"\\\"\\\"\")})\") {\n\n    override fun rawSet(item: Item<*>, value: Any?) {\n        if (item in facade) {\n            facade.rawSet(item, value)\n        } else {\n            fallback.rawSet(item, value)\n        }\n    }\n\n    override fun getItemOrNull(name: String): Item<*>? {\n        return facade.getItemOrNull(name) ?: fallback.getItemOrNull(name)\n    }\n\n    override fun <T> lazySet(item: Item<T>, thunk: (config: ItemContainer) -> T) {\n        if (item in facade) {\n            facade.lazySet(item, thunk)\n        } else {\n            fallback.lazySet(item, thunk)\n        }\n    }\n\n    override fun unset(item: Item<*>) {\n        if (item in facade) {\n            facade.unset(item)\n        } else {\n            fallback.unset(item)\n        }\n    }\n\n    override fun addBeforeLoadFunction(beforeLoadFunction: (source: Source) -> Unit) {\n        facade.addBeforeLoadFunction(beforeLoadFunction)\n        fallback.addBeforeLoadFunction(beforeLoadFunction)\n    }\n\n    override fun removeBeforeLoadFunction(beforeLoadFunction: (source: Source) -> Unit) {\n        facade.removeBeforeLoadFunction(beforeLoadFunction)\n        fallback.removeBeforeLoadFunction(beforeLoadFunction)\n    }\n\n    override fun addAfterLoadFunction(afterLoadFunction: (source: Source) -> Unit) {\n        facade.addAfterLoadFunction(afterLoadFunction)\n        fallback.addAfterLoadFunction(afterLoadFunction)\n    }\n\n    override fun removeAfterLoadFunction(afterLoadFunction: (source: Source) -> Unit) {\n        facade.removeAfterLoadFunction(afterLoadFunction)\n        fallback.removeAfterLoadFunction(afterLoadFunction)\n    }\n\n    override fun addBeforeSetFunction(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        facade.addBeforeSetFunction(beforeSetFunction)\n        fallback.addBeforeSetFunction(beforeSetFunction)\n    }\n\n    override fun removeBeforeSetFunction(beforeSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        facade.removeBeforeSetFunction(beforeSetFunction)\n        fallback.removeBeforeSetFunction(beforeSetFunction)\n    }\n\n    override fun addAfterSetFunction(afterSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        facade.addAfterSetFunction(afterSetFunction)\n        fallback.addAfterSetFunction(afterSetFunction)\n    }\n\n    override fun removeAfterSetFunction(afterSetFunction: (item: Item<*>, value: Any?) -> Unit) {\n        facade.removeAfterSetFunction(afterSetFunction)\n        fallback.removeAfterSetFunction(afterSetFunction)\n    }\n\n    override fun clear() {\n        facade.clear()\n        fallback.clear()\n    }\n\n    override fun clearAll() {\n        facade.clearAll()\n        fallback.clearAll()\n    }\n\n    override val specs: List<Spec>\n        get() = facade.specs + fallback.specs\n\n    override val sources: List<Source>\n        get() = facade.sources.toMutableList().apply {\n            for (source in fallback.sources) {\n                add(source)\n            }\n        }\n\n    override fun addItem(item: Item<*>, prefix: String) {\n        val path = prefix.toPath() + item.name.toPath()\n        val name = path.name\n        if (item !in fallback) {\n            if (path in fallback) {\n                throw NameConflictException(\"item $name cannot be added\")\n            }\n        } else {\n            throw RepeatedItemException(name)\n        }\n        facade.addItem(item, prefix)\n    }\n\n    override fun addSpec(spec: Spec) {\n        spec.items.forEach { item ->\n            val name = spec.qualify(item)\n            if (item !in fallback) {\n                val path = name.toPath()\n                if (path in fallback) {\n                    throw NameConflictException(\"item $name cannot be added\")\n                }\n            } else {\n                throw RepeatedItemException(name)\n            }\n        }\n        facade.addSpec(spec)\n    }\n\n    override fun <T> lock(action: () -> T): T = facade.lock { fallback.lock(action) }\n\n    override fun getOrNull(\n        item: Item<*>,\n        errorWhenNotFound: Boolean,\n        errorWhenGetDefault: Boolean,\n        lazyContext: ItemContainer\n    ): Any? {\n        if (item in facade && item in fallback) {\n            try {\n                return facade.getOrNull(item, errorWhenNotFound, true, lazyContext)\n            } catch (ex: Exception) {\n                when (ex) {\n                    is UnsetValueException -> {\n                        return fallback.getOrNull(item, errorWhenNotFound, errorWhenGetDefault, lazyContext)\n                    }\n                    is GetDefaultValueException -> {\n                        try {\n                            return fallback.getOrNull(item, errorWhenNotFound, errorWhenGetDefault, lazyContext)\n                        } catch (ex: Exception) {\n                            when (ex) {\n                                is UnsetValueException -> {\n                                    if (errorWhenGetDefault) {\n                                        throw GetDefaultValueException(item)\n                                    } else {\n                                        return (item as OptionalItem).default\n                                    }\n                                }\n                                else -> throw ex\n                            }\n                        }\n                    }\n                    else -> throw ex\n                }\n            }\n        } else if (item in facade) {\n            return facade.getOrNull(item, errorWhenNotFound, errorWhenGetDefault, lazyContext)\n        } else {\n            return fallback.getOrNull(item, errorWhenNotFound, errorWhenGetDefault, lazyContext)\n        }\n    }\n\n    override fun iterator(): Iterator<Item<*>> =\n        (facade.iterator().asSequence() + fallback.iterator().asSequence()).iterator()\n\n    override fun contains(item: Item<*>): Boolean = item in facade || item in fallback\n\n    override fun contains(name: String): Boolean = name in facade || name in fallback\n\n    override fun contains(path: Path): Boolean = path in facade || path in fallback\n\n    override fun nameOf(item: Item<*>): String {\n        return if (item in facade) {\n            facade.nameOf(item)\n        } else {\n            fallback.nameOf(item)\n        }\n    }\n\n    override val itemWithNames: List<Pair<Item<*>, String>>\n        get() = facade.itemWithNames + fallback.itemWithNames\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/MergedMap.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nclass MergedMap<K, V>(val fallback: MutableMap<K, V>, val facade: MutableMap<K, V>) : MutableMap<K, V> {\n    override val size: Int\n        get() = keys.size\n\n    override fun containsKey(key: K): Boolean {\n        return facade.containsKey(key) || fallback.containsKey(key)\n    }\n\n    override fun containsValue(value: V): Boolean {\n        return facade.containsValue(value) || fallback.containsValue(value)\n    }\n\n    override fun get(key: K): V? {\n        return facade[key] ?: fallback[key]\n    }\n\n    override fun isEmpty(): Boolean {\n        return facade.isEmpty() && fallback.isEmpty()\n    }\n\n    override val entries: MutableSet<MutableMap.MutableEntry<K, V>>\n        get() = keys.map { it to getValue(it) }.toMap(LinkedHashMap()).entries\n    override val keys: MutableSet<K>\n        get() = facade.keys.union(fallback.keys).toMutableSet()\n    override val values: MutableCollection<V>\n        get() = keys.map { getValue(it) }.toMutableList()\n\n    override fun clear() {\n        facade.clear()\n        fallback.clear()\n    }\n\n    override fun put(key: K, value: V): V? {\n        return facade.put(key, value)\n    }\n\n    override fun putAll(from: Map<out K, V>) {\n        facade.putAll(from)\n    }\n\n    override fun remove(key: K): V? {\n        if (key in facade) {\n            if (key in fallback) {\n                fallback.remove(key)\n            }\n            return facade.remove(key)\n        } else {\n            return fallback.remove(key)\n        }\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Prefix.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.uchuhimo.konf.source.Source\n\n/**\n * Convenient class for constructing [Spec]/[Config]/[Source] with prefix.\n */\ndata class Prefix(\n    /**\n     * The path of the prefix\n     */\n    val path: String = \"\"\n) {\n    /**\n     * Returns a config spec with this prefix.\n     *\n     * @param spec the base config spec\n     * @return a config spec with this prefix\n     */\n    operator fun plus(spec: Spec): Spec = spec.withPrefix(path)\n\n    /**\n     * Returns a config with this prefix.\n     *\n     * @param config the base config\n     * @return a config with this prefix\n     */\n    operator fun plus(config: Config): Config = config.withPrefix(path)\n\n    /**\n     * Returns a source with this prefix.\n     *\n     * @param source the base source\n     * @return a source with this prefix\n     */\n    operator fun plus(source: Source): Source = source.withPrefix(path)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/SizeInBytes.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.annotation.JsonCreator\nimport com.fasterxml.jackson.annotation.JsonValue\nimport com.uchuhimo.konf.source.ParseException\nimport java.io.Serializable\nimport java.math.BigDecimal\nimport java.math.BigInteger\n\n/**\n * Represents size in unit of bytes.\n */\ndata class SizeInBytes(\n    /**\n     * Number of bytes.\n     */\n    @JsonValue\n    val bytes: Long\n) : Serializable {\n    init {\n        require(bytes >= 0)\n    }\n\n    companion object {\n        /**\n         * Parses a size-in-bytes string. If no units are specified in the string,\n         * it is assumed to be in bytes. The returned value is in bytes.\n         *\n         * @param input the string to parse\n         * @return size in bytes\n         */\n        @JsonCreator\n        @JvmStatic\n        fun parse(input: String): SizeInBytes {\n            val s = input.trim()\n            val unitString = getUnits(s)\n            val numberString = s.substring(\n                0,\n                s.length - unitString.length\n            ).trim()\n\n            // this would be caught later anyway, but the error message\n            // is more helpful if we check it here.\n            if (numberString.isEmpty())\n                throw ParseException(\"No number in size-in-bytes value '$input'\")\n\n            val units = MemoryUnit.parseUnit(unitString)\n                ?: throw ParseException(\n                    \"Could not parse size-in-bytes unit '$unitString'\" +\n                        \" (try k, K, kB, KiB, kilobytes, kibibytes)\"\n                )\n\n            try {\n                val result: BigInteger\n                // if the string is purely digits, parse as an integer to avoid\n                // possible precision loss; otherwise as a double.\n                result = if (numberString.matches(\"[0-9]+\".toRegex())) {\n                    units.bytes.multiply(BigInteger(numberString))\n                } else {\n                    val resultDecimal = BigDecimal(units.bytes).multiply(BigDecimal(numberString))\n                    resultDecimal.toBigInteger()\n                }\n                return if (result.bitLength() < 64) {\n                    SizeInBytes(result.toLong())\n                } else {\n                    throw ParseException(\"size-in-bytes value is out of range for a 64-bit long: '$input'\")\n                }\n            } catch (e: NumberFormatException) {\n                throw ParseException(\"Could not parse size-in-bytes number '$numberString'\")\n            }\n        }\n\n        private enum class Radix {\n            KILO {\n                override fun toInt(): Int = 1000\n            },\n            KIBI {\n                override fun toInt(): Int = 1024\n            };\n\n            abstract fun toInt(): Int\n        }\n\n        private enum class MemoryUnit(\n            private val prefix: String,\n            private val radix: Radix,\n            private val power: Int\n        ) {\n            BYTES(\"\", Radix.KIBI, 0),\n\n            KILOBYTES(\"kilo\", Radix.KILO, 1),\n            MEGABYTES(\"mega\", Radix.KILO, 2),\n            GIGABYTES(\"giga\", Radix.KILO, 3),\n            TERABYTES(\"tera\", Radix.KILO, 4),\n            PETABYTES(\"peta\", Radix.KILO, 5),\n            EXABYTES(\"exa\", Radix.KILO, 6),\n            ZETTABYTES(\"zetta\", Radix.KILO, 7),\n            YOTTABYTES(\"yotta\", Radix.KILO, 8),\n\n            KIBIBYTES(\"kibi\", Radix.KIBI, 1),\n            MEBIBYTES(\"mebi\", Radix.KIBI, 2),\n            GIBIBYTES(\"gibi\", Radix.KIBI, 3),\n            TEBIBYTES(\"tebi\", Radix.KIBI, 4),\n            PEBIBYTES(\"pebi\", Radix.KIBI, 5),\n            EXBIBYTES(\"exbi\", Radix.KIBI, 6),\n            ZEBIBYTES(\"zebi\", Radix.KIBI, 7),\n            YOBIBYTES(\"yobi\", Radix.KIBI, 8);\n\n            internal val bytes: BigInteger = BigInteger.valueOf(radix.toInt().toLong()).pow(power)\n\n            companion object {\n\n                private val unitsMap = mutableMapOf<String, MemoryUnit>().apply {\n                    for (unit in MemoryUnit.values()) {\n                        put(unit.prefix + \"byte\", unit)\n                        put(unit.prefix + \"bytes\", unit)\n                        if (unit.prefix.isEmpty()) {\n                            put(\"b\", unit)\n                            put(\"B\", unit)\n                            put(\"\", unit) // no unit specified means bytes\n                        } else {\n                            val first = unit.prefix.substring(0, 1)\n                            val firstUpper = first.toUpperCase()\n                            when (unit.radix) {\n                                Radix.KILO -> {\n                                    if (unit.power == 1) {\n                                        put(first + \"B\", unit) // 512kB\n                                    } else {\n                                        put(firstUpper + \"B\", unit) // 512MB\n                                    }\n                                }\n                                Radix.KIBI -> {\n                                    put(first, unit) // 512m\n                                    put(firstUpper, unit) // 512M\n                                    put(firstUpper + \"i\", unit) // 512Mi\n                                    put(firstUpper + \"iB\", unit) // 512MiB\n                                }\n                            }\n                        }\n                    }\n                }\n\n                internal fun parseUnit(unit: String): MemoryUnit? {\n                    return unitsMap[unit]\n                }\n            }\n        }\n    }\n}\n\n/**\n * Converts a string to [SizeInBytes].\n */\nfun String.toSizeInBytes(): SizeInBytes = SizeInBytes.parse(this)\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Spec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.databind.JavaType\nimport com.fasterxml.jackson.databind.type.TypeFactory\nimport kotlin.properties.PropertyDelegateProvider\nimport kotlin.properties.ReadOnlyProperty\nimport kotlin.reflect.KProperty\n\n/**\n * Config spec is specification for config.\n *\n * Config spec describes a group of items with common prefix, which can be loaded into config\n * together using [Config.addSpec].\n * Config spec also provides convenient API to specify item in it without hand-written object\n * declaration.\n *\n * @see Config\n */\ninterface Spec {\n    /**\n     * Common prefix for items in this config spec.\n     *\n     * An empty prefix means names of items in this config spec are unqualified.\n     */\n    val prefix: String\n\n    /**\n     * Qualify item name with prefix of this config spec.\n     *\n     * When prefix is empty, original item name will be returned.\n     *\n     * @param item the config item\n     * @return qualified item name\n     */\n    fun qualify(item: Item<*>): String = (prefix.toPath() + item.path).name\n\n    /**\n     * Add the specified item into this config spec.\n     *\n     * @param item the specified item\n     */\n    fun addItem(item: Item<*>)\n\n    /**\n     * Set of specified items in this config spec.\n     */\n    val items: Set<Item<*>>\n\n    /**\n     * Add the specified inner spec into this config spec.\n     *\n     * @param spec the specified spec\n     */\n    fun addInnerSpec(spec: Spec)\n\n    /**\n     * Set of inner specs in this config spec.\n     */\n    val innerSpecs: Set<Spec>\n\n    /**\n     * Returns a config spec overlapped by the specified facade config spec.\n     *\n     * New items will be added to the facade config spec.\n     *\n     * @param spec the facade config spec\n     * @return a config spec overlapped by the specified facade config spec\n     */\n    operator fun plus(spec: Spec): Spec {\n        return object : Spec by spec {\n            override fun addItem(item: Item<*>) {\n                if (item !in this@Spec.items) {\n                    spec.addItem(item)\n                } else {\n                    throw RepeatedItemException(item.name)\n                }\n            }\n\n            override val items: Set<Item<*>>\n                get() = this@Spec.items + spec.items\n\n            override fun qualify(item: Item<*>): String {\n                return if (item in spec.items) {\n                    spec.qualify(item)\n                } else {\n                    this@Spec.qualify(item)\n                }\n            }\n        }\n    }\n\n    /**\n     * Returns a config spec backing by the specified fallback config spec.\n     *\n     * New items will be added to the current config spec.\n     *\n     * @param spec the fallback config spec\n     * @return a config spec backing by the specified fallback config spec\n     */\n    fun withFallback(spec: Spec): Spec = spec + this\n\n    /**\n     * Returns sub-spec in the specified path.\n     *\n     * @param path the specified path\n     * @return sub-source with specified prefix\n     */\n    operator fun get(path: String): Spec = get(prefix.toPath(), path.toPath())\n\n    private fun get(prefix: Path, path: Path): Spec {\n        return if (path.isEmpty()) {\n            this\n        } else if (prefix.size >= path.size && prefix.subList(0, path.size) == path) {\n            ConfigSpec(prefix.subList(path.size, prefix.size).name, items, innerSpecs)\n        } else {\n            if (prefix.size < path.size && path.subList(0, prefix.size) == prefix) {\n                val pathForInnerSpec = path.subList(prefix.size, path.size).name\n                val filteredInnerSpecs = innerSpecs.mapNotNull { spec ->\n                    try {\n                        spec[pathForInnerSpec]\n                    } catch (_: NoSuchPathException) {\n                        null\n                    }\n                }\n                if (filteredInnerSpecs.isEmpty()) {\n                    throw NoSuchPathException(path.name)\n                } else if (filteredInnerSpecs.size == 1) {\n                    return filteredInnerSpecs[0]\n                } else {\n                    ConfigSpec(\"\", emptySet(), filteredInnerSpecs.toMutableSet())\n                }\n            } else {\n                throw NoSuchPathException(path.name)\n            }\n        }\n    }\n\n    /**\n     * Returns config spec with the specified additional prefix.\n     *\n     * @param prefix additional prefix\n     * @return config spec with the specified additional prefix\n     */\n    fun withPrefix(prefix: String): Spec = withPrefix(this.prefix.toPath(), prefix.toPath())\n\n    private fun withPrefix(prefix: Path, newPrefix: Path): Spec {\n        return if (newPrefix.isEmpty()) {\n            this\n        } else {\n            ConfigSpec((newPrefix + prefix).name, items, innerSpecs)\n        }\n    }\n\n    companion object {\n        /**\n         * A dummy implementation for [Spec].\n         *\n         * It will swallow all items added to it. Used for items belonged to no config spec.\n         */\n        val dummy: Spec = object : Spec {\n            override val prefix: String = \"\"\n\n            override fun addItem(item: Item<*>) {}\n\n            override val items: Set<Item<*>> = emptySet()\n\n            override fun addInnerSpec(spec: Spec) {}\n\n            override val innerSpecs: Set<Spec> = emptySet()\n        }\n    }\n}\n\n/**\n * Specify a required item in this config spec.\n *\n * @param name item name without prefix\n * @param description description for this item\n * @return a property of a required item with prefix of this config spec\n */\ninline fun <reified T> Spec.required(name: String? = null, description: String = \"\") =\n    object : RequiredProperty<T>(this, name, description, null is T) {}\n\nopen class RequiredProperty<T>(\n    private val spec: Spec,\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, RequiredItem<T>>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadOnlyProperty<Any?, RequiredItem<T>> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(RequiredProperty::class.java).bindings.typeParameters[0]\n        val item = object : RequiredItem<T>(\n            spec,\n            name\n                ?: property.name,\n            description,\n            type,\n            nullable\n        ) {}\n        return ReadOnlyProperty<Any?, RequiredItem<T>> { _, _ -> item }\n    }\n}\n\n/**\n * Specify an optional item in this config spec.\n *\n * @param default default value returned before associating this item with specified value\n * @param name item name without prefix\n * @param description description for this item\n *\n * @return a property of an optional item with prefix of this config spec\n */\ninline fun <reified T> Spec.optional(default: T, name: String? = null, description: String = \"\") =\n    object : OptionalProperty<T>(this, default, name, description, null is T) {}\n\nopen class OptionalProperty<T>(\n    private val spec: Spec,\n    private val default: T,\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, OptionalItem<T>>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadOnlyProperty<Any?, OptionalItem<T>> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(OptionalProperty::class.java).bindings.typeParameters[0]\n        val item = object : OptionalItem<T>(\n            spec,\n            name\n                ?: property.name,\n            default,\n            description,\n            type,\n            nullable\n        ) {}\n        return ReadOnlyProperty<Any?, OptionalItem<T>> { _, _ -> item }\n    }\n}\n\n/**\n * Specify a lazy item in this config spec.\n *\n * @param name item name without prefix\n * @param description description for this item\n * @param thunk thunk used to evaluate value for this item\n * @return a property of a lazy item with prefix of this config spec\n */\ninline fun <reified T> Spec.lazy(\n    name: String? = null,\n    description: String = \"\",\n    noinline thunk: (config: ItemContainer) -> T\n) =\n    object : LazyProperty<T>(this, thunk, name, description, null is T) {}\n\nopen class LazyProperty<T>(\n    private val spec: Spec,\n    private val thunk: (config: ItemContainer) -> T,\n    private val name: String? = null,\n    private val description: String = \"\",\n    private val nullable: Boolean = false\n) : PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, LazyItem<T>>> {\n    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):\n        ReadOnlyProperty<Any?, LazyItem<T>> {\n        val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)\n            .findSuperType(LazyProperty::class.java).bindings.typeParameters[0]\n        val item = object : LazyItem<T>(\n            spec,\n            name\n                ?: property.name,\n            thunk,\n            description,\n            type,\n            nullable\n        ) {}\n        return ReadOnlyProperty<Any?, LazyItem<T>> { _, _ -> item }\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/TreeNode.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport java.util.Collections\n\n/**\n * Tree node that represents internal structure of config/source.\n */\ninterface TreeNode {\n    /**\n     * Children nodes in this tree node with their names as keys.\n     */\n    val children: MutableMap<String, TreeNode>\n\n    /**\n     * Associate path with specified node.\n     *\n     * @param path path\n     * @param node associated node\n     */\n    operator fun set(path: Path, node: TreeNode) {\n        if (path.isEmpty()) {\n            throw PathConflictException(path.name)\n        }\n        val key = path.first()\n        if (this is LeafNode) {\n            throw PathConflictException(path.name)\n        }\n        try {\n            return if (path.size == 1) {\n                children[key] = node\n            } else {\n                val rest = path.drop(1)\n                var child = children[key]\n                if (child == null) {\n                    child = ContainerNode(mutableMapOf())\n                    children[key] = child\n                }\n                child[rest] = node\n            }\n        } catch (_: PathConflictException) {\n            throw PathConflictException(path.name)\n        } finally {\n            if (this is MapNode && isPlaceHolder) {\n                isPlaceHolder = false\n            }\n        }\n    }\n\n    /**\n     * Associate path with specified node.\n     *\n     * @param path path\n     * @param node associated node\n     */\n    operator fun set(path: String, node: TreeNode) {\n        set(path.toPath(), node)\n    }\n\n    /**\n     * Whether this tree node contains node(s) in specified path or not.\n     *\n     * @param path item path\n     * @return `true` if this tree node contains node(s) in specified path, `false` otherwise\n     */\n    operator fun contains(path: Path): Boolean {\n        return if (path.isEmpty()) {\n            true\n        } else {\n            val key = path.first()\n            val rest = path.drop(1)\n            val result = children[key]\n            if (result != null) {\n                return rest in result\n            } else {\n                return false\n            }\n        }\n    }\n\n    /**\n     * Returns tree node in specified path if this tree node contains value(s) in specified path,\n     * `null` otherwise.\n     *\n     * @param path item path\n     * @return tree node in specified path if this tree node contains value(s) in specified path,\n     * `null` otherwise\n     */\n    fun getOrNull(path: Path): TreeNode? {\n        return if (path.isEmpty()) {\n            this\n        } else {\n            val key = path.first()\n            val rest = path.drop(1)\n            val result = children[key]\n            result?.getOrNull(rest)\n        }\n    }\n\n    /**\n     * Returns tree node in specified path if this tree node contains value(s) in specified path,\n     * `null` otherwise.\n     *\n     * @param path item path\n     * @return tree node in specified path if this tree node contains value(s) in specified path,\n     * `null` otherwise\n     */\n    fun getOrNull(path: String): TreeNode? = getOrNull(path.toPath())\n\n    /**\n     * Returns a node backing by specified fallback node.\n     *\n     * @param fallback fallback node\n     * @return a node backing by specified fallback node\n     */\n    fun withFallback(fallback: TreeNode): TreeNode {\n        fun traverseTree(facade: TreeNode, fallback: TreeNode, path: Path): TreeNode {\n            if (facade is LeafNode || fallback is LeafNode) {\n                return facade\n            } else {\n                return ContainerNode(\n                    facade.children.toMutableMap().also { map ->\n                        for ((key, child) in fallback.children) {\n                            if (key in facade.children) {\n                                map[key] = traverseTree(facade.children.getValue(key), child, path + key)\n                            } else {\n                                map[key] = child\n                            }\n                        }\n                    }\n                )\n            }\n        }\n        return traverseTree(this, fallback, \"\".toPath())\n    }\n\n    /**\n     * Returns a node overlapped by the specified facade node.\n     *\n     * @param facade the facade node\n     * @return a node overlapped by the specified facade node\n     */\n    operator fun plus(facade: TreeNode): TreeNode = facade.withFallback(this)\n\n    /**\n     * Returns a tree node containing all nodes of the original tree node\n     * except the nodes contained in the given [other] tree node.\n     *\n     * @return a tree node\n     */\n    operator fun minus(other: TreeNode): TreeNode {\n        fun traverseTree(left: TreeNode, right: TreeNode): TreeNode {\n            if (left is LeafNode) {\n                return EmptyNode\n            } else {\n                if (right is LeafNode) {\n                    return EmptyNode\n                } else {\n                    val leftKeys = left.children.keys\n                    val rightKeys = right.children.keys\n                    val diffKeys = leftKeys - rightKeys\n                    val sharedKeys = leftKeys.intersect(rightKeys)\n                    val children = mutableMapOf<String, TreeNode>()\n                    diffKeys.forEach { key ->\n                        children[key] = left.children[key]!!\n                    }\n                    sharedKeys.forEach { key ->\n                        val child = traverseTree(left.children[key]!!, right.children[key]!!)\n                        if (child != EmptyNode) {\n                            children[key] = child\n                        }\n                    }\n                    return if (children.isEmpty()) {\n                        EmptyNode\n                    } else {\n                        ContainerNode(children)\n                    }\n                }\n            }\n        }\n        return traverseTree(this, other)\n    }\n\n    /**\n     * List of all paths in this tree node.\n     */\n    val paths: List<String>\n        get() {\n            return mutableListOf<String>().also { list ->\n                fun traverseTree(node: TreeNode, path: Path) {\n                    if (node is LeafNode) {\n                        list.add(path.name)\n                    } else {\n                        node.children.forEach { (key, child) ->\n                            traverseTree(child, path + key)\n                        }\n                    }\n                }\n                traverseTree(this, \"\".toPath())\n            }\n        }\n\n    fun firstPath(predicate: (TreeNode) -> Boolean): Path? {\n        fun traverseTree(node: TreeNode, path: Path): Path? {\n            if (predicate(node)) {\n                return path\n            } else {\n                node.children.forEach { (key, child) ->\n                    val matchPath = traverseTree(child, path + key)\n                    if (matchPath != null) {\n                        return matchPath\n                    }\n                }\n                return null\n            }\n        }\n        return traverseTree(this, \"\".toPath())\n    }\n\n    /**\n     * Map of all leaves indexed by paths in this tree node.\n     */\n    val leafByPath: Map<String, TreeNode>\n        get() {\n            return mutableMapOf<String, TreeNode>().also { map ->\n                fun traverseTree(node: TreeNode, path: Path) {\n                    if (node is LeafNode) {\n                        map[path.name] = node\n                    } else {\n                        node.children.forEach { (key, child) ->\n                            traverseTree(child, path + key)\n                        }\n                    }\n                }\n                traverseTree(this, \"\".toPath())\n            }\n        }\n\n    fun withoutPlaceHolder(): TreeNode {\n        when (this) {\n            is NullNode -> return this\n            is ValueNode -> return this\n            is ListNode -> return this\n            is MapNode -> {\n                val newChildren = children.mapValues { (_, child) ->\n                    child.withoutPlaceHolder()\n                }\n                if (newChildren.isNotEmpty() && newChildren.all { (_, child) -> child is MapNode && child.isPlaceHolder }) {\n                    return ContainerNode.placeHolder()\n                } else {\n                    return withMap(newChildren.filterValues { !(it is MapNode && it.isPlaceHolder) })\n                }\n            }\n            else -> return this\n        }\n    }\n\n    fun isEmpty(): Boolean {\n        when (this) {\n            is EmptyNode -> return true\n            is MapNode -> {\n                return children.isEmpty() || children.all { (_, child) -> child.isEmpty() }\n            }\n            else -> return false\n        }\n    }\n\n    fun isPlaceHolderNode(): Boolean {\n        when (this) {\n            is MapNode -> {\n                if (isPlaceHolder) {\n                    return true\n                } else {\n                    return children.isNotEmpty() && children.all { (_, child) -> child.isPlaceHolderNode() }\n                }\n            }\n            else -> return false\n        }\n    }\n}\n\ninterface LeafNode : TreeNode\n\ninterface MapNode : TreeNode {\n    fun withMap(map: Map<String, TreeNode>): MapNode = throw NotImplementedError()\n    var isPlaceHolder: Boolean\n}\n\nval emptyMutableMap: MutableMap<String, TreeNode> = Collections.unmodifiableMap(mutableMapOf())\n\ninterface ValueNode : LeafNode {\n    val value: Any\n    override val children: MutableMap<String, TreeNode>\n        get() = emptyMutableMap\n}\n\ninterface NullNode : LeafNode\n\ninterface ListNode : LeafNode {\n    val list: List<TreeNode>\n    fun withList(list: List<TreeNode>): ListNode = throw NotImplementedError()\n}\n\n/**\n * Tree node that contains children nodes.\n */\nopen class ContainerNode(\n    override val children: MutableMap<String, TreeNode>,\n    override var isPlaceHolder: Boolean = false\n) : MapNode {\n    override fun withMap(map: Map<String, TreeNode>): MapNode {\n        val isPlaceHolder = map.isEmpty() && this.isPlaceHolder\n        if (map is MutableMap<String, TreeNode>) {\n            return ContainerNode(map, isPlaceHolder)\n        } else {\n            return ContainerNode(map.toMutableMap(), isPlaceHolder)\n        }\n    }\n\n    companion object {\n        fun empty(): ContainerNode = ContainerNode(mutableMapOf())\n        fun placeHolder(): ContainerNode = ContainerNode(mutableMapOf(), true)\n    }\n}\n\n/**\n * Tree node that represents a empty tree.\n */\nobject EmptyNode : LeafNode {\n    override val children: MutableMap<String, TreeNode> = emptyMutableMap\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/Utils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport java.io.File\n\n/**\n * Throws [UnsupportedOperationException].\n *\n * @throws UnsupportedOperationException\n */\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun unsupported(): Nothing {\n    throw UnsupportedOperationException()\n}\n\ninternal fun getUnits(s: String): String {\n    var i = s.length - 1\n    while (i >= 0) {\n        val c = s[i]\n        if (!c.isLetter())\n            break\n        i -= 1\n    }\n    return s.substring(i + 1)\n}\n\n/**\n * Returns default value if string is empty, original string otherwise.\n */\nfun String.notEmptyOr(default: String): String = if (isEmpty()) default else this\n\nfun String.toLittleCase(): String {\n    return if (this.all { it.isUpperCase() }) {\n        this.toLowerCase()\n    } else {\n        when (val firstLowerCaseIndex = this.indexOfFirst { it.isLowerCase() }) {\n            -1, 0 -> this\n            1 -> this[0].toLowerCase() + this.drop(1)\n            else ->\n                this.substring(0, firstLowerCaseIndex - 1).toLowerCase() +\n                    this.substring(firstLowerCaseIndex - 1)\n        }\n    }\n}\n\n/**\n * Modified implementation from [org.apache.commons.text.CaseUtils.toCamelCase].\n */\nfun String.toCamelCase(): String {\n    if (isEmpty()) {\n        return this\n    }\n    val strLen = this.length\n    val newCodePoints = IntArray(strLen)\n    var outOffset = 0\n    val delimiterSet = setOf(\" \".codePointAt(0), \"_\".codePointAt(0))\n    var capitalizeNext = Character.isUpperCase(this.codePointAt(0))\n    var lowercaseNext = false\n    var previousIsUppercase = false\n    var index = 0\n    while (index < strLen) {\n        val codePoint: Int = this.codePointAt(index)\n        when {\n            delimiterSet.contains(codePoint) -> {\n                capitalizeNext = outOffset != 0\n                lowercaseNext = false\n                previousIsUppercase = false\n                index += Character.charCount(codePoint)\n            }\n            capitalizeNext -> {\n                val upperCaseCodePoint = Character.toUpperCase(codePoint)\n                newCodePoints[outOffset++] = upperCaseCodePoint\n                index += Character.charCount(upperCaseCodePoint)\n                capitalizeNext = false\n                lowercaseNext = true\n            }\n            lowercaseNext -> {\n                val lowerCaseCodePoint = Character.toLowerCase(codePoint)\n                newCodePoints[outOffset++] = lowerCaseCodePoint\n                index += Character.charCount(lowerCaseCodePoint)\n                if (Character.isLowerCase(codePoint)) {\n                    lowercaseNext = false\n                    if (previousIsUppercase) {\n                        previousIsUppercase = false\n                        val previousCodePoint = newCodePoints[outOffset - 2]\n                        val upperCaseCodePoint = Character.toUpperCase(previousCodePoint)\n                        newCodePoints[outOffset - 2] = upperCaseCodePoint\n                    }\n                } else {\n                    previousIsUppercase = true\n                }\n            }\n            else -> {\n                newCodePoints[outOffset++] = codePoint\n                index += Character.charCount(codePoint)\n            }\n        }\n    }\n    return if (outOffset != 0) {\n        String(newCodePoints, 0, outOffset)\n    } else this\n}\n\nfun String.toLittleCamelCase(): String {\n    return this.toCamelCase().toLittleCase()\n}\n\nfun tempDirectory(\n    prefix: String = \"tmp\",\n    suffix: String? = null,\n    directory: File? = null\n): File {\n    return createTempDir(prefix, suffix, directory)\n}\n\nfun tempFile(prefix: String = \"tmp\", suffix: String? = null, directory: File? = null): File {\n    return createTempFile(prefix, suffix, directory)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/annotation/Annotations.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.annotation\n\n/**\n * Indicates that this API is specially designed to be used in Java.\n */\n@Target(\n    AnnotationTarget.FUNCTION,\n    AnnotationTarget.CONSTRUCTOR,\n    AnnotationTarget.PROPERTY_GETTER,\n    AnnotationTarget.PROPERTY_SETTER,\n    AnnotationTarget.CLASS\n)\n@Retention(AnnotationRetention.SOURCE)\nannotation class JavaApi\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.base.FlatSource\nimport com.uchuhimo.konf.source.base.KVSource\nimport com.uchuhimo.konf.source.base.MapSource\nimport com.uchuhimo.konf.source.env.EnvProvider\nimport com.uchuhimo.konf.source.json.JsonProvider\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport kotlinx.coroutines.Dispatchers\nimport java.io.File\nimport java.net.URL\nimport java.util.concurrent.TimeUnit\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Default loaders for config.\n *\n * If [transform] is provided, source will be applied the given [transform] function when loaded.\n *\n * @param config parent config for loader\n * @param transform the given transformation function\n */\nclass DefaultLoaders(\n    /**\n     * Parent config for loader.\n     */\n    val config: Config,\n    /**\n     * The given transformation function.\n     */\n    private val transform: ((Source) -> Source)? = null\n) {\n    val optional = config.isEnabled(Feature.OPTIONAL_SOURCE_BY_DEFAULT)\n    fun Provider.orMapped(): Provider =\n        if (transform != null) this.map(transform) else this\n\n    fun Source.orMapped(): Source = transform?.invoke(this) ?: this\n\n    /**\n     * Returns default loaders applied the given [transform] function.\n     *\n     * @param transform the given transformation function\n     * @return the default loaders applied the given [transform] function\n     */\n    fun mapped(transform: (Source) -> Source): DefaultLoaders = DefaultLoaders(config) {\n        transform(it.orMapped())\n    }\n\n    /**\n     * Returns default loaders where sources have specified additional prefix.\n     *\n     * @param prefix additional prefix\n     * @return the default loaders where sources have specified additional prefix\n     */\n    fun prefixed(prefix: String): DefaultLoaders = mapped { it.withPrefix(prefix) }\n\n    /**\n     * Returns default loaders where sources are scoped in specified path.\n     *\n     * @param path path that is the scope of sources\n     * @return the default loaders where sources are scoped in specified path\n     */\n    fun scoped(path: String): DefaultLoaders = mapped { it[path] }\n\n    fun enabled(feature: Feature): DefaultLoaders = mapped { it.enabled(feature) }\n\n    fun disabled(feature: Feature): DefaultLoaders = mapped { it.disabled(feature) }\n\n    /**\n     * Loader for JSON source.\n     */\n    @JvmField\n    val json = Loader(config, JsonProvider.orMapped())\n\n    /**\n     * Loader for properties source.\n     */\n    @JvmField\n    val properties = Loader(config, PropertiesProvider.orMapped())\n\n    /**\n     * Loader for map source.\n     */\n    @JvmField\n    val map = MapLoader(config, transform)\n\n    /**\n     * Loader for a source from the specified provider.\n     *\n     * @param provider the specified provider\n     * @return a loader for a source from the specified provider\n     */\n    fun source(provider: Provider) = Loader(config, provider.orMapped())\n\n    /**\n     * Returns a child config containing values from system environment.\n     *\n     * @param nested whether to treat \"AA_BB_CC\" as nested format \"AA.BB.CC\" or not. True by default.\n     * @return a child config containing values from system environment\n     */\n    @JvmOverloads\n    fun env(nested: Boolean = true): Config = config.withSource(EnvProvider.env(nested).orMapped())\n\n    @JvmOverloads\n    fun envMap(map: Map<String, String>, nested: Boolean = true): Config = config.withSource(EnvProvider.envMap(map, nested).orMapped())\n\n    /**\n     * Returns a child config containing values from system properties.\n     *\n     * @return a child config containing values from system properties\n     */\n    fun systemProperties(): Config = config.withSource(PropertiesProvider.system().orMapped())\n\n    /**\n     * Returns corresponding loader based on extension.\n     *\n     * @param extension the file extension\n     * @param source the source description for error message\n     * @return the corresponding loader based on extension\n     */\n    fun dispatchExtension(extension: String, source: String = \"\"): Loader =\n        Loader(\n            config,\n            Provider.of(extension)?.orMapped()\n                ?: throw UnsupportedExtensionException(source)\n        )\n\n    /**\n     * Returns a child config containing values from specified file.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified file\n     * @throws UnsupportedExtensionException\n     */\n    fun file(file: File, optional: Boolean = this.optional): Config = dispatchExtension(file.extension, file.name).file(file, optional)\n\n    /**\n     * Returns a child config containing values from specified file path.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file path\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified file path\n     * @throws UnsupportedExtensionException\n     */\n    fun file(file: String, optional: Boolean = this.optional): Config = file(File(file), optional)\n\n    /**\n     * Returns a child config containing values from specified file,\n     * and reloads values when file content has been changed.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file\n     * @param delayTime delay to observe between every check. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated file is loaded\n     * @return a child config containing values from watched file\n     * @throws UnsupportedExtensionException\n     */\n    fun watchFile(\n        file: File,\n        delayTime: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config = dispatchExtension(file.extension, file.name)\n        .watchFile(file, delayTime, unit, context, optional, onLoad)\n\n    /**\n     * Returns a child config containing values from specified file path,\n     * and reloads values when file content has been changed.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file path\n     * @param delayTime delay to observe between every check. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated file is loaded\n     * @return a child config containing values from watched file\n     * @throws UnsupportedExtensionException\n     */\n    fun watchFile(\n        file: String,\n        delayTime: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config = watchFile(File(file), delayTime, unit, context, optional, onLoad)\n\n    /**\n     * Returns a child config containing values from specified url.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified url\n     * @throws UnsupportedExtensionException\n     */\n    fun url(url: URL, optional: Boolean = this.optional): Config = dispatchExtension(File(url.path).extension, url.toString()).url(url, optional)\n\n    /**\n     * Returns a child config containing values from specified url string.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url string\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified url string\n     * @throws UnsupportedExtensionException\n     */\n    fun url(url: String, optional: Boolean = this.optional): Config = url(URL(url), optional)\n\n    /**\n     * Returns a child config containing values from specified url,\n     * and reloads values periodically.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url\n     * @param period reload period. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated URL is loaded\n     * @return a child config containing values from specified url\n     * @throws UnsupportedExtensionException\n     */\n    fun watchUrl(\n        url: URL,\n        period: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config = dispatchExtension(File(url.path).extension, url.toString())\n        .watchUrl(url, period, unit, context, optional, onLoad)\n\n    /**\n     * Returns a child config containing values from specified url string,\n     * and reloads values periodically.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url string\n     * @param period reload period. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated URL is loaded\n     * @return a child config containing values from specified url string\n     * @throws UnsupportedExtensionException\n     */\n    fun watchUrl(\n        url: String,\n        period: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config = watchUrl(URL(url), period, unit, context, optional, onLoad)\n}\n\n/**\n * Loader to load source from map of variant formats.\n *\n * If [transform] is provided, source will be applied the given [transform] function when loaded.\n *\n * @param config parent config\n */\nclass MapLoader(\n    /**\n     * Parent config for all child configs loading source in this loader.\n     */\n    val config: Config,\n    /**\n     * The given transformation function.\n     */\n    private val transform: ((Source) -> Source)? = null\n) {\n    fun Source.orMapped(): Source = transform?.invoke(this) ?: this\n\n    /**\n     * Returns a child config containing values from specified hierarchical map.\n     *\n     * @param map a hierarchical map\n     * @return a child config containing values from specified hierarchical map\n     */\n    fun hierarchical(map: Map<String, Any>): Config = config.withSource(MapSource(map).orMapped())\n\n    /**\n     * Returns a child config containing values from specified map in key-value format.\n     *\n     * @param map a map in key-value format\n     * @return a child config containing values from specified map in key-value format\n     */\n    fun kv(map: Map<String, Any>): Config = config.withSource(KVSource(map).orMapped())\n\n    /**\n     * Returns a child config containing values from specified map in flat format.\n     *\n     * @param map a map in flat format\n     * @return a child config containing values from specified map in flat format\n     */\n    fun flat(map: Map<String, String>): Config = config.withSource(FlatSource(map).orMapped())\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultProviders.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.base.FlatSource\nimport com.uchuhimo.konf.source.base.KVSource\nimport com.uchuhimo.konf.source.base.MapSource\nimport com.uchuhimo.konf.source.env.EnvProvider\nimport com.uchuhimo.konf.source.json.JsonProvider\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport java.io.File\nimport java.net.URL\n\n/**\n * Default providers.\n */\nobject DefaultProviders {\n    /**\n     * Provider for JSON source.\n     */\n    @JvmField\n    val json = JsonProvider\n\n    /**\n     * Provider for properties source.\n     */\n    @JvmField\n    val properties = PropertiesProvider\n\n    /**\n     * Provider for map source.\n     */\n    @JvmField\n    val map = DefaultMapProviders\n\n    /**\n     * Returns a source from system environment.\n     *\n     * @param nested whether to treat \"AA_BB_CC\" as nested format \"AA.BB.CC\" or not. True by default.\n     * @return a source from system environment\n     */\n    @JvmOverloads\n    fun env(nested: Boolean = true): Source = EnvProvider.env(nested)\n\n    /**\n     * Returns a source from system properties.\n     *\n     * @return a source from system properties\n     */\n    fun systemProperties(): Source = PropertiesProvider.system()\n\n    /**\n     * Returns corresponding provider based on extension.\n     *\n     * @param extension the file extension\n     * @param source the source description for error message\n     * @return the corresponding provider based on extension\n     */\n    fun dispatchExtension(extension: String, source: String = \"\"): Provider =\n        Provider.of(extension) ?: throw UnsupportedExtensionException(source)\n\n    /**\n     * Returns a source from specified file.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file\n     * @param optional whether the source is optional\n     * @return a source from specified file\n     * @throws UnsupportedExtensionException\n     */\n    fun file(file: File, optional: Boolean = false): Source = dispatchExtension(file.extension, file.name).file(file, optional)\n\n    /**\n     * Returns a source from specified file path.\n     *\n     * Format of the file is auto-detected from the file extension.\n     * Supported file formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the file extension is unsupported.\n     *\n     * @param file specified file path\n     * @param optional whether the source is optional\n     * @return a source from specified file path\n     * @throws UnsupportedExtensionException\n     */\n    fun file(file: String, optional: Boolean = false): Source = file(File(file), optional)\n\n    /**\n     * Returns a source from specified url.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url\n     * @param optional whether the source is optional\n     * @return a source from specified url\n     * @throws UnsupportedExtensionException\n     */\n    fun url(url: URL, optional: Boolean = false): Source =\n        dispatchExtension(File(url.path).extension, url.toString()).url(url, optional)\n\n    /**\n     * Returns a source from specified url string.\n     *\n     * Format of the url is auto-detected from the url extension.\n     * Supported url formats and the corresponding extensions:\n     * - HOCON: conf\n     * - JSON: json\n     * - Properties: properties\n     * - TOML: toml\n     * - XML: xml\n     * - YAML: yml, yaml\n     *\n     * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n     *\n     * @param url specified url string\n     * @param optional whether the source is optional\n     * @return a source from specified url string\n     * @throws UnsupportedExtensionException\n     */\n    fun url(url: String, optional: Boolean = false): Source = url(URL(url), optional)\n}\n\n/**\n * Providers for map of variant formats.\n */\nobject DefaultMapProviders {\n    /**\n     * Returns a source from specified hierarchical map.\n     *\n     * @param map a hierarchical map\n     * @return a source from specified hierarchical map\n     */\n    fun hierarchical(map: Map<String, Any>): Source = MapSource(map)\n\n    /**\n     * Returns a source from specified map in key-value format.\n     *\n     * @param map a map in key-value format\n     * @return a source from specified map in key-value format\n     */\n    fun kv(map: Map<String, Any>): Source = KVSource(map)\n\n    /**\n     * Returns a source from specified map in flat format.\n     *\n     * @param map a map in flat format\n     * @return a source from specified map in flat format\n     */\n    fun flat(map: Map<String, String>): Source = FlatSource(map)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/Loader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.io.File\nimport java.io.InputStream\nimport java.io.Reader\nimport java.net.URL\nimport java.nio.file.FileSystems\nimport java.nio.file.Path\nimport java.nio.file.StandardWatchEventKinds\nimport java.nio.file.WatchEvent\nimport java.security.DigestInputStream\nimport java.security.MessageDigest\nimport java.util.concurrent.TimeUnit\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Loader to load source from various input formats.\n *\n * @param config parent config\n */\nclass Loader(\n    /**\n     * Parent config for all child configs loading source in this loader.\n     */\n    val config: Config,\n    /**\n     * Source provider to provide source from various input format.\n     */\n    val provider: Provider\n) {\n    val optional = config.isEnabled(Feature.OPTIONAL_SOURCE_BY_DEFAULT)\n\n    /**\n     * Returns a child config containing values from specified reader.\n     *\n     * @param reader specified reader for reading character streams\n     * @return a child config containing values from specified reader\n     */\n    fun reader(reader: Reader): Config =\n        config.withSource(provider.reader(reader))\n\n    /**\n     * Returns a child config containing values from specified input stream.\n     *\n     * @param inputStream specified input stream of bytes\n     * @return a child config containing values from specified input stream\n     */\n    fun inputStream(inputStream: InputStream): Config =\n        config.withSource(provider.inputStream(inputStream))\n\n    /**\n     * Returns a child config containing values from specified file.\n     *\n     * @param file specified file\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified file\n     */\n    fun file(file: File, optional: Boolean = this.optional): Config =\n        config.withSource(provider.file(file, optional))\n\n    /**\n     * Returns a child config containing values from specified file path.\n     *\n     * @param file specified file path\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified file path\n     */\n    fun file(file: String, optional: Boolean = this.optional): Config =\n        config.withSource(provider.file(file, optional))\n\n    private val File.digest: ByteArray\n        get() {\n            val messageDigest = MessageDigest.getInstance(\"MD5\")\n            DigestInputStream(inputStream().buffered(), messageDigest).use { it.readBytes() }\n            return messageDigest.digest()\n        }\n\n    /**\n     * Returns a child config containing values from specified file,\n     * and reloads values when file content has been changed.\n     *\n     * @param file specified file\n     * @param delayTime delay to observe between every check. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated file is loaded\n     * @return a child config containing values from watched file\n     */\n    fun watchFile(\n        file: File,\n        delayTime: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config {\n        val absoluteFile = file.absoluteFile\n        return provider.file(absoluteFile, optional).let { source ->\n            config.withLoadTrigger(\"watch ${source.description}\") { newConfig, load ->\n                newConfig.lock {\n                    load(source)\n                }\n                onLoad?.invoke(newConfig, source)\n                val path = absoluteFile.toPath().parent\n                val isMac = \"mac\" in System.getProperty(\"os.name\").toLowerCase()\n                val watcher = FileSystems.getDefault().newWatchService()\n                path.register(\n                    watcher,\n                    StandardWatchEventKinds.ENTRY_MODIFY,\n                    StandardWatchEventKinds.ENTRY_CREATE\n                )\n                var digest = absoluteFile.digest\n                GlobalScope.launch(context) {\n                    while (true) {\n                        delay(unit.toMillis(delayTime))\n                        if (isMac) {\n                            val newDigest = absoluteFile.digest\n                            if (!newDigest.contentEquals(digest)) {\n                                digest = newDigest\n                                val newSource = provider.file(file, optional)\n                                newConfig.lock {\n                                    newConfig.clear()\n                                    load(newSource)\n                                }\n                                onLoad?.invoke(newConfig, newSource)\n                            }\n                        } else {\n                            val key = watcher.poll()\n                            if (key != null) {\n                                for (event in key.pollEvents()) {\n                                    val kind = event.kind()\n                                    @Suppress(\"UNCHECKED_CAST\")\n                                    event as WatchEvent<Path>\n                                    val filename = event.context()\n                                    if (filename.toString() == absoluteFile.name) {\n                                        if (kind == StandardWatchEventKinds.OVERFLOW) {\n                                            continue\n                                        } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY ||\n                                            kind == StandardWatchEventKinds.ENTRY_CREATE\n                                        ) {\n                                            val newSource = provider.file(file, optional)\n                                            newConfig.lock {\n                                                newConfig.clear()\n                                                load(newSource)\n                                            }\n                                            onLoad?.invoke(newConfig, newSource)\n                                        }\n                                    }\n                                    val valid = key.reset()\n                                    if (!valid) {\n                                        watcher.close()\n                                        throw InvalidWatchKeyException(source)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }.withLayer()\n        }\n    }\n\n    /**\n     * Returns a child config containing values from specified file path,\n     * and reloads values when file content has been changed.\n     *\n     * @param file specified file path\n     * @param delayTime delay to observe between every check. The default value is 5.\n     * @param unit time unit of delay. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated file is loaded\n     * @return a child config containing values from watched file\n     */\n    fun watchFile(\n        file: String,\n        delayTime: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config =\n        watchFile(File(file), delayTime, unit, context, optional, onLoad)\n\n    /**\n     * Returns a child config containing values from specified string.\n     *\n     * @param content specified string\n     * @return a child config containing values from specified string\n     */\n    fun string(content: String): Config =\n        config.withSource(provider.string(content))\n\n    /**\n     * Returns a child config containing values from specified byte array.\n     *\n     * @param content specified byte array\n     * @return a child config containing values from specified byte array\n     */\n    fun bytes(content: ByteArray): Config =\n        config.withSource(provider.bytes(content))\n\n    /**\n     * Returns a child config containing values from specified portion of byte array.\n     *\n     * @param content specified byte array\n     * @param offset the start offset of the portion of the array to read\n     * @param length the length of the portion of the array to read\n     * @return a child config containing values from specified portion of byte array\n     */\n    fun bytes(content: ByteArray, offset: Int, length: Int): Config =\n        config.withSource(provider.bytes(content, offset, length))\n\n    /**\n     * Returns a child config containing values from specified url.\n     *\n     * @param url specified url\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified url\n     */\n    fun url(url: URL, optional: Boolean = this.optional): Config =\n        config.withSource(provider.url(url, optional))\n\n    /**\n     * Returns a child config containing values from specified url string.\n     *\n     * @param url specified url string\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified url string\n     */\n    fun url(url: String, optional: Boolean = this.optional): Config =\n        config.withSource(provider.url(url, optional))\n\n    /**\n     * Returns a child config containing values from specified url,\n     * and reloads values periodically.\n     *\n     * @param url specified url\n     * @param period reload period. The default value is 5.\n     * @param unit time unit of reload period. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated URL is loaded\n     * @return a child config containing values from specified url\n     */\n    fun watchUrl(\n        url: URL,\n        period: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config {\n        return provider.url(url, optional).let { source ->\n            config.withLoadTrigger(\"watch ${source.description}\") { newConfig, load ->\n                newConfig.lock {\n                    load(source)\n                }\n                onLoad?.invoke(newConfig, source)\n                GlobalScope.launch(context) {\n                    while (true) {\n                        delay(unit.toMillis(period))\n                        val newSource = provider.url(url, optional)\n                        newConfig.lock {\n                            newConfig.clear()\n                            load(newSource)\n                        }\n                        onLoad?.invoke(newConfig, newSource)\n                    }\n                }\n            }.withLayer()\n        }\n    }\n\n    /**\n     * Returns a child config containing values from specified url string,\n     * and reloads values periodically.\n     *\n     * @param url specified url string\n     * @param period reload period. The default value is 5.\n     * @param unit time unit of reload period. The default value is [TimeUnit.SECONDS].\n     * @param context context of the coroutine. The default value is [Dispatchers.Default].\n     * @param optional whether the source is optional\n     * @param onLoad function invoked after the updated URL is loaded\n     * @return a child config containing values from specified url string\n     */\n    fun watchUrl(\n        url: String,\n        period: Long = 5,\n        unit: TimeUnit = TimeUnit.SECONDS,\n        context: CoroutineContext = Dispatchers.Default,\n        optional: Boolean = this.optional,\n        onLoad: ((config: Config, source: Source) -> Unit)? = null\n    ): Config =\n        watchUrl(URL(url), period, unit, context, optional, onLoad)\n\n    /**\n     * Returns a child config containing values from specified resource.\n     *\n     * @param resource path of specified resource\n     * @param optional whether the source is optional\n     * @return a child config containing values from specified resource\n     */\n    fun resource(resource: String, optional: Boolean = this.optional): Config =\n        config.withSource(provider.resource(resource, optional))\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/MergedSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.MergedMap\nimport com.uchuhimo.konf.Path\nimport com.uchuhimo.konf.TreeNode\nimport java.util.Collections\n\nclass MergedSource(val facade: Source, val fallback: Source) : Source {\n    override val info: SourceInfo by lazy {\n        SourceInfo(\n            \"facade\" to facade.description,\n            \"fallback\" to fallback.description\n        )\n    }\n\n    override val tree: TreeNode by lazy { facade.tree.withFallback(fallback.tree) }\n\n    override val features: Map<Feature, Boolean> by lazy {\n        MergedMap(\n            Collections.unmodifiableMap(fallback.features),\n            Collections.unmodifiableMap(facade.features)\n        )\n    }\n\n    override fun disabled(feature: Feature): Source = MergedSource(facade.disabled(feature), fallback)\n\n    override fun enabled(feature: Feature): Source = MergedSource(facade.enabled(feature), fallback)\n\n    override fun substituted(root: Source, enabled: Boolean, errorWhenUndefined: Boolean): Source {\n        val substitutedFacade = facade.substituted(root, enabled, errorWhenUndefined)\n        val substitutedFallback = fallback.substituted(root, enabled, errorWhenUndefined)\n        if (substitutedFacade === facade && substitutedFallback === fallback) {\n            return this\n        } else {\n            return MergedSource(substitutedFacade, substitutedFallback)\n        }\n    }\n\n    override fun lowercased(enabled: Boolean): Source {\n        val lowercasedFacade = facade.lowercased(enabled)\n        val lowercasedFallback = fallback.lowercased(enabled)\n        if (lowercasedFacade === facade && lowercasedFallback === fallback) {\n            return this\n        } else {\n            return MergedSource(lowercasedFacade, lowercasedFallback)\n        }\n    }\n\n    override fun littleCamelCased(enabled: Boolean): Source {\n        val littleCamelCasedFacade = facade.littleCamelCased(enabled)\n        val littleCamelCasedFallback = fallback.littleCamelCased(enabled)\n        if (littleCamelCasedFacade === facade && littleCamelCasedFallback === fallback) {\n            return this\n        } else {\n            return MergedSource(littleCamelCasedFacade, littleCamelCasedFallback)\n        }\n    }\n\n    override fun normalized(lowercased: Boolean, littleCamelCased: Boolean): Source {\n        val normalizedFacade = facade.normalized(lowercased, littleCamelCased)\n        val normalizedFallback = fallback.normalized(lowercased, littleCamelCased)\n        if (normalizedFacade === facade && normalizedFallback === fallback) {\n            return this\n        } else {\n            return MergedSource(normalizedFacade, normalizedFallback)\n        }\n    }\n\n    override fun getNodeOrNull(path: Path, lowercased: Boolean, littleCamelCased: Boolean): TreeNode? {\n        val facadeNode = facade.getNodeOrNull(path, lowercased, littleCamelCased)\n        val fallbackNode = fallback.getNodeOrNull(path, lowercased, littleCamelCased)\n        return if (facadeNode != null) {\n            if (fallbackNode != null) {\n                facadeNode.withFallback(fallbackNode)\n            } else {\n                facadeNode\n            }\n        } else {\n            fallbackNode\n        }\n    }\n\n    override fun getOrNull(path: Path): Source? {\n        return if (path.isEmpty()) {\n            this\n        } else {\n            val subFacade = facade.getOrNull(path)\n            val subFallback = fallback.getOrNull(path)\n            if (subFacade != null) {\n                if (subFallback != null) {\n                    MergedSource(subFacade, subFallback)\n                } else {\n                    subFacade\n                }\n            } else {\n                subFallback\n            }\n        }\n    }\n\n    override fun withPrefix(prefix: Path): Source {\n        return if (prefix.isEmpty()) {\n            this\n        } else {\n            MergedSource(facade.withPrefix(prefix), fallback.withPrefix(prefix))\n        }\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/Provider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.base.EmptyMapSource\nimport com.uchuhimo.konf.source.json.JsonProvider\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport org.reflections.ReflectionUtils\nimport org.reflections.Reflections\nimport org.reflections.scanners.SubTypesScanner\nimport org.reflections.scanners.TypeAnnotationsScanner\nimport java.io.File\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.Reader\nimport java.net.URL\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * Provides source from various input format.\n */\ninterface Provider {\n    /**\n     * Returns a new source from specified reader.\n     *\n     * @param reader specified reader for reading character streams\n     * @return a new source from specified reader\n     */\n    fun reader(reader: Reader): Source\n\n    /**\n     * Returns a new source from specified input stream.\n     *\n     * @param inputStream specified input stream of bytes\n     * @return a new source from specified input stream\n     */\n    fun inputStream(inputStream: InputStream): Source\n\n    /**\n     * Returns a new source from specified file.\n     *\n     * @param file specified file\n     * @param optional whether this source is optional\n     * @return a new source from specified file\n     */\n    fun file(file: File, optional: Boolean = false): Source {\n        val extendContext: Source.() -> Unit = {\n            info[\"file\"] = file.toString()\n        }\n        if (!file.exists() && optional) {\n            return EmptyMapSource().apply(extendContext)\n        }\n        return file.inputStream().buffered().use { inputStream ->\n            inputStream(inputStream).apply(extendContext)\n        }\n    }\n\n    /**\n     * Returns a new source from specified file path.\n     *\n     * @param file specified file path\n     * @param optional whether this source is optional\n     * @return a new source from specified file path\n     */\n    fun file(file: String, optional: Boolean = false): Source = file(File(file), optional)\n\n    /**\n     * Returns a new source from specified string.\n     *\n     * @param content specified string\n     * @return a new source from specified string\n     */\n    fun string(content: String): Source = reader(content.reader()).apply {\n        info[\"content\"] = \"\\\"\\n$content\\n\\\"\"\n    }\n\n    /**\n     * Returns a new source from specified byte array.\n     *\n     * @param content specified byte array\n     * @return a new source from specified byte array\n     */\n    fun bytes(content: ByteArray): Source {\n        return content.inputStream().use {\n            inputStream(it)\n        }\n    }\n\n    /**\n     * Returns a new source from specified portion of byte array.\n     *\n     * @param content specified byte array\n     * @param offset the start offset of the portion of the array to read\n     * @param length the length of the portion of the array to read\n     * @return a new source from specified portion of byte array\n     */\n    fun bytes(content: ByteArray, offset: Int, length: Int): Source {\n        return content.inputStream(offset, length).use {\n            inputStream(it)\n        }\n    }\n\n    /**\n     * Returns a new source from specified url.\n     *\n     * @param url specified url\n     * @param optional whether this source is optional\n     * @return a new source from specified url\n     */\n    fun url(url: URL, optional: Boolean = false): Source {\n        // from com.fasterxml.jackson.core.JsonFactory._optimizedStreamFromURL in version 2.8.9\n        val extendContext: Source.() -> Unit = {\n            info[\"url\"] = url.toString()\n        }\n        if (url.protocol == \"file\") {\n            val host = url.host\n            if (host == null || host.isEmpty()) {\n                val path = url.path\n                if (path.indexOf('%') < 0) {\n                    val file = File(path)\n                    if (!file.exists() && optional) {\n                        return EmptyMapSource().apply(extendContext)\n                    }\n                    return file.inputStream().use {\n                        inputStream(it).apply(extendContext)\n                    }\n                }\n            }\n        }\n        return try {\n            val stream = url.openStream()\n            stream.use {\n                inputStream(it).apply(extendContext)\n            }\n        } catch (ex: IOException) {\n            if (optional) {\n                EmptyMapSource().apply(extendContext)\n            } else {\n                throw ex\n            }\n        }\n    }\n\n    /**\n     * Returns a new source from specified url string.\n     *\n     * @param url specified url string\n     * @param optional whether this source is optional\n     * @return a new source from specified url string\n     */\n    fun url(url: String, optional: Boolean = false): Source = url(URL(url), optional)\n\n    /**\n     * Returns a new source from specified resource.\n     *\n     * @param resource path of specified resource\n     * @param optional whether this source is optional\n     * @return a new source from specified resource\n     */\n    fun resource(resource: String, optional: Boolean = false): Source {\n        val extendContext: Source.() -> Unit = {\n            info[\"resource\"] = resource\n        }\n        val loader = Thread.currentThread().contextClassLoader\n        val e = try {\n            loader.getResources(resource)\n        } catch (ex: IOException) {\n            if (optional) {\n                return EmptyMapSource().apply(extendContext)\n            } else {\n                throw ex\n            }\n        }\n        if (!e.hasMoreElements()) {\n            if (optional) {\n                return EmptyMapSource().apply(extendContext)\n            } else {\n                throw SourceNotFoundException(\"resource not found on classpath: $resource\")\n            }\n        }\n        val sources = mutableListOf<Source>()\n        while (e.hasMoreElements()) {\n            val url = e.nextElement()\n            val source = url(url, optional)\n            sources.add(source)\n        }\n\n        return sources.reduce(Source::withFallback).apply(extendContext)\n    }\n\n    /**\n     * Returns a provider providing sources that applying the given [transform] function.\n     *\n     * @param transform the given transformation function\n     * @return a provider providing sources that applying the given [transform] function\n     */\n    fun map(transform: (Source) -> Source): Provider {\n        return object : Provider {\n            override fun reader(reader: Reader): Source =\n                this@Provider.reader(reader).let(transform)\n\n            override fun inputStream(inputStream: InputStream): Source =\n                this@Provider.inputStream(inputStream).let(transform)\n\n            override fun file(file: File, optional: Boolean): Source =\n                this@Provider.file(file, optional).let(transform)\n\n            override fun file(file: String, optional: Boolean): Source =\n                this@Provider.file(file, optional).let(transform)\n\n            override fun string(content: String): Source =\n                this@Provider.string(content).let(transform)\n\n            override fun bytes(content: ByteArray): Source =\n                this@Provider.bytes(content).let(transform)\n\n            override fun bytes(content: ByteArray, offset: Int, length: Int): Source =\n                this@Provider.bytes(content, offset, length).let(transform)\n\n            override fun url(url: URL, optional: Boolean): Source =\n                this@Provider.url(url, optional).let(transform)\n\n            override fun url(url: String, optional: Boolean): Source =\n                this@Provider.url(url, optional).let(transform)\n\n            override fun resource(resource: String, optional: Boolean): Source =\n                this@Provider.resource(resource, optional).let(transform)\n        }\n    }\n\n    companion object {\n        private val extensionToProvider = ConcurrentHashMap(\n            mutableMapOf(\n                \"json\" to JsonProvider,\n                \"properties\" to PropertiesProvider\n            )\n        )\n\n        init {\n            val reflections = Reflections(\n                \"com.uchuhimo.konf\",\n                SubTypesScanner(),\n                TypeAnnotationsScanner()\n            )\n            val providers = reflections.getSubTypesOf(Provider::class.java)\n                .intersect(reflections.getTypesAnnotatedWith(RegisterExtension::class.java))\n            for (provider in providers) {\n                for (\n                    annotation in ReflectionUtils.getAnnotations(provider).filter {\n                        it.annotationClass == RegisterExtension::class\n                    }\n                ) {\n                    for (extension in (annotation as RegisterExtension).value) {\n                        registerExtension(extension, provider.kotlin.objectInstance!! as Provider)\n                    }\n                }\n            }\n        }\n\n        /**\n         * Register extension with the corresponding provider.\n         *\n         * @param extension the file extension\n         * @param provider the corresponding provider\n         */\n        fun registerExtension(extension: String, provider: Provider) {\n            extensionToProvider[extension] = provider\n        }\n\n        /**\n         * Unregister the given extension.\n         *\n         * @param extension the file extension\n         */\n        fun unregisterExtension(extension: String): Provider? =\n            extensionToProvider.remove(extension)\n\n        /**\n         * Returns corresponding provider based on extension.\n         *\n         * Returns null if the specific extension is unregistered.\n         *\n         * @param extension the file extension\n         * @return the corresponding provider based on extension\n         */\n        fun of(extension: String): Provider? =\n            extensionToProvider[extension]\n    }\n}\n\n@Retention(AnnotationRetention.RUNTIME)\n@Target(AnnotationTarget.CLASS)\nannotation class RegisterExtension(val value: Array<String>)\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.fasterxml.jackson.core.JsonProcessingException\nimport com.fasterxml.jackson.databind.JavaType\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.databind.node.ArrayNode\nimport com.fasterxml.jackson.databind.node.BigIntegerNode\nimport com.fasterxml.jackson.databind.node.BooleanNode\nimport com.fasterxml.jackson.databind.node.DecimalNode\nimport com.fasterxml.jackson.databind.node.DoubleNode\nimport com.fasterxml.jackson.databind.node.FloatNode\nimport com.fasterxml.jackson.databind.node.IntNode\nimport com.fasterxml.jackson.databind.node.JsonNodeFactory\nimport com.fasterxml.jackson.databind.node.LongNode\nimport com.fasterxml.jackson.databind.node.ObjectNode\nimport com.fasterxml.jackson.databind.node.ShortNode\nimport com.fasterxml.jackson.databind.node.TextNode\nimport com.fasterxml.jackson.databind.node.TreeTraversingParser\nimport com.fasterxml.jackson.databind.type.ArrayType\nimport com.fasterxml.jackson.databind.type.CollectionLikeType\nimport com.fasterxml.jackson.databind.type.MapLikeType\nimport com.fasterxml.jackson.databind.type.SimpleType\nimport com.fasterxml.jackson.databind.type.TypeFactory\nimport com.fasterxml.jackson.module.kotlin.convertValue\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ContainerNode\nimport com.uchuhimo.konf.EmptyNode\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.Item\nimport com.uchuhimo.konf.ListNode\nimport com.uchuhimo.konf.MapNode\nimport com.uchuhimo.konf.MergedMap\nimport com.uchuhimo.konf.NullNode\nimport com.uchuhimo.konf.Path\nimport com.uchuhimo.konf.SizeInBytes\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.base.ListStringNode\nimport com.uchuhimo.konf.source.base.toHierarchical\nimport com.uchuhimo.konf.toLittleCamelCase\nimport com.uchuhimo.konf.toPath\nimport com.uchuhimo.konf.toTree\nimport com.uchuhimo.konf.toValue\nimport org.apache.commons.text.StringSubstitutor\nimport org.apache.commons.text.lookup.StringLookup\nimport org.apache.commons.text.lookup.StringLookupFactory\nimport java.lang.reflect.InvocationTargetException\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Duration\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZoneOffset\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeParseException\nimport java.util.ArrayDeque\nimport java.util.Collections\nimport java.util.Date\nimport java.util.Queue\nimport java.util.SortedMap\nimport java.util.SortedSet\nimport java.util.TreeMap\nimport java.util.TreeSet\nimport java.util.regex.Pattern\nimport kotlin.Byte\nimport kotlin.Char\nimport kotlin.Double\nimport kotlin.Float\nimport kotlin.Int\nimport kotlin.Long\nimport kotlin.Short\nimport kotlin.String\nimport kotlin.reflect.KClass\nimport kotlin.reflect.full.isSubclassOf\nimport kotlin.reflect.full.starProjectedType\nimport com.fasterxml.jackson.databind.node.NullNode as JacksonNullNode\n\n/**\n * Source to provide values for config.\n *\n * When config loads values from source, config will iterate all items in it, and\n * retrieve value with path of each item from source.\n * When source contains single value, a series of `is` operations can be used to\n * judge the actual type of value, and `to` operation can be used to get the value\n * with specified type.\n * When source contains multiple value, `contains` operations can be used to check\n * whether value(s) in specified path is in this source, and `get` operations can be used\n * to retrieve the corresponding sub-source.\n */\ninterface Source {\n    /**\n     * Description of this source.\n     */\n    val description: String\n        get() = this.info.map { (name, value) ->\n            \"$name: $value\"\n        }.joinToString(separator = \", \", prefix = \"[\", postfix = \"]\")\n\n    /**\n     * Information about this source.\n     *\n     * Info is in form of key-value pairs.\n     */\n    val info: SourceInfo\n\n    /**\n     * a tree node that represents internal structure of this source.\n     */\n    val tree: TreeNode\n\n    /**\n     * Feature flags in this source.\n     */\n    val features: Map<Feature, Boolean> get() = emptyMap()\n\n    /**\n     * Whether this source contains value(s) in specified path or not.\n     *\n     * @param path item path\n     * @return `true` if this source contains value(s) in specified path, `false` otherwise\n     */\n    operator fun contains(path: Path): Boolean = path in tree\n\n    /**\n     * Returns sub-source in specified path if this source contains value(s) in specified path,\n     * `null` otherwise.\n     *\n     * @param path item path\n     * @return sub-source in specified path if this source contains value(s) in specified path,\n     * `null` otherwise\n     */\n    fun getOrNull(path: Path): Source? {\n        return if (path.isEmpty()) {\n            this\n        } else {\n            getTreeOrNull(tree, normalizedPath(path))?.let {\n                Source(info = info, tree = it, features = features)\n            }\n        }\n    }\n\n    private fun getTreeOrNull(tree: TreeNode, path: Path): TreeNode? {\n        return if (path.isEmpty()) {\n            tree\n        } else {\n            val key = normalizedKey(path.first())\n            val rest = path.drop(1)\n            var result: TreeNode? = null\n            for ((childKey, child) in tree.children) {\n                if (key == normalizedKey(childKey)) {\n                    result = child\n                    break\n                }\n            }\n            result?.let { getTreeOrNull(it, rest) }\n        }\n    }\n\n    private fun normalizedKey(key: String): String {\n        var currentKey = key\n        if (isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)) {\n            currentKey = currentKey.toLittleCamelCase()\n        }\n        if (isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) {\n            currentKey = currentKey.toLowerCase()\n        }\n        return currentKey\n    }\n\n    private fun normalizedPath(path: Path, lowercased: Boolean = false, littleCamelCased: Boolean = true): Path {\n        var currentPath = path\n        if (littleCamelCased && isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)) {\n            currentPath = currentPath.map { it.toLittleCamelCase() }\n        }\n        if (lowercased || isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) {\n            currentPath = currentPath.map { it.toLowerCase() }\n        }\n        return currentPath\n    }\n\n    fun getNodeOrNull(path: Path, lowercased: Boolean = false, littleCamelCased: Boolean = true): TreeNode? {\n        return tree.getOrNull(normalizedPath(path, lowercased, littleCamelCased))\n    }\n\n    /**\n     * Returns sub-source in specified path.\n     *\n     * Throws [NoSuchPathException] if there is no value in specified path.\n     *\n     * @param path item path\n     * @return sub-source in specified path\n     * @throws NoSuchPathException\n     */\n    operator fun get(path: Path): Source = getOrNull(path) ?: throw NoSuchPathException(this, path)\n\n    /**\n     * Whether this source contains value(s) with specified prefix or not.\n     *\n     * @param prefix item prefix\n     * @return `true` if this source contains value(s) with specified prefix, `false` otherwise\n     */\n    operator fun contains(prefix: String): Boolean = contains(prefix.toPath())\n\n    /**\n     * Returns sub-source in specified path if this source contains value(s) in specified path,\n     * `null` otherwise.\n     *\n     * @param path item path\n     * @return sub-source in specified path if this source contains value(s) in specified path,\n     * `null` otherwise\n     */\n    fun getOrNull(path: String): Source? = getOrNull(path.toPath())\n\n    /**\n     * Returns sub-source in specified path.\n     *\n     * Throws [NoSuchPathException] if there is no value in specified path.\n     *\n     * @param path item path\n     * @return sub-source in specified path\n     * @throws NoSuchPathException\n     */\n    operator fun get(path: String): Source = get(path.toPath())\n\n    /**\n     * Returns source with specified additional prefix.\n     *\n     * @param prefix additional prefix\n     * @return source with specified additional prefix\n     */\n    fun withPrefix(prefix: Path): Source {\n        return if (prefix.isEmpty()) {\n            this\n        } else {\n            var prefixedTree = tree\n            for (key in prefix.asReversed()) {\n                prefixedTree = ContainerNode(mutableMapOf(key to prefixedTree))\n            }\n            Source(\n                info = this@Source.info,\n                tree = prefixedTree,\n                features = features\n            )\n        }\n    }\n\n    /**\n     * Returns source with specified additional prefix.\n     *\n     * @param prefix additional prefix\n     * @return source with specified additional prefix\n     */\n    fun withPrefix(prefix: String): Source = withPrefix(prefix.toPath())\n\n    /**\n     * Returns a source backing by specified fallback source.\n     *\n     * When config fails to retrieve values from this source, it will try to retrieve them from\n     * fallback source.\n     *\n     * @param fallback fallback source\n     * @return a source backing by specified fallback source\n     */\n    fun withFallback(fallback: Source): Source = MergedSource(this, fallback)\n\n    /**\n     * Returns a source overlapped by the specified facade source.\n     *\n     * When config fails to retrieve values from the facade source, it will try to retrieve them\n     * from this source.\n     *\n     * @param facade the facade source\n     * @return a source overlapped by the specified facade source\n     */\n    operator fun plus(facade: Source): Source = facade.withFallback(this)\n\n    /**\n     * Return a source that substitutes path variables within all strings by values.\n     *\n     * See [StringSubstitutor](https://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/StringSubstitutor.html)\n     * for detailed substitution rules. An exception is when the string is in reference format like `${path}`,\n     * the whole node will be replace by a reference to the sub-tree in the specified path.\n     *\n     * @param root the root source for substitution\n     * @param enabled whether enabled or let the source decide by itself\n     * @param errorWhenUndefined whether throw exception when this source contains undefined path variables\n     * @return a source that substitutes path variables within all strings by values\n     * @throws UndefinedPathVariableException\n     */\n    fun substituted(root: Source = this, enabled: Boolean = true, errorWhenUndefined: Boolean = true): Source {\n        return if (!enabled || !this.isEnabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)) {\n            this\n        } else {\n            Source(info, tree.substituted(root, errorWhenUndefined), features)\n        }\n    }\n\n    fun lowercased(enabled: Boolean = false): Source {\n        return if (enabled || this.isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) {\n            Source(info, tree.lowercased(), features)\n        } else {\n            this\n        }\n    }\n\n    fun littleCamelCased(enabled: Boolean = true): Source {\n        return if (!enabled || !this.isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)) {\n            this\n        } else {\n            Source(info, tree.littleCamelCased(), features)\n        }\n    }\n\n    fun normalized(lowercased: Boolean = false, littleCamelCased: Boolean = true): Source {\n        var currentSource = this\n        currentSource = currentSource.littleCamelCased(littleCamelCased)\n        currentSource = currentSource.lowercased(lowercased)\n        return currentSource\n    }\n\n    /**\n     * Returns a new source that enables the specified feature.\n     *\n     * @param feature the specified feature\n     * @return a new source\n     */\n    fun enabled(feature: Feature): Source = Source(\n        info,\n        tree,\n        MergedMap(Collections.unmodifiableMap(features), mutableMapOf(feature to true))\n    )\n\n    /**\n     * Returns a new source that disables the specified feature.\n     *\n     * @param feature the specified feature\n     * @return a new source\n     */\n    fun disabled(feature: Feature): Source = Source(\n        info,\n        tree,\n        MergedMap(Collections.unmodifiableMap(features), mutableMapOf(feature to false))\n    )\n\n    /**\n     * Check whether the specified feature is enabled or not.\n     *\n     * @param feature the specified feature\n     * @return whether the specified feature is enabled or not\n     */\n    fun isEnabled(feature: Feature): Boolean = features[feature] ?: feature.enabledByDefault\n\n    companion object {\n        operator fun invoke(\n            info: SourceInfo = SourceInfo(),\n            tree: TreeNode = ContainerNode.empty(),\n            features: Map<Feature, Boolean> = emptyMap()\n        ): Source {\n            return BaseSource(info, tree, features)\n        }\n\n        /**\n         * Returns default providers.\n         *\n         * It is a fluent API for default providers.\n         */\n        val from = DefaultProviders\n\n        /**\n         * Returns default providers.\n         *\n         * It is a fluent API for default providers.\n         *\n         * @return default providers.\n         */\n        @JavaApi\n        @JvmStatic\n        fun from() = from\n    }\n}\n\n/**\n * Returns a value casted from source.\n *\n * @return a value casted from source\n */\ninline fun <reified T> Source.toValue(): T {\n    return Config().withSource(this).toValue()\n}\n\nprivate val singleVariablePattern = Pattern.compile(\"^\\\\$\\\\{(.+)}$\")\n\nprivate fun TreeNode.substituted(\n    source: Source,\n    errorWhenUndefined: Boolean,\n    lookup: TreeLookup = TreeLookup(source.tree, source, errorWhenUndefined)\n): TreeNode {\n    when (this) {\n        is NullNode -> return this\n        is ValueNode -> {\n            if (this is SubstitutableNode && value is String) {\n                val text = (if (substituted) originalValue else value) as String\n                val matcher = singleVariablePattern.matcher(text.trim())\n                if (matcher.find()) {\n                    val matchedValue = matcher.group(1)\n                    try {\n                        val resolvedValue = lookup.replace(matchedValue)\n                        val node = lookup.root.getOrNull(resolvedValue)\n                        if (node != null) {\n                            return node.substituted(source, true, lookup)\n                        }\n                    } catch (_: Exception) {\n                    }\n                }\n                try {\n                    return substitute(lookup.replace(text))\n                } catch (_: IllegalArgumentException) {\n                    throw UndefinedPathVariableException(source, text)\n                }\n            } else {\n                return this\n            }\n        }\n        is ListNode -> {\n            return withList(list.map { it.substituted(source, errorWhenUndefined, lookup) })\n        }\n        is MapNode -> {\n            return withMap(\n                children.mapValues { (_, child) ->\n                    child.substituted(source, errorWhenUndefined, lookup)\n                }\n            )\n        }\n        else -> throw UnsupportedNodeTypeException(source, this)\n    }\n}\n\nprivate fun TreeNode.lowercased(): TreeNode {\n    if (this is ContainerNode) {\n        return withMap(\n            children.mapKeys { (key, _) ->\n                key.toLowerCase()\n            }.mapValues { (_, child) ->\n                child.lowercased()\n            }\n        )\n    } else {\n        return this\n    }\n}\n\nprivate fun TreeNode.littleCamelCased(): TreeNode {\n    if (this is ContainerNode) {\n        return withMap(\n            children.mapKeys { (key, _) ->\n                key.toLittleCamelCase()\n            }.mapValues { (_, child) ->\n                child.littleCamelCased()\n            }\n        )\n    } else {\n        return this\n    }\n}\n\nclass TreeLookup(val root: TreeNode, val source: Source, errorWhenUndefined: Boolean) : StringLookup {\n    val substitutor: StringSubstitutor = StringSubstitutor(\n        StringLookupFactory.INSTANCE.interpolatorStringLookup(this)\n    ).apply {\n        isEnableSubstitutionInVariables = true\n        isEnableUndefinedVariableException = errorWhenUndefined\n    }\n\n    override fun lookup(key: String): String? {\n        val node = root.getOrNull(key)\n        if (node != null && node is ValueNode) {\n            if (node.value::class in listOf(\n                    String::class,\n                    Char::class,\n                    Byte::class,\n                    Short::class,\n                    Int::class,\n                    Long::class,\n                    BigInteger::class\n                )\n            ) {\n                val value = node.value.toString()\n                return substitutor.replace(value)\n            } else {\n                throw WrongTypeException(\n                    \"${node.value} in ${source.description}\",\n                    node.value::class.java.simpleName,\n                    \"String\"\n                )\n            }\n        } else {\n            return null\n        }\n    }\n\n    fun replace(text: String): String {\n        return substitutor.replace(text)\n    }\n}\n\nopen class BaseSource(\n    override val info: SourceInfo = SourceInfo(),\n    override val tree: TreeNode = ContainerNode.empty(),\n    override val features: Map<Feature, Boolean> = emptyMap()\n) : Source\n\n/**\n * Information of source for debugging.\n */\nclass SourceInfo(\n    private val info: MutableMap<String, String> = mutableMapOf()\n) : MutableMap<String, String> by info {\n    constructor(vararg pairs: Pair<String, String>) : this(mutableMapOf(*pairs))\n\n    fun with(vararg pairs: Pair<String, String>): SourceInfo {\n        return SourceInfo(MergedMap(fallback = this, facade = mutableMapOf(*pairs)))\n    }\n\n    fun with(sourceInfo: SourceInfo): SourceInfo {\n        return SourceInfo(MergedMap(fallback = this, facade = sourceInfo.toMutableMap()))\n    }\n}\n\ninline fun <reified T> Source.asValue(): T {\n    return tree.asValueOf(this, T::class.java) as T\n}\n\nfun TreeNode.asValueOf(source: Source, type: Class<*>): Any {\n    return castOrNull(source, type)\n        ?: throw WrongTypeException(\n            if (this is ValueNode) \"${this.value} in ${source.description}\"\n            else \"$this in ${source.description}\",\n            if (this is ValueNode) this.value::class.java.simpleName else \"Unknown\",\n            type.simpleName\n        )\n}\n\ninternal fun Any?.toCompatibleValue(mapper: ObjectMapper): Any {\n    return when (this) {\n        is OffsetTime,\n        is OffsetDateTime,\n        is ZonedDateTime,\n        is LocalDate,\n        is LocalDateTime,\n        is LocalTime,\n        is Year,\n        is YearMonth,\n        is Instant,\n        is Duration -> this.toString()\n        is Date -> this.toInstant().toString()\n        is SizeInBytes -> this.bytes.toString()\n        is Enum<*> -> this.name\n        is ByteArray -> this.toList()\n        is CharArray -> this.toList().map { it.toString() }\n        is BooleanArray -> this.toList()\n        is IntArray -> this.toList()\n        is ShortArray -> this.toList()\n        is LongArray -> this.toList()\n        is DoubleArray -> this.toList()\n        is FloatArray -> this.toList()\n        is List<*> -> this.map { it!!.toCompatibleValue(mapper) }\n        is Set<*> -> this.map { it!!.toCompatibleValue(mapper) }\n        is Array<*> -> this.map { it!!.toCompatibleValue(mapper) }\n        is Map<*, *> -> this.mapValues { (_, value) -> value.toCompatibleValue(mapper) }\n        is Char -> this.toString()\n        is String,\n        is Boolean,\n        is Int,\n        is Short,\n        is Byte,\n        is Long,\n        is BigInteger,\n        is Double,\n        is Float,\n        is BigDecimal -> this\n        else -> {\n            if (this == null) {\n                \"null\"\n            } else {\n                mapper.convertValue<Any>(this).toCompatibleValue(mapper)\n            }\n        }\n    }\n}\n\ninternal fun Config.loadItem(item: Item<*>, path: Path, source: Source): Boolean {\n    try {\n        val itemNode = source.getNodeOrNull(\n            path,\n            lowercased = this.isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY),\n            littleCamelCased = this.isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)\n        )\n        if (itemNode != null && !itemNode.isPlaceHolderNode()) {\n            if (item.nullable &&\n                (\n                    (itemNode is NullNode) ||\n                        (itemNode is ValueNode && itemNode.value == \"null\")\n                    )\n            ) {\n                rawSet(item, null)\n            } else {\n                rawSet(item, itemNode.toValue(source, item.type, mapper))\n            }\n            return true\n        } else {\n            return false\n        }\n    } catch (cause: SourceException) {\n        throw LoadException(path, cause)\n    }\n}\n\ninternal fun load(config: Config, source: Source): Source {\n    var currentSource = source\n    currentSource = currentSource.normalized(\n        lowercased = config.isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY),\n        littleCamelCased = config.isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)\n    )\n    currentSource = currentSource.substituted(\n        enabled = config.isEnabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)\n    )\n    config.lock {\n        for (item in config) {\n            config.loadItem(item, config.pathOf(item), currentSource)\n        }\n        if (currentSource.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) ||\n            config.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH)\n        ) {\n            val treeFromSource = currentSource.tree\n            val treeFromConfig = config.toTree()\n            val diffTree = treeFromSource - treeFromConfig\n            if (diffTree != EmptyNode) {\n                val unknownPaths = diffTree.paths\n                throw UnknownPathsException(currentSource, unknownPaths)\n            }\n        }\n    }\n    return currentSource\n}\n\nprivate inline fun <reified T> TreeNode.cast(source: Source): T {\n    if (this !is ValueNode) {\n        throw WrongTypeException(\"$this in ${source.description}\", this::class.java.simpleName, T::class.java.simpleName)\n    }\n    if (T::class.java.isInstance(value)) {\n        return value as T\n    } else {\n        throw WrongTypeException(\"$value in ${source.description}\", value::class.java.simpleName, T::class.java.simpleName)\n    }\n}\n\ninternal fun stringToBoolean(value: String): Boolean {\n    return when {\n        value.toLowerCase() == \"true\" -> true\n        value.toLowerCase() == \"false\" -> false\n        else -> throw ParseException(\"$value cannot be parsed to a boolean\")\n    }\n}\n\ninternal fun shortToByte(value: Short): Byte {\n    if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {\n        throw ParseException(\"$value cannot be parsed to a byte\")\n    }\n    return value.toByte()\n}\n\ninternal fun intToShort(value: Int): Short {\n    if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {\n        throw ParseException(\"$value cannot be parsed to a short\")\n    }\n    return value.toShort()\n}\n\ninternal fun longToInt(value: Long): Int {\n    if (value < Int.MIN_VALUE || value > Int.MAX_VALUE) {\n        throw ParseException(\"$value cannot be parsed to an int\")\n    }\n    return value.toInt()\n}\n\ninternal fun stringToChar(value: String): Char {\n    if (value.length != 1) {\n        throw ParseException(\"$value cannot be parsed to a char\")\n    }\n    return value[0]\n}\n\nprivate inline fun <T> String.tryParse(block: (String) -> T): T {\n    try {\n        return block(this)\n    } catch (cause: DateTimeParseException) {\n        throw ParseException(\"fail to parse \\\"$this\\\" as data time\", cause)\n    }\n}\n\ninternal fun stringToDate(value: String): Date {\n    return try {\n        Date.from(value.tryParse { Instant.parse(it) })\n    } catch (e: ParseException) {\n        try {\n            Date.from(\n                value.tryParse {\n                    LocalDateTime.parse(it)\n                }.toInstant(ZoneOffset.UTC)\n            )\n        } catch (e: ParseException) {\n            Date.from(\n                value.tryParse {\n                    LocalDate.parse(it)\n                }.atStartOfDay().toInstant(ZoneOffset.UTC)\n            )\n        }\n    }\n}\n\nprivate fun <In, Out> ((In) -> Out).asPromote(): PromoteFunc<*> {\n    return { value, _ ->\n        @Suppress(\"UNCHECKED_CAST\")\n        this(value as In)\n    }\n}\n\nprivate inline fun <reified T> tryParseAsPromote(noinline block: (String) -> T): PromoteFunc<*> {\n    return { value, _ ->\n        try {\n            block(value as String)\n        } catch (cause: Exception) {\n            if (cause is DateTimeParseException || cause is NumberFormatException) {\n                throw ParseException(\"fail to parse \\\"$value\\\" as ${T::class.simpleName}\", cause)\n            } else {\n                throw cause\n            }\n        }\n    }\n}\n\ntypealias PromoteFunc<Out> = (Any, Source) -> Out\n\nprivate val promoteMap: MutableMap<KClass<*>, List<Pair<KClass<*>, PromoteFunc<*>>>> = mutableMapOf(\n    String::class to listOf(\n        Boolean::class to ::stringToBoolean.asPromote(),\n        Char::class to ::stringToChar.asPromote(),\n\n        Byte::class to tryParseAsPromote { value: String -> value.toByte() },\n        Short::class to tryParseAsPromote { value: String -> value.toShort() },\n        Int::class to tryParseAsPromote { value: String -> value.toInt() },\n        Long::class to tryParseAsPromote { value: String -> value.toLong() },\n        Float::class to tryParseAsPromote { value: String -> value.toFloat() },\n        Double::class to tryParseAsPromote { value: String -> value.toDouble() },\n        BigInteger::class to tryParseAsPromote { value: String -> value.toBigInteger() },\n        BigDecimal::class to tryParseAsPromote { value: String -> value.toBigDecimal() },\n\n        OffsetTime::class to tryParseAsPromote { OffsetTime.parse(it) },\n        OffsetDateTime::class to tryParseAsPromote { OffsetDateTime.parse(it) },\n        ZonedDateTime::class to tryParseAsPromote { ZonedDateTime.parse(it) },\n        LocalDate::class to tryParseAsPromote { LocalDate.parse(it) },\n        LocalTime::class to tryParseAsPromote { LocalTime.parse(it) },\n        LocalDateTime::class to tryParseAsPromote { LocalDateTime.parse(it) },\n        Year::class to tryParseAsPromote { Year.parse(it) },\n        YearMonth::class to tryParseAsPromote { YearMonth.parse(it) },\n        Instant::class to tryParseAsPromote { Instant.parse(it) },\n\n        Date::class to ::stringToDate.asPromote(),\n        Duration::class to String::toDuration.asPromote(),\n\n        SizeInBytes::class to { value: String -> SizeInBytes.parse(value) }.asPromote()\n    ),\n    Char::class to listOf(\n        String::class to { value: Char -> \"$value\" }.asPromote()\n    ),\n    Byte::class to listOf(\n        Short::class to Byte::toShort.asPromote(),\n        Int::class to Byte::toInt.asPromote(),\n        Long::class to Byte::toLong.asPromote(),\n        Float::class to Byte::toFloat.asPromote(),\n        Double::class to Byte::toDouble.asPromote()\n    ),\n    Short::class to listOf(\n        Byte::class to ::shortToByte.asPromote(),\n        Int::class to Short::toInt.asPromote(),\n        Long::class to Short::toLong.asPromote(),\n        Float::class to Short::toFloat.asPromote(),\n        Double::class to Short::toDouble.asPromote()\n    ),\n    Int::class to listOf(\n        Short::class to ::intToShort.asPromote(),\n        Long::class to Int::toLong.asPromote(),\n        Float::class to Int::toFloat.asPromote(),\n        Double::class to Int::toDouble.asPromote()\n    ),\n    Long::class to listOf(\n        Int::class to ::longToInt.asPromote(),\n        Float::class to Long::toFloat.asPromote(),\n        Double::class to Long::toDouble.asPromote(),\n        BigInteger::class to { value: Long -> BigInteger.valueOf(value) }.asPromote()\n    ),\n    Float::class to listOf(\n        Double::class to Float::toDouble.asPromote()\n    ),\n    Double::class to listOf(\n        Float::class to Double::toFloat.asPromote(),\n        BigDecimal::class to { value: Double -> BigDecimal.valueOf(value) }.asPromote()\n    )\n)\n\nprivate val promoteMatchers: MutableList<Pair<(KClass<*>) -> Boolean, List<Pair<KClass<*>, PromoteFunc<*>>>>> = mutableListOf(\n    { type: KClass<*> -> type.starProjectedType == Array<Int>::class.starProjectedType } to listOf(\n        List::class to { value: Array<*> -> value.asList() }.asPromote(),\n        Set::class to { value: Array<*> -> value.asList().toSet() }.asPromote()\n    ),\n    { type: KClass<*> -> type.isSubclassOf(Set::class) } to listOf(\n        List::class to { value: Set<*> -> value.toList() }.asPromote()\n    )\n)\n\nprivate fun walkPromoteMap(\n    valueType: KClass<*>,\n    targetType: KClass<*>,\n    tasks: Queue<() -> PromoteFunc<*>?>,\n    visitedTypes: MutableSet<KClass<*>>,\n    previousPromoteFunc: PromoteFunc<*>? = null\n): PromoteFunc<*>? {\n    if (valueType in visitedTypes) {\n        return null\n    }\n    visitedTypes.add(valueType)\n    var promotedTypes = promoteMap[valueType]\n    if (promotedTypes == null) {\n        for ((matcher, types) in promoteMatchers) {\n            if (matcher(valueType)) {\n                promotedTypes = types\n                break\n            }\n        }\n    }\n    if (promotedTypes == null) {\n        return null\n    }\n    for ((promotedType, promoteFunc) in promotedTypes) {\n        val currentPromoteFunc: PromoteFunc<*> = if (previousPromoteFunc != null) {\n            { value, source -> promoteFunc(previousPromoteFunc(value, source)!!, source) }\n        } else {\n            promoteFunc\n        }\n        if (promotedType == targetType) {\n            return currentPromoteFunc\n        } else {\n            tasks.offer {\n                walkPromoteMap(promotedType, targetType, tasks, visitedTypes, currentPromoteFunc)\n            }\n        }\n    }\n    return null\n}\n\nprivate fun getPromoteFunc(valueType: KClass<*>, targetType: KClass<*>): PromoteFunc<*>? {\n    val tasks = ArrayDeque<() -> PromoteFunc<*>?>()\n    tasks.offer {\n        walkPromoteMap(valueType, targetType, tasks, mutableSetOf())\n    }\n    while (tasks.isNotEmpty()) {\n        val func = tasks.poll()()\n        if (func != null) {\n            return func\n        }\n    }\n    return null\n}\n\nprivate fun <T : Any> TreeNode.castOrNull(source: Source, clazz: Class<T>): T? {\n    if (this is ValueNode) {\n        if (clazz.kotlin.javaObjectType.isInstance(value)) {\n            @Suppress(\"UNCHECKED_CAST\")\n            return value as T\n        } else {\n            val promoteFunc = getPromoteFunc(value::class, clazz.kotlin)\n            if (promoteFunc != null) {\n                @Suppress(\"UNCHECKED_CAST\")\n                return promoteFunc(value, source) as T\n            } else {\n                return null\n            }\n        }\n    } else {\n        return null\n    }\n}\n\nprivate val promotedFromStringTypes = promoteMap.getValue(String::class).map { it.first }\nprivate val promotedFromStringMap = promoteMap.getValue(String::class).toMap()\n\nprivate fun TreeNode.toValue(source: Source, type: JavaType, mapper: ObjectMapper): Any {\n    if (this is ValueNode &&\n        type == TypeFactory.defaultInstance().constructType(value::class.java)\n    ) {\n        return value\n    }\n    when (type) {\n        is SimpleType -> {\n            val clazz = type.rawClass\n            if (type.isEnumType) {\n                val valueOfMethod = clazz.getMethod(\"valueOf\", String::class.java)\n                val name: String = cast(source)\n                try {\n                    return valueOfMethod.invoke(null, name)\n                } catch (cause: InvocationTargetException) {\n                    throw ParseException(\n                        \"enum type $clazz has no constant with name $name\",\n                        cause\n                    )\n                }\n            } else {\n                val value = castOrNull(source, clazz)\n                if (value != null) {\n                    return value\n                } else {\n                    try {\n                        return mapper.readValue<Any>(\n                            TreeTraversingParser(withoutPlaceHolder().toJsonNode(source), mapper),\n                            type\n                        )\n                    } catch (cause: JsonProcessingException) {\n                        throw ObjectMappingException(\"${this.toHierarchical()} in ${source.description}\", clazz, cause)\n                    }\n                }\n            }\n        }\n        is ArrayType -> {\n            val clazz = type.contentType.rawClass\n            val list = toListValue(source, type.contentType, mapper)\n            if (!clazz.isPrimitive) {\n                val array = java.lang.reflect.Array.newInstance(clazz, list.size) as Array<*>\n                @Suppress(\"PLATFORM_CLASS_MAPPED_TO_KOTLIN\")\n                return (list as java.util.Collection<*>).toArray(array)\n            } else {\n                @Suppress(\"UNCHECKED_CAST\")\n                return when (clazz) {\n                    Boolean::class.java -> (list as List<Boolean>).toBooleanArray()\n                    Int::class.java -> (list as List<Int>).toIntArray()\n                    Short::class.java -> (list as List<Short>).toShortArray()\n                    Byte::class.java -> (list as List<Byte>).toByteArray()\n                    Long::class.java -> (list as List<Long>).toLongArray()\n                    Double::class.java -> (list as List<Double>).toDoubleArray()\n                    Float::class.java -> (list as List<Float>).toFloatArray()\n                    Char::class.java -> (list as List<Char>).toCharArray()\n                    else -> throw UnsupportedTypeException(source, clazz)\n                }\n            }\n        }\n        is CollectionLikeType -> {\n            if (MutableCollection::class.java.isAssignableFrom(type.rawClass)) {\n                @Suppress(\"UNCHECKED_CAST\")\n                return (implOf(type.rawClass).getDeclaredConstructor().newInstance() as MutableCollection<Any>).apply {\n                    addAll(toListValue(source, type.contentType, mapper) as List<Any>)\n                }\n            } else {\n                throw UnsupportedTypeException(source, type.rawClass)\n            }\n        }\n        is MapLikeType -> {\n            if (MutableMap::class.java.isAssignableFrom(type.rawClass)) {\n                when {\n                    type.keyType.rawClass == String::class.java -> {\n                        @Suppress(\"UNCHECKED_CAST\")\n                        return (implOf(type.rawClass).getDeclaredConstructor().newInstance() as MutableMap<String, Any>).apply {\n                            putAll(\n                                this@toValue.toMap(source).mapValues { (_, value) ->\n                                    value.toValue(source, type.contentType, mapper)\n                                }\n                            )\n                        }\n                    }\n                    type.keyType.rawClass.kotlin in promotedFromStringTypes -> {\n                        val promoteFunc = promotedFromStringMap.getValue(type.keyType.rawClass.kotlin)\n                        @Suppress(\"UNCHECKED_CAST\")\n                        return (implOf(type.rawClass).getDeclaredConstructor().newInstance() as MutableMap<Any, Any>).apply {\n                            putAll(\n                                this@toValue.toMap(source).map { (key, value) ->\n                                    promoteFunc(key, source)!! to value.toValue(source, type.contentType, mapper)\n                                }\n                            )\n                        }\n                    }\n                    else -> {\n                        throw UnsupportedMapKeyException(type.keyType.rawClass)\n                    }\n                }\n            } else {\n                throw UnsupportedTypeException(source, type.rawClass)\n            }\n        }\n        else -> throw UnsupportedTypeException(source, type.rawClass)\n    }\n}\n\nprivate fun TreeNode.toListValue(source: Source, type: JavaType, mapper: ObjectMapper): List<*> {\n    return when (this) {\n        is ListNode -> list.map { it.toValue(source, type, mapper) }\n        else -> throw WrongTypeException(\"$this in ${source.description}\", this::class.java.simpleName, List::class.java.simpleName)\n    }\n}\n\nprivate fun TreeNode.toMap(source: Source): Map<String, TreeNode> {\n    return when (this) {\n        is MapNode -> children\n        else -> throw WrongTypeException(\"$this in ${source.description}\", this::class.java.simpleName, Map::class.java.simpleName)\n    }\n}\n\nprivate fun TreeNode.toJsonNode(source: Source): JsonNode {\n    return when (this) {\n        is NullNode -> JacksonNullNode.instance\n        is ListStringNode ->\n            ArrayNode(\n                JsonNodeFactory.instance,\n                list.map {\n                    it.toJsonNode(source)\n                }\n            )\n        is ValueNode -> {\n            when (value) {\n                is Boolean -> BooleanNode.valueOf(value as Boolean)\n                is Long -> LongNode.valueOf(value as Long)\n                is Int -> IntNode.valueOf(value as Int)\n                is Short -> ShortNode.valueOf(value as Short)\n                is Byte -> ShortNode.valueOf((value as Byte).toShort())\n                is BigInteger -> BigIntegerNode.valueOf(value as BigInteger)\n                is Double -> DoubleNode.valueOf(value as Double)\n                is Float -> FloatNode.valueOf(value as Float)\n                is Char -> TextNode.valueOf(value.toString())\n                is BigDecimal -> DecimalNode.valueOf(value as BigDecimal)\n                is String -> TextNode.valueOf(value as String)\n                is OffsetTime -> TextNode.valueOf(value.toString())\n                is OffsetDateTime -> TextNode.valueOf(value.toString())\n                is ZonedDateTime -> TextNode.valueOf(value.toString())\n                is LocalDate -> TextNode.valueOf(value.toString())\n                is LocalTime -> TextNode.valueOf(value.toString())\n                is LocalDateTime -> TextNode.valueOf(value.toString())\n                is Date -> TextNode.valueOf((value as Date).toInstant().toString())\n                is Year -> TextNode.valueOf(value.toString())\n                is YearMonth -> TextNode.valueOf(value.toString())\n                is Instant -> TextNode.valueOf(value.toString())\n                is Duration -> TextNode.valueOf(value.toString())\n                is SizeInBytes -> LongNode.valueOf((value as SizeInBytes).bytes)\n                else -> throw ParseException(\"fail to cast source ${source.description} to JSON node\")\n            }\n        }\n        is ListNode ->\n            ArrayNode(\n                JsonNodeFactory.instance,\n                list.map {\n                    it.toJsonNode(source)\n                }\n            )\n        is MapNode -> ObjectNode(\n            JsonNodeFactory.instance,\n            children.mapValues { (_, value) ->\n                value.toJsonNode(source)\n            }\n        )\n        else -> throw ParseException(\"fail to cast source ${source.description} to JSON node\")\n    }\n}\n\nprivate fun implOf(clazz: Class<*>): Class<*> =\n    when (clazz) {\n        List::class.java -> ArrayList::class.java\n        Set::class.java -> HashSet::class.java\n        SortedSet::class.java -> TreeSet::class.java\n        Map::class.java -> HashMap::class.java\n        SortedMap::class.java -> TreeMap::class.java\n        else -> clazz\n    }\n\nfun Any.asTree(): TreeNode =\n    when (this) {\n        is TreeNode -> this\n        is Source -> this.tree\n        is List<*> ->\n            @Suppress(\"UNCHECKED_CAST\")\n            ListSourceNode((this as List<Any>).map { it.asTree() })\n        is Map<*, *> -> {\n            when {\n                this.size == 0 -> ContainerNode(mutableMapOf())\n                this.iterator().next().key is String -> {\n                    @Suppress(\"UNCHECKED_CAST\")\n                    ContainerNode(\n                        (this as Map<String, Any>).mapValues { (_, value) ->\n                            value.asTree()\n                        }.toMutableMap()\n                    )\n                }\n                this.iterator().next().key!!::class in listOf(\n                    Char::class,\n                    Byte::class,\n                    Short::class,\n                    Int::class,\n                    Long::class,\n                    BigInteger::class\n                ) -> {\n                    @Suppress(\"UNCHECKED_CAST\")\n                    ContainerNode(\n                        (this as Map<Any, Any>).map { (key, value) ->\n                            key.toString() to value.asTree()\n                        }.toMap().toMutableMap()\n                    )\n                }\n                else -> ValueSourceNode(this)\n            }\n        }\n        else -> ValueSourceNode(this)\n    }\n\nfun Any.asSource(type: String = \"\", info: SourceInfo = SourceInfo()): Source =\n    when (this) {\n        is Source -> this\n        is TreeNode -> Source(info.with(\"type\" to type), this)\n        else -> Source(info.with(\"type\" to type), asTree())\n    }\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/SourceException.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.ConfigException\nimport com.uchuhimo.konf.Path\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.name\n\n/**\n * Exception for source.\n */\nopen class SourceException : ConfigException {\n    constructor(message: String) : super(message)\n    constructor(message: String, cause: Throwable) : super(message, cause)\n}\n\n/**\n * Exception indicates that actual type of value in source is unmatched with expected type.\n */\nclass WrongTypeException(val source: String, actual: String, expected: String) :\n    SourceException(\"source $source has type $actual rather than $expected\")\n\n/**\n * Exception indicates that expected value in specified path is not existed in the source.\n */\nclass NoSuchPathException(val source: Source, val path: Path) :\n    SourceException(\"cannot find path \\\"${path.name}\\\" in source ${source.description}\")\n\n/**\n * Exception indicates that there is a parsing error.\n */\nclass ParseException : SourceException {\n    constructor(message: String) : super(message)\n    constructor(message: String, cause: Throwable) : super(message, cause)\n}\n\n/**\n * Exception indicates that value of specified class in unsupported in the source.\n */\nclass UnsupportedTypeException(source: Source, clazz: Class<*>) :\n    SourceException(\"value of type ${clazz.simpleName} is unsupported in source ${source.description}\")\n\n/**\n * Exception indicates that watch key is no longer valid for the source.\n */\nclass InvalidWatchKeyException(source: Source) :\n    SourceException(\"watch key for source ${source.description} is no longer valid\")\n\n/**\n * Exception indicates that the given repository is not in the remote list of the local repository.\n */\nclass InvalidRemoteRepoException(repo: String, dir: String) :\n    SourceException(\"$repo is not in the remote list of $dir\")\n\n/**\n * Exception indicates failure to map source to value of specified class.\n */\nclass ObjectMappingException(source: String, clazz: Class<*>, cause: Throwable) :\n    SourceException(\"unable to map source $source to value of type ${clazz.simpleName}\", cause)\n\n/**\n * Exception indicates that value of specified class is unsupported as key of map.\n */\nclass UnsupportedMapKeyException(val clazz: Class<*>) : SourceException(\n    \"cannot support map with ${clazz.simpleName} key\"\n)\n\n/**\n * Exception indicates failure to load specified path.\n */\nclass LoadException(val path: Path, cause: Throwable) :\n    SourceException(\"fail to load ${path.name}\", cause)\n\n/**\n * Exception indicates that the source contains unknown paths.\n */\nclass UnknownPathsException(source: Source, val paths: List<String>) :\n    SourceException(\n        \"source ${source.description} contains the following unknown paths:\\n\" +\n            paths.joinToString(\"\\n\")\n    )\n\n/**\n * Exception indicates that specified source is not found.\n */\nclass SourceNotFoundException(message: String) : SourceException(message)\n\n/**\n * Exception indicates that specified source has unsupported extension.\n */\nclass UnsupportedExtensionException(source: String) : SourceException(\n    \"cannot detect supported extension for \\\"$source\\\",\" +\n        \" supported extensions: conf, json, properties, toml, xml, yml, yaml\"\n)\n\n/**\n * Exception indicates that undefined paths occur during variable substitution.\n */\nclass UndefinedPathVariableException(val source: Source, val text: String) : SourceException(\n    \"\\\"$text\\\" in source ${source.description} contains undefined path variables during path substitution\"\n)\n\n/**\n * Exception indicates that the specified node has unsupported type.\n */\nclass UnsupportedNodeTypeException(val source: Source, val node: TreeNode) : SourceException(\n    \"$node of type ${node::class.java.simpleName} in source ${source.description} is unsupported\"\n)\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/SourceNode.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.ListNode\nimport com.uchuhimo.konf.MapNode\nimport com.uchuhimo.konf.NullNode\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.emptyMutableMap\nimport java.util.Collections\n\ninterface SubstitutableNode : ValueNode {\n    fun substitute(value: String): TreeNode\n    val substituted: Boolean\n    val originalValue: Any?\n}\n\nclass ValueSourceNode(\n    override val value: Any,\n    override val substituted: Boolean = false,\n    override val originalValue: Any? = null\n) : SubstitutableNode {\n    override fun substitute(value: String): TreeNode {\n        return ValueSourceNode(value, true, originalValue ?: this.value)\n    }\n}\n\nobject NullSourceNode : NullNode {\n    override val children: MutableMap<String, TreeNode> = emptyMutableMap\n}\n\nopen class ListSourceNode(\n    override val list: List<TreeNode>,\n    override var isPlaceHolder: Boolean = false\n) : ListNode, MapNode {\n    override val children: MutableMap<String, TreeNode>\n        get() = Collections.unmodifiableMap(\n            list.withIndex().associate { (key, value) -> key.toString() to value }\n        )\n\n    override fun withList(list: List<TreeNode>): ListNode {\n        return ListSourceNode(list)\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/Utils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.getUnits\nimport java.time.Duration\nimport java.time.format.DateTimeParseException\nimport java.util.concurrent.TimeUnit\n\n/**\n * Parses specified string to duration.\n *\n * @receiver specified string\n * @return duration\n */\nfun String.toDuration(): Duration {\n    return try {\n        Duration.parse(this)\n    } catch (e: DateTimeParseException) {\n        Duration.ofNanos(parseDuration(this))\n    }\n}\n\n/**\n * Parses a duration string. If no units are specified in the string, it is\n * assumed to be in milliseconds. The returned duration is in nanoseconds.\n *\n * @param input the string to parse\n * @return duration in nanoseconds\n */\ninternal fun parseDuration(input: String): Long {\n    val s = input.trim()\n    val originalUnitString = getUnits(s)\n    var unitString = originalUnitString\n    val numberString = s.substring(0, s.length - unitString.length).trim()\n\n    // this would be caught later anyway, but the error message\n    // is more helpful if we check it here.\n    if (numberString.isEmpty())\n        throw ParseException(\"No number in duration value '$input'\")\n\n    if (unitString.length > 2 && !unitString.endsWith(\"s\"))\n        unitString += \"s\"\n\n    // note that this is deliberately case-sensitive\n    val units = if (unitString == \"\" || unitString == \"ms\" || unitString == \"millis\" ||\n        unitString == \"milliseconds\"\n    ) {\n        TimeUnit.MILLISECONDS\n    } else if (unitString == \"us\" || unitString == \"micros\" || unitString == \"microseconds\") {\n        TimeUnit.MICROSECONDS\n    } else if (unitString == \"ns\" || unitString == \"nanos\" || unitString == \"nanoseconds\") {\n        TimeUnit.NANOSECONDS\n    } else if (unitString == \"d\" || unitString == \"days\") {\n        TimeUnit.DAYS\n    } else if (unitString == \"h\" || unitString == \"hours\") {\n        TimeUnit.HOURS\n    } else if (unitString == \"s\" || unitString == \"seconds\") {\n        TimeUnit.SECONDS\n    } else if (unitString == \"m\" || unitString == \"minutes\") {\n        TimeUnit.MINUTES\n    } else {\n        throw ParseException(\"Could not parse time unit '$originalUnitString' (try ns, us, ms, s, m, h, d)\")\n    }\n\n    return try {\n        // if the string is purely digits, parse as an integer to avoid\n        // possible precision loss;\n        // otherwise as a double.\n        if (numberString.matches(\"[+-]?[0-9]+\".toRegex())) {\n            units.toNanos(java.lang.Long.parseLong(numberString))\n        } else {\n            val nanosInUnit = units.toNanos(1)\n            (java.lang.Double.parseDouble(numberString) * nanosInUnit).toLong()\n        }\n    } catch (e: NumberFormatException) {\n        throw ParseException(\"Could not parse duration number '$numberString'\")\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/Writer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport java.io.File\nimport java.io.OutputStream\nimport java.io.StringWriter\n\n/**\n * Save config to various output format.\n */\ninterface Writer {\n    /**\n     * Save to specified writer.\n     *\n     * @param writer specified writer for writing character streams\n     */\n    fun toWriter(writer: java.io.Writer)\n\n    /**\n     * Save to specified output stream.\n     *\n     * @param outputStream specified output stream of bytes\n     */\n    fun toOutputStream(outputStream: OutputStream)\n\n    /**\n     * Save to specified file.\n     *\n     * @param file specified file\n     * @return a new source from specified file\n     */\n    fun toFile(file: File) {\n        file.outputStream().use {\n            toOutputStream(it)\n        }\n    }\n\n    /**\n     * Save to specified file path.\n     *\n     * @param file specified file path\n     */\n    fun toFile(file: String) = toFile(File(file))\n\n    /**\n     * Save to string.\n     *\n     * @return string\n     */\n    fun toText(): String = StringWriter().apply { toWriter(this) }.toString()\n\n    /**\n     * Save to byte array.\n     *\n     * @return byte array\n     */\n    fun toBytes(): ByteArray = toText().toByteArray()\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/base/FlatSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ContainerNode\nimport com.uchuhimo.konf.ListNode\nimport com.uchuhimo.konf.PathConflictException\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.notEmptyOr\nimport com.uchuhimo.konf.source.ListSourceNode\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.source.SubstitutableNode\nimport com.uchuhimo.konf.source.ValueSourceNode\nimport com.uchuhimo.konf.source.asTree\nimport java.util.Collections\n\n/**\n * Source from a map in flat format.\n */\nopen class FlatSource(\n    val map: Map<String, String>,\n    type: String = \"\",\n    final override val info: SourceInfo = SourceInfo(),\n    private val allowConflict: Boolean = false\n) : Source {\n    init {\n        info[\"type\"] = type.notEmptyOr(\"flat\")\n    }\n\n    override val tree: TreeNode = ContainerNode(mutableMapOf()).apply {\n        map.forEach { (path, value) ->\n            try {\n                set(path, value.asTree())\n            } catch (ex: PathConflictException) {\n                if (!allowConflict) {\n                    throw ex\n                }\n            }\n        }\n    }.promoteToList()\n}\n\nobject EmptyStringNode : SubstitutableNode, ListNode {\n    override val value: Any = \"\"\n    override val list: List<TreeNode> = listOf()\n    override val originalValue: Any? = null\n    override val substituted: Boolean = false\n    override fun substitute(value: String): TreeNode {\n        check(value.isEmpty())\n        return this\n    }\n}\n\nclass SingleStringListNode(\n    override val value: String,\n    override val substituted: Boolean = false,\n    override val originalValue: Any? = null\n) : SubstitutableNode, ListNode {\n    override val children: MutableMap<String, TreeNode> = Collections.unmodifiableMap(\n        mutableMapOf(\"0\" to value.asTree())\n    )\n    override val list: List<TreeNode> = listOf(value.asTree())\n    override fun substitute(value: String): TreeNode = value.promoteToList(\n        true,\n        originalValue\n            ?: this.value\n    )\n}\n\nclass ListStringNode(\n    override val value: String,\n    override val substituted: Boolean = false,\n    override val originalValue: Any? = null\n) : ListSourceNode(value.split(',').map { ValueSourceNode(it) }), SubstitutableNode {\n    override fun substitute(value: String): TreeNode =\n        value.promoteToList(true, originalValue ?: this.value)\n\n    override val children: MutableMap<String, TreeNode> get() = super<ListSourceNode>.children\n}\n\nfun String.promoteToList(substitute: Boolean = false, originalValue: Any? = null): TreeNode {\n    return when {\n        ',' in this -> ListStringNode(this, substitute, originalValue)\n        this == \"\" -> EmptyStringNode\n        else -> SingleStringListNode(this, substitute, originalValue)\n    }\n}\n\nfun ContainerNode.promoteToList(): TreeNode {\n    for ((key, child) in children) {\n        if (child is ContainerNode) {\n            children[key] = child.promoteToList()\n        } else if (child is ValueNode) {\n            val value = child.value\n            if (value is String) {\n                children[key] = value.promoteToList()\n            }\n        }\n    }\n    val list = generateSequence(0) { it + 1 }.map {\n        val key = it.toString()\n        if (key in children) key else null\n    }.takeWhile {\n        it != null\n    }.filterNotNull().toList()\n    if (list.isNotEmpty() && list.toSet() == children.keys) {\n        return ListSourceNode(list.map { children[it]!! })\n    } else {\n        return this\n    }\n}\n\n/**\n * Returns a map in flat format for this config.\n *\n * The returned map contains all items in this config.\n * This map can be loaded into config as [com.uchuhimo.konf.source.base.FlatSource] using\n * `config.from.map.flat(map)`.\n */\nfun Config.toFlatMap(): Map<String, String> {\n    fun MutableMap<String, String>.putFlat(key: String, value: Any) {\n        when (value) {\n            is List<*> -> {\n                if (value.isNotEmpty()) {\n                    val first = value[0]\n                    when (first) {\n                        is List<*>, is Map<*, *> ->\n                            value.forEachIndexed { index, child ->\n                                putFlat(\"$key.$index\", child!!)\n                            }\n                        else -> {\n                            if (value.map { it.toString() }.any { it.contains(',') }) {\n                                value.forEachIndexed { index, child ->\n                                    putFlat(\"$key.$index\", child!!)\n                                }\n                            } else {\n                                put(key, value.joinToString(\",\"))\n                            }\n                        }\n                    }\n                } else {\n                    put(key, \"\")\n                }\n            }\n            is Map<*, *> ->\n                value.forEach { (suffix, child) ->\n                    putFlat(\"$key.$suffix\", child!!)\n                }\n            else -> put(key, value.toString())\n        }\n    }\n    return mutableMapOf<String, String>().apply {\n        for ((key, value) in this@toFlatMap.toMap()) {\n            putFlat(key, value)\n        }\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/base/KVSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.ContainerNode\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.notEmptyOr\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.source.asTree\n\n/**\n * Source from a map in key-value format.\n */\nopen class KVSource(\n    val map: Map<String, Any>,\n    type: String = \"\",\n    info: SourceInfo = SourceInfo()\n) : ValueSource(map, type.notEmptyOr(\"KV\"), info) {\n    override val tree: TreeNode = map.kvToTree()\n}\n\nfun Map<String, Any>.kvToTree(): TreeNode {\n    return ContainerNode(mutableMapOf()).apply {\n        this@kvToTree.forEach { (path, value) ->\n            set(path, value.asTree())\n        }\n    }\n}\n\nfun Map<String, Any>.asKVSource() = KVSource(this)\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/base/MapSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ListNode\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.notEmptyOr\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.toTree\n\n/**\n * Source from a hierarchical map.\n */\nopen class MapSource(\n    val map: Map<String, Any>,\n    type: String = \"\",\n    info: SourceInfo = SourceInfo()\n) : ValueSource(map, type.notEmptyOr(\"map\"), info)\n\n/**\n * Returns a hierarchical map for this config.\n *\n * The returned map contains all items in this config.\n * This map can be loaded into config as [com.uchuhimo.konf.source.base.MapSource] using\n * `config.from.map.hierarchical(map)`.\n */\n@Suppress(\"UNCHECKED_CAST\")\nfun Config.toHierarchicalMap(): Map<String, Any> {\n    return toTree().toHierarchical() as Map<String, Any>\n}\n\n/**\n * Returns a hierarchical value for this tree node.\n *\n * The returned value contains all items in this tree node.\n */\nfun TreeNode.toHierarchical(): Any = withoutPlaceHolder().toHierarchicalInternal()\n\nprivate fun TreeNode.toHierarchicalInternal(): Any {\n    when (this) {\n        is ValueNode -> return value\n        is ListNode -> return list.map { it.toHierarchicalInternal() }\n        else -> return children.mapValues { (_, child) -> child.toHierarchicalInternal() }\n    }\n}\n\n/**\n * Source from an empty map.\n */\nclass EmptyMapSource : MapSource(emptyMap(), \"empty map\")\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/base/ValueSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.notEmptyOr\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.source.asTree\n\n/**\n * Source from a single value.\n */\nopen class ValueSource(\n    val value: Any,\n    type: String = \"\",\n    final override val info: SourceInfo = SourceInfo()\n) : Source {\n    init {\n        info[\"type\"] = type.notEmptyOr(\"value\")\n    }\n\n    override val tree: TreeNode = value.asTree()\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/DurationDeserializer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.uchuhimo.konf.source.SourceException\nimport com.uchuhimo.konf.source.toDuration\nimport java.time.Duration\nimport java.time.format.DateTimeParseException\n\n/**\n * Deserializer for [Duration].\n */\nobject DurationDeserializer : JSR310Deserializer<Duration>(Duration::class.java) {\n    override fun parse(string: String): Duration {\n        return try {\n            Duration.parse(string)\n        } catch (exception: DateTimeParseException) {\n            try {\n                string.toDuration()\n            } catch (_: SourceException) {\n                throw exception\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/EmptyStringToCollectionDeserializerModifier.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.core.JsonToken\nimport com.fasterxml.jackson.databind.BeanDescription\nimport com.fasterxml.jackson.databind.BeanProperty\nimport com.fasterxml.jackson.databind.DeserializationConfig\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.JsonDeserializer\nimport com.fasterxml.jackson.databind.deser.BeanDeserializerModifier\nimport com.fasterxml.jackson.databind.deser.ContextualDeserializer\nimport com.fasterxml.jackson.databind.deser.ResolvableDeserializer\nimport com.fasterxml.jackson.databind.type.ArrayType\nimport com.fasterxml.jackson.databind.type.CollectionType\nimport com.fasterxml.jackson.databind.type.MapType\n\nobject EmptyStringToCollectionDeserializerModifier : BeanDeserializerModifier() {\n\n    override fun modifyMapDeserializer(\n        config: DeserializationConfig?,\n        type: MapType?,\n        beanDesc: BeanDescription?,\n        deserializer: JsonDeserializer<*>\n    ): JsonDeserializer<*>? =\n        object : JsonDeserializer<Map<Any, Any>>(), ContextualDeserializer, ResolvableDeserializer {\n            @Suppress(\"UNCHECKED_CAST\")\n            override fun deserialize(jp: JsonParser, ctx: DeserializationContext?): Map<Any, Any>? {\n                if (!jp.isExpectedStartArrayToken && jp.hasToken(JsonToken.VALUE_STRING) && jp.text.isEmpty()) {\n                    return deserializer.getEmptyValue(ctx) as Map<Any, Any>?\n                }\n                return deserializer.deserialize(jp, ctx) as Map<Any, Any>?\n            }\n\n            override fun createContextual(\n                ctx: DeserializationContext?,\n                property: BeanProperty?\n            ): JsonDeserializer<*>? =\n                modifyMapDeserializer(\n                    config,\n                    type,\n                    beanDesc,\n                    (deserializer as ContextualDeserializer)\n                        .createContextual(ctx, property)\n                )\n\n            override fun resolve(ctx: DeserializationContext?) {\n                (deserializer as? ResolvableDeserializer)?.resolve(ctx)\n            }\n        }\n\n    override fun modifyCollectionDeserializer(\n        config: DeserializationConfig?,\n        type: CollectionType?,\n        beanDesc: BeanDescription?,\n        deserializer: JsonDeserializer<*>\n    ): JsonDeserializer<*>? =\n        object : JsonDeserializer<Collection<Any>>(), ContextualDeserializer {\n            @Suppress(\"UNCHECKED_CAST\")\n            override fun deserialize(jp: JsonParser, ctx: DeserializationContext?): Collection<Any>? {\n                if (!jp.isExpectedStartArrayToken && jp.hasToken(JsonToken.VALUE_STRING) && jp.text.isEmpty()) {\n                    return deserializer.getEmptyValue(ctx) as Collection<Any>?\n                }\n                return deserializer.deserialize(jp, ctx) as Collection<Any>?\n            }\n\n            override fun createContextual(\n                ctx: DeserializationContext?,\n                property: BeanProperty?\n            ): JsonDeserializer<*>? =\n                modifyCollectionDeserializer(\n                    config,\n                    type,\n                    beanDesc,\n                    (deserializer as ContextualDeserializer)\n                        .createContextual(ctx, property)\n                )\n        }\n\n    override fun modifyArrayDeserializer(\n        config: DeserializationConfig?,\n        valueType: ArrayType?,\n        beanDesc: BeanDescription?,\n        deserializer: JsonDeserializer<*>\n    ): JsonDeserializer<*> =\n        object : JsonDeserializer<Any>(), ContextualDeserializer {\n            @Suppress(\"UNCHECKED_CAST\")\n            override fun deserialize(jp: JsonParser, ctx: DeserializationContext?): Any? {\n                if (!jp.isExpectedStartArrayToken && jp.hasToken(JsonToken.VALUE_STRING) && jp.text.isEmpty()) {\n                    val emptyValue = deserializer.getEmptyValue(ctx)\n                    return if (emptyValue is Array<*>) {\n                        java.lang.reflect.Array.newInstance(valueType!!.contentType.rawClass, 0)\n                    } else {\n                        emptyValue\n                    }\n                }\n                return deserializer.deserialize(jp, ctx)\n            }\n\n            override fun createContextual(\n                ctx: DeserializationContext?,\n                property: BeanProperty?\n            ): JsonDeserializer<*>? =\n                modifyArrayDeserializer(\n                    config,\n                    valueType,\n                    beanDesc,\n                    (deserializer as ContextualDeserializer)\n                        .createContextual(ctx, property)\n                )\n        }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/JSR310Deserializer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.core.JsonTokenId\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.deser.std.StdDeserializer\nimport com.fasterxml.jackson.databind.exc.MismatchedInputException\nimport java.time.format.DateTimeParseException\n\n/**\n * Base class of deserializers for datetime classes in JSR310.\n *\n * @param T type of datetime value\n */\nabstract class JSR310Deserializer<T>(clazz: Class<T>) : StdDeserializer<T>(clazz) {\n    /**\n     * Parses from a string to datetime value.\n     *\n     * @param string input string\n     * @return datetime value\n     * @throws DateTimeParseException\n     */\n    abstract fun parse(string: String): T\n\n    final override fun deserialize(parser: JsonParser, context: DeserializationContext): T? {\n        when (parser.currentTokenId()) {\n            JsonTokenId.ID_STRING -> {\n                val string = parser.text.trim { it <= ' ' }\n                if (string.isEmpty()) {\n                    return null\n                }\n                try {\n                    return parse(string)\n                } catch (exception: DateTimeParseException) {\n                    throw context.weirdStringException(string, handledType(), exception.message).apply {\n                        initCause(exception)\n                    }\n                }\n            }\n        }\n        throw MismatchedInputException.from(\n            parser,\n            handledType(),\n            \"Unexpected token (${parser.currentToken}), expected string for ${handledType().name} value\"\n        )\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/OffsetDateTimeDeserializer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport java.time.OffsetDateTime\n\n/**\n * Deserializer for [OffsetDateTime].\n */\nobject OffsetDateTimeDeserializer : JSR310Deserializer<OffsetDateTime>(OffsetDateTime::class.java) {\n    override fun parse(string: String): OffsetDateTime = OffsetDateTime.parse(string)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/StringDeserializer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.core.JsonToken\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.fasterxml.jackson.databind.deser.std.StringDeserializer as JacksonStringDeserializer\n\nobject StringDeserializer : JacksonStringDeserializer() {\n    override fun _deserializeFromArray(p: JsonParser, ctxt: DeserializationContext): String? {\n        val t = p.nextToken()\n        if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {\n            return getNullValue(ctxt)\n        }\n        if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {\n            val parsed = deserialize(p, ctxt)\n            val token = p.nextToken()\n            if (token != JsonToken.END_ARRAY) {\n                return parsed + \",\" + deserializeFromRestOfArray(token, p, ctxt)\n            }\n            return parsed\n        }\n        return deserializeFromRestOfArray(t, p, ctxt)\n    }\n\n    private fun deserializeFromRestOfArray(\n        token: JsonToken,\n        p: JsonParser,\n        ctxt: DeserializationContext\n    ): String {\n        var t = token\n        val sb = StringBuilder(64)\n        while (t != JsonToken.END_ARRAY) {\n            val str = if (t == JsonToken.VALUE_STRING) {\n                p.text\n            } else {\n                _parseString(p, ctxt)\n            }\n            if (sb.isEmpty()) {\n                sb.append(str)\n            } else {\n                sb.append(',').append(str)\n            }\n            t = p.nextToken()\n        }\n        return sb.toString()\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/deserializer/ZoneDateTimeDeserializer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport java.time.ZonedDateTime\n\n/**\n * Deserializer for [ZonedDateTime].\n */\nobject ZoneDateTimeDeserializer : JSR310Deserializer<ZonedDateTime>(ZonedDateTime::class.java) {\n    override fun parse(string: String): ZonedDateTime = ZonedDateTime.parse(string)\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.env\n\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.FlatSource\n\n/**\n * Provider for system environment source.\n */\nobject EnvProvider {\n    private val validEnv = Regex(\"(\\\\w+)(.\\\\w+)*\")\n\n    /**\n     * Returns a new source from system environment.\n     *\n     * @param nested whether to treat \"AA_BB_CC\" as nested format \"AA.BB.CC\" or not. True by default.\n     * @return a new source from system environment\n     */\n    @JvmOverloads\n    fun env(nested: Boolean = true): Source = envMap(System.getenv(), nested)\n\n    @JvmOverloads\n    fun envMap(map: Map<String, String>, nested: Boolean = true): Source {\n        return FlatSource(\n            map.mapKeys { (key, _) ->\n                if (nested) key.replace('_', '.') else key\n            }.filter { (key, _) ->\n                key.matches(validEnv)\n            }.toSortedMap(),\n            type = \"system-environment\",\n            allowConflict = true\n        ).enabled(\n            Feature.LOAD_KEYS_CASE_INSENSITIVELY\n        ).disabled(\n            Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED\n        )\n    }\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/json/JsonProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.Source\nimport java.io.InputStream\nimport java.io.Reader\n\n/**\n * Provider for JSON source.\n */\nobject JsonProvider : Provider {\n    override fun reader(reader: Reader): Source =\n        JsonSource(ObjectMapper().readTree(reader))\n\n    override fun inputStream(inputStream: InputStream): Source =\n        JsonSource(ObjectMapper().readTree(inputStream))\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/json/JsonSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.uchuhimo.konf.ContainerNode\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.source.ListSourceNode\nimport com.uchuhimo.konf.source.NullSourceNode\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.source.ValueSourceNode\n\n/**\n * Source from a JSON node.\n */\nclass JsonSource(\n    val node: JsonNode\n) : Source {\n    override val info: SourceInfo = SourceInfo(\"type\" to \"JSON\")\n\n    override val tree: TreeNode = node.toTree()\n}\n\nfun JsonNode.toTree(): TreeNode {\n    return when {\n        isNull -> NullSourceNode\n        isBoolean -> ValueSourceNode(booleanValue())\n        isNumber -> ValueSourceNode(numberValue())\n        isTextual -> ValueSourceNode(textValue())\n        isArray -> ListSourceNode(\n            mutableListOf<TreeNode>().apply {\n                elements().forEach {\n                    add(it.toTree())\n                }\n            }\n        )\n        isObject -> ContainerNode(\n            mutableMapOf<String, TreeNode>().apply {\n                for ((key, value) in fields()) {\n                    put(key, value.toTree())\n                }\n            }\n        )\n        isMissingNode -> ContainerNode(mutableMapOf())\n        else -> throw NotImplementedError()\n    }\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/json/JsonWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.fasterxml.jackson.core.util.DefaultIndenter\nimport com.fasterxml.jackson.core.util.DefaultPrettyPrinter\nimport com.fasterxml.jackson.databind.ObjectWriter\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport java.io.OutputStream\n\n/**\n * Writer for JSON source.\n */\nclass JsonWriter(val config: Config) : Writer {\n    private val objectWriter: ObjectWriter = config.mapper.writer(\n        DefaultPrettyPrinter().withObjectIndenter(\n            DefaultIndenter().withLinefeed(System.lineSeparator())\n        )\n    )\n    override fun toWriter(writer: java.io.Writer) {\n        objectWriter.writeValue(writer, config.toHierarchicalMap())\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        objectWriter.writeValue(outputStream, config.toHierarchicalMap())\n    }\n}\n\n/**\n * Returns Writer for JSON source.\n */\nval Config.toJson: Writer get() = JsonWriter(this)\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/properties/PropertiesProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.properties\n\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.FlatSource\nimport java.io.InputStream\nimport java.io.Reader\nimport java.util.Properties\n\n/**\n * Provider for properties source.\n */\nobject PropertiesProvider : Provider {\n    @Suppress(\"UNCHECKED_CAST\")\n    private fun Properties.toMap(): Map<String, String> = this as Map<String, String>\n\n    override fun reader(reader: Reader): Source =\n        FlatSource(Properties().apply { load(reader) }.toMap(), type = \"properties\")\n\n    override fun inputStream(inputStream: InputStream): Source =\n        FlatSource(Properties().apply { load(inputStream) }.toMap(), type = \"properties\")\n\n    /**\n     * Returns a new source from system properties.\n     *\n     * @return a new source from system properties\n     */\n    fun system(): Source = FlatSource(\n        System.getProperties().toMap(),\n        type = \"system-properties\",\n        allowConflict = true\n    )\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-core/src/main/kotlin/com/uchuhimo/konf/source/properties/PropertiesWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.properties\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toFlatMap\nimport java.io.FilterOutputStream\nimport java.io.OutputStream\nimport java.util.Properties\n\n/**\n * Writer for properties source.\n */\nclass PropertiesWriter(val config: Config) : Writer {\n    override fun toWriter(writer: java.io.Writer) {\n        NoCommentProperties().apply { putAll(config.toFlatMap()) }.store(writer, null)\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        NoCommentProperties().apply { putAll(config.toFlatMap()) }.store(outputStream, null)\n    }\n}\n\nprivate class NoCommentProperties : Properties() {\n    private class StripFirstLineStream(out: OutputStream) : FilterOutputStream(out) {\n        private var firstLineSeen = false\n\n        override fun write(b: Int) {\n            if (firstLineSeen) {\n                super.write(b)\n            } else if (b == '\\n'.toInt()) {\n                firstLineSeen = true\n            }\n        }\n    }\n\n    private class StripFirstLineWriter(writer: java.io.Writer) : java.io.FilterWriter(writer) {\n        override fun write(cbuf: CharArray, off: Int, len: Int) {\n            val offset = cbuf.indexOfFirst { it == '\\n' }\n            super.write(cbuf, offset + 1, len - offset - 1)\n        }\n    }\n\n    override fun store(out: OutputStream, comments: String?) {\n        super.store(StripFirstLineStream(out), null)\n    }\n\n    override fun store(writer: java.io.Writer, comments: String?) {\n        super.store(StripFirstLineWriter(writer), null)\n    }\n}\n\n/**\n * Returns writer for properties source.\n */\nval Config.toProperties: Writer get() = PropertiesWriter(this)\n"
  },
  {
    "path": "konf-core/src/test/java/com/uchuhimo/konf/AnonymousConfigSpec.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\npublic class AnonymousConfigSpec {\n  public static Spec spec = new ConfigSpec() {};\n}\n"
  },
  {
    "path": "konf-core/src/test/java/com/uchuhimo/konf/ConfigJavaApiTest.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\n\nimport com.uchuhimo.konf.source.Source;\nimport java.util.HashMap;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\n@DisplayName(\"test Java API of Config\")\nclass ConfigJavaApiTest {\n  private Config config;\n\n  @BeforeEach\n  void initConfig() {\n    config = Configs.create();\n    config.addSpec(NetworkBufferInJava.spec);\n  }\n\n  @Test\n  @DisplayName(\"test `Configs.create`\")\n  void create() {\n    final Config config = Configs.create();\n    assertThat(config.getItems().size(), equalTo(0));\n  }\n\n  @Test\n  @DisplayName(\"test `Configs.create` with init block\")\n  void createWithInit() {\n    final Config config = Configs.create(it -> it.addSpec(NetworkBufferInJava.spec));\n    assertThat(config.getItems().size(), equalTo(5));\n  }\n\n  @Test\n  @DisplayName(\"test fluent API to load from map\")\n  void loadFromMap() {\n    final HashMap<String, Integer> map = new HashMap<>();\n    map.put(config.nameOf(NetworkBufferInJava.size), 1024);\n    final Config newConfig = config.from().map.kv(map);\n    assertThat(newConfig.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n\n  @Test\n  @DisplayName(\"test fluent API to load from system properties\")\n  void loadFromSystem() {\n    System.setProperty(config.nameOf(NetworkBufferInJava.size), \"1024\");\n    final Config newConfig = config.from().systemProperties();\n    assertThat(newConfig.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n\n  @Test\n  @DisplayName(\"test fluent API to load from source\")\n  void loadFromSource() {\n    final HashMap<String, Integer> map = new HashMap<>();\n    map.put(config.nameOf(NetworkBufferInJava.size), 1024);\n    final Config newConfig = config.withSource(Source.from().map.kv(map));\n    assertThat(newConfig.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n\n  @Test\n  @DisplayName(\"test `get(Item<T>)`\")\n  void getWithItem() {\n    final String name = config.get(NetworkBufferInJava.name);\n    assertThat(name, equalTo(\"buffer\"));\n  }\n\n  @Test\n  @DisplayName(\"test `get(String)`\")\n  void getWithName() {\n    final NetworkBuffer.Type type = config.get(config.nameOf(NetworkBufferInJava.type));\n    assertThat(type, equalTo(NetworkBuffer.Type.OFF_HEAP));\n  }\n\n  @Test\n  @DisplayName(\"test `set(Item<T>, T)`\")\n  void setWithItem() {\n    config.set(NetworkBufferInJava.size, 1024);\n    assertThat(config.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n\n  @Test\n  @DisplayName(\"test `set(String, T)`\")\n  void setWithName() {\n    config.set(config.nameOf(NetworkBufferInJava.size), 1024);\n    assertThat(config.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n\n  @Test\n  @DisplayName(\"test `lazySet(Item<T>, Function1<ItemContainer, T>)`\")\n  void lazySetWithItem() {\n    config.lazySet(NetworkBufferInJava.maxSize, it -> it.get(NetworkBufferInJava.size) * 4);\n    config.set(NetworkBufferInJava.size, 1024);\n    assertThat(config.get(NetworkBufferInJava.maxSize), equalTo(1024 * 4));\n  }\n\n  @Test\n  @DisplayName(\"test `lazySet(String, Function1<ItemContainer, T>)`\")\n  void lazySetWithName() {\n    config.lazySet(\n        config.nameOf(NetworkBufferInJava.maxSize), it -> it.get(NetworkBufferInJava.size) * 4);\n    config.set(NetworkBufferInJava.size, 1024);\n    assertThat(config.get(NetworkBufferInJava.maxSize), equalTo(1024 * 4));\n  }\n}\n"
  },
  {
    "path": "konf-core/src/test/java/com/uchuhimo/konf/NetworkBufferInJava.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\npublic class NetworkBufferInJava {\n  public static final ConfigSpec spec = new ConfigSpec(\"network.buffer\");\n\n  public static final RequiredItem<Integer> size =\n      new RequiredItem<Integer>(spec, \"size\", \"size of buffer in KB\") {};\n\n  public static final LazyItem<Integer> maxSize =\n      new LazyItem<Integer>(\n          spec, \"maxSize\", config -> config.get(size) * 2, \"max size of buffer in KB\") {};\n\n  public static final OptionalItem<String> name =\n      new OptionalItem<String>(spec, \"name\", \"buffer\", \"name of buffer\") {};\n\n  public static final OptionalItem<NetworkBuffer.Type> type =\n      new OptionalItem<NetworkBuffer.Type>(\n          spec,\n          \"type\",\n          NetworkBuffer.Type.OFF_HEAP,\n          \"type of network buffer.\\n\"\n              + \"two type:\\n\"\n              + \"- on-heap\\n\"\n              + \"- off-heap\\n\"\n              + \"buffer is off-heap by default.\") {};\n\n  public static final OptionalItem<Integer> offset =\n      new OptionalItem<Integer>(spec, \"offset\", null, \"initial offset of buffer\", null, true) {};\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/AdHocConfigItemSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.toValue\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport kotlin.test.assertNull\n\nobject AdHocConfigItemSpec : Spek({\n    on(\"load config into ad-hoc config class with ad-hoc config items\") {\n        val config = Config().from.map.kv(\n            mapOf(\n                \"network.buffer.size\" to 1,\n                \"network.buffer.heap.type\" to AdHocNetworkBuffer.Type.ON_HEAP,\n                \"network.buffer.offset\" to 0\n            )\n        )\n        val networkBuffer = AdHocNetworkBuffer(config)\n        it(\"should load correct values\") {\n            assertThat(networkBuffer.size, equalTo(1))\n            assertThat(networkBuffer.maxSize, equalTo(2))\n            assertThat(networkBuffer.name, equalTo(\"buffer\"))\n            assertThat(networkBuffer.type, equalTo(AdHocNetworkBuffer.Type.ON_HEAP))\n            assertThat(networkBuffer.offset, equalTo(0))\n        }\n    }\n    val source = Source.from.map.hierarchical(\n        mapOf(\n            \"size\" to 1,\n            \"maxSize\" to 2,\n            \"name\" to \"buffer\",\n            \"type\" to \"ON_HEAP\",\n            \"offset\" to \"null\"\n        )\n    )\n    on(\"cast config to config class property\") {\n        val networkBufferForCast: NetworkBufferForCast by Config().withSource(source).cast()\n        it(\"should load correct values\") {\n            assertThat(networkBufferForCast.size, equalTo(1))\n            assertThat(networkBufferForCast.maxSize, equalTo(2))\n            assertThat(networkBufferForCast.name, equalTo(\"buffer\"))\n            assertThat(networkBufferForCast.type, equalTo(NetworkBufferForCast.Type.ON_HEAP))\n            assertNull(networkBufferForCast.offset)\n        }\n    }\n    on(\"cast config to config class\") {\n        val networkBufferForCast = Config().withSource(source).toValue<NetworkBufferForCast>()\n        it(\"should load correct values\") {\n            assertThat(networkBufferForCast.size, equalTo(1))\n            assertThat(networkBufferForCast.maxSize, equalTo(2))\n            assertThat(networkBufferForCast.name, equalTo(\"buffer\"))\n            assertThat(networkBufferForCast.type, equalTo(NetworkBufferForCast.Type.ON_HEAP))\n            assertNull(networkBufferForCast.offset)\n        }\n    }\n    on(\"cast multi-layer config to config class\") {\n        val networkBufferForCast = Config().withSource(source).from.json.string(\"\").toValue<NetworkBufferForCast>()\n        it(\"should load correct values\") {\n            assertThat(networkBufferForCast.size, equalTo(1))\n            assertThat(networkBufferForCast.maxSize, equalTo(2))\n            assertThat(networkBufferForCast.name, equalTo(\"buffer\"))\n            assertThat(networkBufferForCast.type, equalTo(NetworkBufferForCast.Type.ON_HEAP))\n            assertNull(networkBufferForCast.offset)\n        }\n    }\n    on(\"cast config with merged source to config class\") {\n        val networkBufferForCast = Config().withSource(source + Source.from.json.string(\"\")).toValue<NetworkBufferForCast>()\n        it(\"should load correct values\") {\n            assertThat(networkBufferForCast.size, equalTo(1))\n            assertThat(networkBufferForCast.maxSize, equalTo(2))\n            assertThat(networkBufferForCast.name, equalTo(\"buffer\"))\n            assertThat(networkBufferForCast.type, equalTo(NetworkBufferForCast.Type.ON_HEAP))\n            assertNull(networkBufferForCast.offset)\n        }\n    }\n    on(\"cast source to config class\") {\n        val networkBufferForCast = source.toValue<NetworkBufferForCast>()\n        it(\"should load correct values\") {\n            assertThat(networkBufferForCast.size, equalTo(1))\n            assertThat(networkBufferForCast.maxSize, equalTo(2))\n            assertThat(networkBufferForCast.name, equalTo(\"buffer\"))\n            assertThat(networkBufferForCast.type, equalTo(NetworkBufferForCast.Type.ON_HEAP))\n            assertNull(networkBufferForCast.offset)\n        }\n    }\n})\n\ndata class NetworkBufferForCast(\n    val size: Int,\n    val maxSize: Int,\n    val name: String,\n    val type: Type,\n    val offset: Int?\n) {\n\n    enum class Type {\n        ON_HEAP, OFF_HEAP\n    }\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/AdHocNetworkBuffer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nclass AdHocNetworkBuffer(config: Config) {\n    private val root = config.at(\"network.buffer\")\n\n    val size: Int by root.required(description = \"size of buffer in KB\")\n\n    val maxSize by root.lazy(name = \"max-size\", description = \"max size of buffer in KB\") { size * 2 }\n\n    val name by root.optional(\"buffer\", description = \"name of buffer\")\n\n    val type by root.optional(\n        Type.OFF_HEAP,\n        prefix = \"heap\",\n        description =\n        \"\"\"\n            | type of network buffer.\n            | two type:\n            | - on-heap\n            | - off-heap\n            | buffer is off-heap by default.\n            \"\"\".trimMargin(\"| \")\n    )\n\n    val offset by root.optional<Int?>(null, description = \"initial offset of buffer\")\n\n    enum class Type {\n        ON_HEAP, OFF_HEAP\n    }\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/ConfigInJavaSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.absent\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.base.asKVSource\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport kotlin.test.assertFalse\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject ConfigInJavaSpec : SubjectSpek<Config>({\n\n    val spec = NetworkBufferInJava.spec\n    val size = NetworkBufferInJava.size\n    val maxSize = NetworkBufferInJava.maxSize\n    val name = NetworkBufferInJava.name\n    val type = NetworkBufferInJava.type\n    val offset = NetworkBufferInJava.offset\n    val prefix = \"network.buffer\"\n\n    fun qualify(name: String): String = \"$prefix.$name\"\n\n    subject { Config { addSpec(spec) } }\n\n    given(\"a config\") {\n        val invalidItem by ConfigSpec(\"invalid\").required<Int>()\n        val invalidItemName = \"invalid.invalidItem\"\n        group(\"addSpec operation\") {\n            on(\"add orthogonal spec\") {\n                val newSpec = object : ConfigSpec(spec.prefix) {\n                    val minSize by optional(1)\n                }\n                val config = subject.withSource(mapOf(newSpec.qualify(newSpec.minSize) to 2).asKVSource())\n                config.addSpec(newSpec)\n                it(\"should contain items in new spec\") {\n                    assertTrue { newSpec.minSize in config }\n                    assertTrue { spec.qualify(newSpec.minSize) in config }\n                    assertThat(config.nameOf(newSpec.minSize), equalTo(spec.qualify(newSpec.minSize)))\n                }\n                it(\"should contain new spec\") {\n                    assertThat(newSpec in config.specs, equalTo(true))\n                    assertThat(spec in config.specs, equalTo(true))\n                }\n                it(\"should load values from the existed sources for items in new spec\") {\n                    assertThat(config[newSpec.minSize], equalTo(2))\n                }\n            }\n            on(\"add repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { subject.addSpec(spec) },\n                        throws(\n                            has(\n                                RepeatedItemException::name,\n                                equalTo(spec.qualify(size))\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"add repeated name\") {\n                val newSpec = ConfigSpec(prefix).apply {\n                    @Suppress(\"UNUSED_VARIABLE\", \"NAME_SHADOWING\")\n                    val size by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addSpec(newSpec) }, throws<NameConflictException>())\n                }\n            }\n            on(\"add conflict name, which is prefix of existed name\") {\n                val newSpec = ConfigSpec().apply {\n                    @Suppress(\"UNUSED_VARIABLE\")\n                    val buffer by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat(\n                        {\n                            subject.addSpec(\n                                newSpec.withPrefix(prefix.toPath().let { it.subList(0, it.size - 1) }.name)\n                            )\n                        },\n                        throws<NameConflictException>()\n                    )\n                }\n            }\n            on(\"add conflict name, and an existed name is prefix of it\") {\n                val newSpec = ConfigSpec(qualify(type.name)).apply {\n                    @Suppress(\"UNUSED_VARIABLE\")\n                    val subType by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addSpec(newSpec) }, throws<NameConflictException>())\n                }\n            }\n        }\n        group(\"addItem operation\") {\n            on(\"add orthogonal item\") {\n                val minSize by Spec.dummy.optional(1)\n                val config = subject.withSource(mapOf(spec.qualify(minSize) to 2).asKVSource())\n                config.addItem(minSize, spec.prefix)\n                it(\"should contain item\") {\n                    assertTrue { minSize in config }\n                    assertTrue { spec.qualify(minSize) in config }\n                    assertThat(config.nameOf(minSize), equalTo(spec.qualify(minSize)))\n                }\n                it(\"should load values from the existed sources for item\") {\n                    assertThat(config[minSize], equalTo(2))\n                }\n            }\n            on(\"add repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { subject.addItem(size, spec.prefix) },\n                        throws(\n                            has(\n                                RepeatedItemException::name,\n                                equalTo(spec.qualify(size))\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"add repeated name\") {\n                @Suppress(\"NAME_SHADOWING\")\n                val size by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addItem(size, prefix) }, throws<NameConflictException>())\n                }\n            }\n            on(\"add conflict name, which is prefix of existed name\") {\n                val buffer by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat(\n                        {\n                            subject.addItem(\n                                buffer,\n                                prefix.toPath().let { it.subList(0, it.size - 1) }.name\n                            )\n                        },\n                        throws<NameConflictException>()\n                    )\n                }\n            }\n            on(\"add conflict name, and an existed name is prefix of it\") {\n                val subType by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addItem(subType, qualify(type.name)) }, throws<NameConflictException>())\n                }\n            }\n        }\n        on(\"iterate items in config\") {\n            it(\"should cover all items in config\") {\n                assertThat(subject.items.toSet(), equalTo(spec.items.toSet()))\n            }\n        }\n        on(\"iterate name of items in config\") {\n            it(\"should cover all items in config\") {\n                assertThat(subject.nameOfItems.toSet(), equalTo(spec.items.map { qualify(it.name) }.toSet()))\n            }\n        }\n        on(\"export values to map\") {\n            it(\"should not contain unset items in map\") {\n                assertThat(\n                    subject.toMap(),\n                    equalTo(\n                        mapOf<String, Any>(\n                            qualify(name.name) to \"buffer\",\n                            qualify(type.name) to NetworkBuffer.Type.OFF_HEAP.name,\n                            qualify(offset.name) to \"null\"\n                        )\n                    )\n                )\n            }\n            it(\"should contain corresponding items in map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toMap()\n                assertThat(\n                    map,\n                    equalTo(\n                        mapOf(\n                            qualify(size.name) to 4,\n                            qualify(maxSize.name) to 8,\n                            qualify(name.name) to \"buffer\",\n                            qualify(type.name) to NetworkBuffer.Type.ON_HEAP.name,\n                            qualify(offset.name) to 0\n                        )\n                    )\n                )\n            }\n            it(\"should recover all items when reloaded from map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toMap()\n                val newConfig = Config { addSpec(spec[spec.prefix].withPrefix(prefix)) }.from.map.kv(map)\n                assertThat(newConfig[size], equalTo(4))\n                assertThat(newConfig[maxSize], equalTo(8))\n                assertThat(newConfig[name], equalTo(\"buffer\"))\n                assertThat(newConfig[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                assertThat(newConfig[offset], equalTo(0))\n                assertThat(newConfig.toMap(), equalTo(subject.toMap()))\n            }\n        }\n        on(\"export values to hierarchical map\") {\n            fun prefixToMap(prefix: String, value: Map<String, Any>): Map<String, Any> {\n                return when {\n                    prefix.isEmpty() -> value\n                    prefix.contains('.') ->\n                        mapOf<String, Any>(\n                            prefix.substring(0, prefix.indexOf('.')) to\n                                prefixToMap(prefix.substring(prefix.indexOf('.') + 1), value)\n                        )\n                    else -> mapOf(prefix to value)\n                }\n            }\n            it(\"should not contain unset items in map\") {\n                assertThat(\n                    subject.toHierarchicalMap(),\n                    equalTo(\n                        prefixToMap(\n                            prefix,\n                            mapOf(\n                                \"name\" to \"buffer\",\n                                \"type\" to NetworkBuffer.Type.OFF_HEAP.name,\n                                \"offset\" to \"null\"\n                            )\n                        )\n                    )\n                )\n            }\n            it(\"should contain corresponding items in map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toHierarchicalMap()\n                assertThat(\n                    map,\n                    equalTo(\n                        prefixToMap(\n                            prefix,\n                            mapOf(\n                                \"size\" to 4,\n                                \"maxSize\" to 8,\n                                \"name\" to \"buffer\",\n                                \"type\" to NetworkBuffer.Type.ON_HEAP.name,\n                                \"offset\" to 0\n                            )\n                        )\n                    )\n                )\n            }\n            it(\"should recover all items when reloaded from map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toHierarchicalMap()\n                val newConfig = Config { addSpec(spec[spec.prefix].withPrefix(prefix)) }.from.map.hierarchical(map)\n                assertThat(newConfig[size], equalTo(4))\n                assertThat(newConfig[maxSize], equalTo(8))\n                assertThat(newConfig[name], equalTo(\"buffer\"))\n                assertThat(newConfig[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                assertThat(newConfig[offset], equalTo(0))\n                assertThat(newConfig.toMap(), equalTo(subject.toMap()))\n            }\n        }\n        on(\"object methods\") {\n            val map = mapOf(\n                qualify(name.name) to \"buffer\",\n                qualify(type.name) to NetworkBuffer.Type.OFF_HEAP.name,\n                qualify(offset.name) to \"null\"\n            )\n            it(\"should not equal to object of other class\") {\n                assertFalse(subject.equals(1))\n            }\n            it(\"should equal to itself\") {\n                assertThat(subject, equalTo(subject))\n            }\n            it(\"should convert to string in map-like format\") {\n                assertThat(subject.toString(), equalTo(\"Config(items=$map)\"))\n            }\n        }\n        on(\"lock config\") {\n            it(\"should be locked\") {\n                subject.lock { }\n            }\n        }\n        group(\"get operation\") {\n            on(\"get with valid item\") {\n                it(\"should return corresponding value\") {\n                    assertThat(subject[name], equalTo(\"buffer\"))\n                    assertTrue { name in subject }\n                    assertNull(subject[offset])\n                    assertTrue { offset in subject }\n                    assertNull(subject.getOrNull(maxSize))\n                    assertTrue { maxSize in subject }\n                }\n            }\n            on(\"get with invalid item\") {\n                it(\"should throw NoSuchItemException when using `get`\") {\n                    assertThat(\n                        { subject[invalidItem] },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n                it(\"should return null when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(invalidItem), absent())\n                    assertTrue { invalidItem !in subject }\n                }\n            }\n            on(\"get with valid name\") {\n                it(\"should return corresponding value\") {\n                    assertThat(subject(qualify(\"name\")), equalTo(\"buffer\"))\n                    assertThat(subject.getOrNull(qualify(\"name\")), equalTo(\"buffer\"))\n                    assertTrue { qualify(\"name\") in subject }\n                }\n            }\n            on(\"get with invalid name\") {\n                it(\"should throw NoSuchItemException when using `get`\") {\n                    assertThat(\n                        { subject<String>(spec.qualify(invalidItem)) },\n                        throws(\n                            has(\n                                NoSuchItemException::name,\n                                equalTo(spec.qualify(invalidItem))\n                            )\n                        )\n                    )\n                }\n                it(\"should return null when using `getOrNull`\") {\n                    assertThat(subject.getOrNull<String>(spec.qualify(invalidItem)), absent())\n                    assertTrue { spec.qualify(invalidItem) !in subject }\n                }\n            }\n            on(\"get unset item\") {\n                it(\"should throw UnsetValueException\") {\n                    assertThat(\n                        { subject[size] },\n                        throws(\n                            has(\n                                UnsetValueException::name,\n                                equalTo(size.asName)\n                            )\n                        )\n                    )\n                    assertThat(\n                        { subject[maxSize] },\n                        throws(\n                            has(\n                                UnsetValueException::name,\n                                equalTo(size.asName)\n                            )\n                        )\n                    )\n                    assertTrue { size in subject }\n                    assertTrue { maxSize in subject }\n                }\n            }\n            on(\"get with lazy item that returns null when the type is nullable\") {\n                it(\"should return null\") {\n                    val lazyItem by Spec.dummy.lazy<Int?> { null }\n                    subject.addItem(lazyItem, prefix)\n                    assertNull(subject[lazyItem])\n                }\n            }\n            on(\"get with lazy item that returns null when the type is not nullable\") {\n                it(\"should throw InvalidLazySetException\") {\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val thunk = { _: ItemContainer -> null } as (ItemContainer) -> Int\n                    val lazyItem by Spec.dummy.lazy(thunk = thunk)\n                    subject.addItem(lazyItem, prefix)\n                    assertThat({ subject[lazyItem] }, throws<InvalidLazySetException>())\n                }\n            }\n        }\n        group(\"set operation\") {\n            on(\"set with valid item when corresponding value is unset\") {\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[size], equalTo(1024))\n                }\n            }\n            on(\"set with valid item when corresponding value exists\") {\n                it(\"should contain the specified value\") {\n                    subject[name] = \"newName\"\n                    assertThat(subject[name], equalTo(\"newName\"))\n                    subject[offset] = 0\n                    assertThat(subject[offset], equalTo(0))\n                    subject[offset] = null\n                    assertNull(subject[offset])\n                }\n            }\n            on(\"raw set with valid item\") {\n                it(\"should contain the specified value\") {\n                    subject.rawSet(size, 2048)\n                    assertThat(subject[size], equalTo(2048))\n                }\n            }\n            on(\"set with valid item when corresponding value is lazy\") {\n                test(\n                    \"before set, the item should be lazy; after set,\" +\n                        \" the item should be no longer lazy, and it contains the specified value\"\n                ) {\n                    subject[size] = 1024\n                    assertThat(subject[maxSize], equalTo(subject[size] * 2))\n                    subject[maxSize] = 0\n                    assertThat(subject[maxSize], equalTo(0))\n                    subject[size] = 2048\n                    assertThat(subject[maxSize], !equalTo(subject[size] * 2))\n                    assertThat(subject[maxSize], equalTo(0))\n                }\n            }\n            on(\"set with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject[invalidItem] = 1024 },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"set with valid name\") {\n                subject[qualify(\"size\")] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[size], equalTo(1024))\n                }\n            }\n            on(\"set with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject[invalidItemName] = 1024 },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n            on(\"set with incorrect type of value\") {\n                it(\"should throw ClassCastException\") {\n                    assertThat({ subject[qualify(size.name)] = \"1024\" }, throws<ClassCastException>())\n                    assertThat({ subject[qualify(size.name)] = null }, throws<ClassCastException>())\n                }\n            }\n            on(\"lazy set with valid item\") {\n                subject.lazySet(maxSize) { it[size] * 4 }\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[maxSize], equalTo(subject[size] * 4))\n                }\n            }\n            on(\"lazy set with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.lazySet(invalidItem) { 1024 } },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"lazy set with valid name\") {\n                subject.lazySet(qualify(maxSize.name)) { it[size] * 4 }\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[maxSize], equalTo(subject[size] * 4))\n                }\n            }\n            on(\"lazy set with valid name and invalid value with incompatible type\") {\n                subject.lazySet(qualify(maxSize.name)) { \"string\" }\n                it(\"should throw InvalidLazySetException when getting\") {\n                    assertThat({ subject[qualify(maxSize.name)] }, throws<InvalidLazySetException>())\n                }\n            }\n            on(\"lazy set with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.lazySet(invalidItemName) { 1024 } },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n            on(\"unset with valid item\") {\n                subject.unset(type)\n                it(\"should contain `null` when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(type), absent())\n                }\n            }\n            on(\"unset with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.unset(invalidItem) },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"unset with valid name\") {\n                subject.unset(qualify(type.name))\n                it(\"should contain `null` when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(type), absent())\n                }\n            }\n            on(\"unset with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.unset(invalidItemName) },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n        }\n        on(\"clear operation\") {\n            it(\"should contain no value\") {\n                val config = if (subject.name == \"multi-layer\") {\n                    subject.parent!!\n                } else {\n                    subject\n                }\n                assertTrue { name in config && type in config }\n                config.clear()\n                assertTrue { name !in config && type !in config }\n            }\n        }\n        group(\"item property\") {\n            on(\"declare a property by item\") {\n                var nameProperty by subject.property(name)\n                it(\"should behave same as `get`\") {\n                    assertThat(nameProperty, equalTo(subject[name]))\n                }\n                it(\"should support set operation as `set`\") {\n                    nameProperty = \"newName\"\n                    assertThat(nameProperty, equalTo(\"newName\"))\n                }\n            }\n            on(\"declare a property by invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        {\n                            @Suppress(\"UNUSED_VARIABLE\")\n                            var nameProperty by subject.property(invalidItem)\n                        },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"declare a property by name\") {\n                var nameProperty by subject.property<String>(qualify(name.name))\n                it(\"should behave same as `get`\") {\n                    assertThat(nameProperty, equalTo(subject[name]))\n                }\n                it(\"should support set operation as `set`\") {\n                    nameProperty = \"newName\"\n                    assertThat(nameProperty, equalTo(\"newName\"))\n                }\n            }\n            on(\"declare a property by invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        {\n                            @Suppress(\"UNUSED_VARIABLE\")\n                            var nameProperty by subject.property<Int>(invalidItemName)\n                        },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/ConfigSpecTestSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.databind.type.TypeFactory\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.isIn\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport kotlin.test.assertFalse\nimport kotlin.test.assertTrue\n\nobject ConfigSpecTestSpec : Spek({\n    given(\"a configSpec\") {\n        fun testItem(spec: Spec, item: Item<*>, description: String) {\n            group(\"for $description, as an item\") {\n                on(\"add to a configSpec\") {\n                    it(\"should be in the spec\") {\n                        assertThat(item, isIn(spec.items))\n                    }\n                    it(\"should have the specified description\") {\n                        assertThat(item.description, equalTo(\"description\"))\n                    }\n                    it(\"should name without prefix\") {\n                        assertThat(item.name, equalTo(\"c.int\"))\n                    }\n                    it(\"should have a valid path\") {\n                        assertThat(item.path, equalTo(listOf(\"c\", \"int\")))\n                    }\n                    it(\"should point to the spec\") {\n                        assertThat(item.spec, equalTo(spec))\n                    }\n                    it(\"should have specified type\") {\n                        assertThat(\n                            item.type,\n                            equalTo(\n                                TypeFactory.defaultInstance()\n                                    .constructType(Int::class.javaObjectType)\n                            )\n                        )\n                    }\n                }\n            }\n        }\n\n        val specForRequired = object : ConfigSpec(\"a.b\") {\n            val item by required<Int>(\"c.int\", \"description\")\n        }\n        testItem(specForRequired, specForRequired.item, \"a required item\")\n        group(\"for a required item\") {\n            val spec = specForRequired\n            on(\"add to a configSpec\") {\n                it(\"should still be a required item\") {\n                    assertFalse(spec.item.nullable)\n                    assertTrue(spec.item.isRequired)\n                    assertFalse(spec.item.isOptional)\n                    assertFalse(spec.item.isLazy)\n                    assertThat(spec.item.asRequiredItem, sameInstance(spec.item))\n                    assertThat({ spec.item.asOptionalItem }, throws<ClassCastException>())\n                    assertThat({ spec.item.asLazyItem }, throws<ClassCastException>())\n                }\n            }\n        }\n        val specForOptional = object : ConfigSpec(\"a.b\") {\n            val item by optional(1, \"c.int\", \"description\")\n        }\n        testItem(specForOptional, specForOptional.item, \"an optional item\")\n        group(\"for an optional item\") {\n            val spec = specForOptional\n            on(\"add to a configSpec\") {\n                it(\"should still be an optional item\") {\n                    assertFalse(spec.item.nullable)\n                    assertFalse(spec.item.isRequired)\n                    assertTrue(spec.item.isOptional)\n                    assertFalse(spec.item.isLazy)\n                    assertThat({ spec.item.asRequiredItem }, throws<ClassCastException>())\n                    assertThat(spec.item.asOptionalItem, sameInstance(spec.item))\n                    assertThat({ spec.item.asLazyItem }, throws<ClassCastException>())\n                }\n                it(\"should contain the specified default value\") {\n                    assertThat(spec.item.default, equalTo(1))\n                }\n            }\n        }\n        val specForLazy = object : ConfigSpec(\"a.b\") {\n            val item by lazy<Int?>(\"c.int\", \"description\") { 2 }\n        }\n        val config = Config { addSpec(specForLazy) }\n        testItem(specForLazy, specForLazy.item, \"a lazy item\")\n        group(\"for a lazy item\") {\n            val spec = specForLazy\n            on(\"add to a configSpec\") {\n                it(\"should still be a lazy item\") {\n                    assertTrue(spec.item.nullable)\n                    assertFalse(spec.item.isRequired)\n                    assertFalse(spec.item.isOptional)\n                    assertTrue(spec.item.isLazy)\n                    assertThat({ spec.item.asRequiredItem }, throws<ClassCastException>())\n                    assertThat({ spec.item.asOptionalItem }, throws<ClassCastException>())\n                    assertThat(spec.item.asLazyItem, sameInstance(spec.item))\n                }\n                it(\"should contain the specified thunk\") {\n                    assertThat(specForLazy.item.thunk(config), equalTo(2))\n                }\n            }\n        }\n        on(\"add repeated item\") {\n            val spec = ConfigSpec()\n            val item by Spec.dummy.required<Int>()\n            spec.addItem(item)\n            it(\"should throw RepeatedItemException\") {\n                assertThat(\n                    { spec.addItem(item) },\n                    throws(has(RepeatedItemException::name, equalTo(\"item\")))\n                )\n            }\n        }\n        on(\"add inner spec\") {\n            val spec = ConfigSpec()\n            val innerSpec: Spec = ConfigSpec()\n            spec.addInnerSpec(innerSpec)\n            it(\"should contain the added spec\") {\n                assertThat(spec.innerSpecs, equalTo(setOf(innerSpec)))\n            }\n            it(\"should throw RepeatedInnerSpecException when adding repeated spec\") {\n                assertThat(\n                    { spec.addInnerSpec(innerSpec) },\n                    throws(has(RepeatedInnerSpecException::spec, equalTo(innerSpec)))\n                )\n            }\n        }\n        val spec = Nested\n        group(\"get operation\") {\n            on(\"get an empty path\") {\n                it(\"should return itself\") {\n                    assertThat(spec[\"\"], equalTo<Spec>(spec))\n                }\n            }\n            on(\"get a valid path\") {\n                it(\"should return a config spec with proper prefix\") {\n                    assertThat(spec[\"a\"].prefix, equalTo(\"bb\"))\n                    assertThat(spec[\"a.bb\"].prefix, equalTo(\"\"))\n                }\n                it(\"should return a config spec with the proper items and inner specs\") {\n                    assertThat(spec[\"a\"].items, equalTo(spec.items))\n                    assertThat(spec[\"a\"].innerSpecs, equalTo(spec.innerSpecs))\n                    assertThat(spec[\"a.bb.inner\"].items, equalTo(Nested.Inner.items))\n                    assertThat(spec[\"a.bb.inner\"].innerSpecs.size, equalTo(0))\n                    assertThat(spec[\"a.bb.inner\"].prefix, equalTo(\"\"))\n                    assertThat(spec[\"a.bb.inner2\"].items, equalTo(Nested.Inner2.items))\n                    assertThat(spec[\"a.bb.inner2\"].innerSpecs.size, equalTo(0))\n                    assertThat(spec[\"a.bb.inner2\"].prefix, equalTo(\"level2\"))\n                    assertThat(spec[\"a.bb.inner3\"].items.size, equalTo(0))\n                    assertThat(spec[\"a.bb.inner3\"].innerSpecs.size, equalTo(2))\n                    assertThat(spec[\"a.bb.inner3\"].innerSpecs.toList()[0].prefix, equalTo(\"a\"))\n                    assertThat(spec[\"a.bb.inner3\"].innerSpecs.toList()[1].prefix, equalTo(\"b\"))\n                }\n            }\n            on(\"get an invalid path\") {\n                it(\"should throw NoSuchPathException\") {\n                    assertThat({ spec[\"b\"] }, throws(has(NoSuchPathException::path, equalTo(\"b\"))))\n                    assertThat({ spec[\"a.\"] }, throws<InvalidPathException>())\n                    assertThat({ spec[\"a.b\"] }, throws(has(NoSuchPathException::path, equalTo(\"a.b\"))))\n                    assertThat(\n                        {\n                            spec[\"a.bb.inner4\"]\n                        },\n                        throws(has(NoSuchPathException::path, equalTo(\"a.bb.inner4\")))\n                    )\n                }\n            }\n        }\n        group(\"prefix operation\") {\n            on(\"prefix with an empty path\") {\n                it(\"should return itself\") {\n                    assertThat(Prefix(\"\") + spec, equalTo<Spec>(spec))\n                }\n            }\n            on(\"prefix with a non-empty path\") {\n                it(\"should return a config spec with proper prefix\") {\n                    assertThat((Prefix(\"c\") + spec).prefix, equalTo(\"c.a.bb\"))\n                    assertThat((Prefix(\"c\") + spec[\"a.bb\"]).prefix, equalTo(\"c\"))\n                }\n                it(\"should return a config spec with the same items and inner specs\") {\n                    assertThat((Prefix(\"c\") + spec).items, equalTo(spec.items))\n                    assertThat((Prefix(\"c\") + spec).innerSpecs, equalTo(spec.innerSpecs))\n                }\n            }\n        }\n        group(\"plus operation\") {\n            val spec1 = object : ConfigSpec(\"a\") {\n                val item1 by required<Int>()\n            }\n            val spec2 = object : ConfigSpec(\"b\") {\n                val item2 by required<Int>()\n            }\n            @Suppress(\"NAME_SHADOWING\")\n            val spec by memoized { spec1 + spec2 }\n            on(\"add a valid item\") {\n                it(\"should contains the item in the facade spec\") {\n                    val item by Spec.dummy.required<Int>()\n                    spec.addItem(item)\n                    assertThat(item, isIn(spec.items))\n                    assertThat(item, isIn(spec2.items))\n                }\n            }\n            on(\"add a repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { spec.addItem(spec1.item1) },\n                        throws(has(RepeatedItemException::name, equalTo(\"item1\")))\n                    )\n                }\n            }\n            on(\"get the list of items\") {\n                it(\"should contains all items in both the facade spec and the fallback spec\") {\n                    assertThat(spec.items, equalTo(spec1.items + spec2.items))\n                }\n            }\n            on(\"qualify item name\") {\n                it(\"should add proper prefix\") {\n                    assertThat(spec.qualify(spec1.item1), equalTo(\"a.item1\"))\n                    assertThat(spec.qualify(spec2.item2), equalTo(\"b.item2\"))\n                }\n            }\n        }\n        group(\"withFallback operation\") {\n            val spec1 = object : ConfigSpec(\"a\") {\n                val item1 by required<Int>()\n            }\n            val spec2 = object : ConfigSpec(\"b\") {\n                val item2 by required<Int>()\n            }\n            @Suppress(\"NAME_SHADOWING\")\n            val spec by memoized { spec2.withFallback(spec1) }\n            on(\"add a valid item\") {\n                it(\"should contains the item in the facade spec\") {\n                    val item by Spec.dummy.required<Int>()\n                    spec.addItem(item)\n                    assertThat(item, isIn(spec.items))\n                    assertThat(item, isIn(spec2.items))\n                }\n            }\n            on(\"add a repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { spec.addItem(spec1.item1) },\n                        throws(has(RepeatedItemException::name, equalTo(\"item1\")))\n                    )\n                }\n            }\n            on(\"get the list of items\") {\n                it(\"should contains all items in both the facade spec and the fallback spec\") {\n                    assertThat(spec.items, equalTo(spec1.items + spec2.items))\n                }\n            }\n            on(\"qualify item name\") {\n                it(\"should add proper prefix\") {\n                    assertThat(spec.qualify(spec1.item1), equalTo(\"a.item1\"))\n                    assertThat(spec.qualify(spec2.item2), equalTo(\"b.item2\"))\n                }\n            }\n        }\n        group(\"prefix inference\") {\n            val configSpecInstance = ConfigSpec()\n            on(\"instance of `ConfigSpec` class\") {\n                it(\"should inference prefix as \\\"\\\"\") {\n                    assertThat(configSpecInstance.prefix, equalTo(\"\"))\n                }\n            }\n            on(\"anonymous class\") {\n                it(\"should inference prefix as \\\"\\\"\") {\n                    assertThat(AnonymousConfigSpec.spec.prefix, equalTo(\"\"))\n                }\n            }\n            val objectExpression = object : ConfigSpec() {}\n            on(\"object expression\") {\n                it(\"should inference prefix as \\\"\\\"\") {\n                    assertThat(objectExpression.prefix, equalTo(\"\"))\n                }\n            }\n            on(\"class with uppercase capital\") {\n                it(\"should inference prefix as the class name with lowercase capital\") {\n                    assertThat(Uppercase.prefix, equalTo(\"uppercase\"))\n                }\n            }\n            on(\"class with uppercase name\") {\n                it(\"should inference prefix as the lowercase class name\") {\n                    assertThat(OK.prefix, equalTo(\"ok\"))\n                }\n            }\n            on(\"class with uppercase first word\") {\n                it(\"should inference prefix as the class name with lowercase first word\") {\n                    assertThat(TCPService.prefix, equalTo(\"tcpService\"))\n                }\n            }\n            on(\"class with lowercase capital\") {\n                it(\"should inference prefix as the class name\") {\n                    assertThat(lowercase.prefix, equalTo(\"lowercase\"))\n                }\n            }\n            on(\"class with \\\"Spec\\\" suffix\") {\n                it(\"should inference prefix as the class name without the suffix\") {\n                    assertThat(SuffixSpec.prefix, equalTo(\"suffix\"))\n                }\n            }\n            on(\"companion object of a class\") {\n                it(\"should inference prefix as the class name\") {\n                    assertThat(OriginalSpec.prefix, equalTo(\"original\"))\n                }\n            }\n        }\n    }\n})\n\nobject Nested : ConfigSpec(\"a.bb\") {\n    val item by required<Int>(\"int\", \"description\")\n\n    object Inner : ConfigSpec() {\n        val item by required<Int>()\n    }\n\n    object Inner2 : ConfigSpec(\"inner2.level2\") {\n        val item by required<Int>()\n    }\n\n    object Inner3a : ConfigSpec(\"inner3.a\") {\n        val item by required<Int>()\n    }\n\n    object Inner3b : ConfigSpec(\"inner3.b\") {\n        val item by required<Int>()\n    }\n}\n\nobject Uppercase : ConfigSpec()\n\nobject OK : ConfigSpec()\n\nobject TCPService : ConfigSpec()\n\n@Suppress(\"ClassName\")\nobject lowercase : ConfigSpec()\n\nobject SuffixSpec : ConfigSpec()\n\nclass OriginalSpec {\n    companion object : ConfigSpec()\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/ConfigTestSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.absent\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.asKVSource\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.dsl.SubjectProviderDsl\nimport kotlin.test.assertFalse\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject ConfigTestSpec : SubjectSpek<Config>({\n    subject { Config { addSpec(NetworkBuffer) } }\n\n    configTestSpec()\n})\n\nfun SubjectProviderDsl<Config>.configTestSpec(prefix: String = \"network.buffer\") {\n    val spec = NetworkBuffer\n    val size = NetworkBuffer.size\n    val maxSize = NetworkBuffer.maxSize\n    val name = NetworkBuffer.name\n    val type = NetworkBuffer.type\n    val offset = NetworkBuffer.offset\n\n    fun qualify(name: String): String = if (prefix.isEmpty()) name else \"$prefix.$name\"\n\n    given(\"a config\") {\n        val invalidItem by ConfigSpec(\"invalid\").required<Int>()\n        val invalidItemName = \"invalid.invalidItem\"\n        group(\"feature operation\") {\n            on(\"enable feature\") {\n                subject.enable(Feature.FAIL_ON_UNKNOWN_PATH)\n                it(\"should let the feature be enabled\") {\n                    assertTrue { subject.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"disable feature\") {\n                subject.disable(Feature.FAIL_ON_UNKNOWN_PATH)\n                it(\"should let the feature be disabled\") {\n                    assertFalse { subject.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"by default\") {\n                it(\"should use the feature's default setting\") {\n                    assertThat(\n                        subject.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH),\n                        equalTo(Feature.FAIL_ON_UNKNOWN_PATH.enabledByDefault)\n                    )\n                }\n            }\n        }\n        group(\"subscribe operation\") {\n            on(\"load source when subscriber is defined\") {\n                var loadFunction: (source: Source) -> Unit = {}\n                var counter = 0\n                val config = subject.withLoadTrigger(\"\") { _, load ->\n                    loadFunction = load\n                }.withLayer()\n                val source = mapOf(qualify(type.name) to NetworkBuffer.Type.ON_HEAP).asKVSource()\n                val handler1 = config.beforeLoad {\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(it, equalTo(source))\n                        assertThat(config[type], equalTo(NetworkBuffer.Type.OFF_HEAP))\n                    }\n                }\n                val handler2 = config.beforeLoad {\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(it, equalTo(source))\n                        assertThat(config[type], equalTo(NetworkBuffer.Type.OFF_HEAP))\n                    }\n                }\n                val handler3 = config.afterLoad {\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(it, equalTo(source))\n                        assertThat(config[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                    }\n                }\n                val handler4 = config.afterLoad {\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(it, equalTo(source))\n                        assertThat(config[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                    }\n                }\n                loadFunction(source)\n                handler1.close()\n                handler2.close()\n                handler3.close()\n                handler4.close()\n                it(\"should notify subscriber\") {\n                    assertThat(counter, equalTo(4))\n                }\n            }\n        }\n        group(\"addSpec operation\") {\n            on(\"add orthogonal spec\") {\n                val newSpec = object : ConfigSpec(spec.prefix) {\n                    val minSize by optional(1)\n                }\n                val config = subject.withSource(mapOf(newSpec.qualify(newSpec.minSize) to 2).asKVSource())\n                config.addSpec(newSpec)\n                it(\"should contain items in new spec\") {\n                    assertTrue { newSpec.minSize in config }\n                    assertTrue { newSpec.qualify(newSpec.minSize) in config }\n                    assertThat(config.nameOf(newSpec.minSize), equalTo(newSpec.qualify(newSpec.minSize)))\n                }\n                it(\"should contain new spec\") {\n                    assertThat(newSpec in config.specs, equalTo(true))\n                    assertThat(spec in config.specs, equalTo(true))\n                }\n                it(\"should load values from the existed sources for items in new spec\") {\n                    assertThat(config[newSpec.minSize], equalTo(2))\n                }\n            }\n            on(\"add spec with inner specs\") {\n                subject.addSpec(Service)\n                it(\"should contain items in new spec\") {\n                    assertTrue { Service.name in subject }\n                    assertTrue { Service.UI.host in subject }\n                    assertTrue { Service.UI.port in subject }\n                    assertTrue { Service.Backend.host in subject }\n                    assertTrue { Service.Backend.port in subject }\n                    assertTrue { Service.Backend.Login.user in subject }\n                    assertTrue { Service.Backend.Login.password in subject }\n                    assertTrue { \"service.name\" in subject }\n                    assertTrue { \"service.ui.host\" in subject }\n                    assertTrue { \"service.ui.port\" in subject }\n                    assertTrue { \"service.backend.host\" in subject }\n                    assertTrue { \"service.backend.port\" in subject }\n                    assertTrue { \"service.backend.login.user\" in subject }\n                    assertTrue { \"service.backend.login.password\" in subject }\n                }\n                it(\"should contain new spec\") {\n                    assertTrue { Service in subject.specs }\n                }\n                it(\"should not contain inner specs in new spec\") {\n                    assertFalse { Service.UI in subject.specs }\n                    assertFalse { Service.Backend in subject.specs }\n                    assertFalse { Service.Backend.Login in subject.specs }\n                }\n            }\n            on(\"add nested spec\") {\n                subject.addSpec(Service.Backend)\n                it(\"should contain items in the nested spec\") {\n                    assertTrue { Service.Backend.host in subject }\n                    assertTrue { Service.Backend.port in subject }\n                    assertTrue { Service.Backend.Login.user in subject }\n                    assertTrue { Service.Backend.Login.password in subject }\n                }\n                it(\"should not contain items in the outer spec\") {\n                    assertFalse { Service.name in subject }\n                    assertFalse { Service.UI.host in subject }\n                    assertFalse { Service.UI.port in subject }\n                }\n                it(\"should contain the nested spec\") {\n                    assertTrue { Service.Backend in subject.specs }\n                }\n                it(\"should not contain the outer spec or inner specs in the nested spec\") {\n                    assertFalse { Service in subject.specs }\n                    assertFalse { Service.UI in subject.specs }\n                    assertFalse { Service.Backend.Login in subject.specs }\n                }\n            }\n            on(\"add repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { subject.addSpec(spec) },\n                        throws(\n                            has(\n                                RepeatedItemException::name,\n                                equalTo(spec.qualify(size))\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"add repeated name\") {\n                val newSpec = ConfigSpec(prefix).apply {\n                    @Suppress(\"UNUSED_VARIABLE\", \"NAME_SHADOWING\")\n                    val size by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addSpec(newSpec) }, throws<NameConflictException>())\n                }\n            }\n            on(\"add conflict name, which is prefix of existed name\") {\n                val newSpec = ConfigSpec().apply {\n                    @Suppress(\"UNUSED_VARIABLE\")\n                    val buffer by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat(\n                        {\n                            subject.addSpec(\n                                newSpec.withPrefix(prefix.toPath().let { it.subList(0, it.size - 1) }.name)\n                            )\n                        },\n                        throws<NameConflictException>()\n                    )\n                }\n            }\n            on(\"add conflict name, and an existed name is prefix of it\") {\n                val newSpec = ConfigSpec(qualify(type.name)).apply {\n                    @Suppress(\"UNUSED_VARIABLE\")\n                    val subType by required<Int>()\n                }\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addSpec(newSpec) }, throws<NameConflictException>())\n                }\n            }\n        }\n        group(\"addItem operation\") {\n            on(\"add orthogonal item\") {\n                val minSize by Spec.dummy.optional(1)\n                val config = subject.withSource(mapOf(qualify(minSize.name) to 2).asKVSource())\n                config.addItem(minSize, prefix)\n                it(\"should contain item\") {\n                    assertTrue { minSize in config }\n                    assertTrue { qualify(minSize.name) in config }\n                    assertThat(config.nameOf(minSize), equalTo(qualify(minSize.name)))\n                }\n                it(\"should load values from the existed sources for item\") {\n                    assertThat(config[minSize], equalTo(2))\n                }\n            }\n            on(\"add repeated item\") {\n                it(\"should throw RepeatedItemException\") {\n                    assertThat(\n                        { subject.addItem(size, prefix) },\n                        throws(\n                            has(\n                                RepeatedItemException::name,\n                                equalTo(qualify(size.name))\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"add repeated name\") {\n                @Suppress(\"NAME_SHADOWING\")\n                val size by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addItem(size, prefix) }, throws<NameConflictException>())\n                }\n            }\n            on(\"add conflict name, which is prefix of existed name\") {\n                val buffer by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat(\n                        {\n                            subject.addItem(\n                                buffer,\n                                prefix.toPath().let { it.subList(0, it.size - 1) }.name\n                            )\n                        },\n                        throws<NameConflictException>()\n                    )\n                }\n            }\n            on(\"add conflict name, and an existed name is prefix of it\") {\n                val subType by Spec.dummy.required<Int>()\n                it(\"should throw NameConflictException\") {\n                    assertThat({ subject.addItem(subType, qualify(type.name)) }, throws<NameConflictException>())\n                }\n            }\n        }\n        on(\"iterate items in config\") {\n            it(\"should cover all items in config\") {\n                assertThat(subject.items.toSet(), equalTo(spec.items.toSet()))\n            }\n        }\n        on(\"iterate name of items in config\") {\n            it(\"should cover all items in config\") {\n                assertThat(subject.nameOfItems.toSet(), equalTo(spec.items.map { qualify(it.name) }.toSet()))\n            }\n        }\n        on(\"export values to map\") {\n            it(\"should not contain unset items in map\") {\n                assertThat(\n                    subject.toMap(),\n                    equalTo(\n                        mapOf<String, Any>(\n                            qualify(name.name) to \"buffer\",\n                            qualify(type.name) to NetworkBuffer.Type.OFF_HEAP.name,\n                            qualify(offset.name) to \"null\"\n                        )\n                    )\n                )\n            }\n            it(\"should contain corresponding items in map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toMap()\n                assertThat(\n                    map,\n                    equalTo(\n                        mapOf(\n                            qualify(size.name) to 4,\n                            qualify(maxSize.name) to 8,\n                            qualify(name.name) to \"buffer\",\n                            qualify(type.name) to NetworkBuffer.Type.ON_HEAP.name,\n                            qualify(offset.name) to 0\n                        )\n                    )\n                )\n            }\n            it(\"should recover all items when reloaded from map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toMap()\n                val newConfig = Config { addSpec(spec[spec.prefix].withPrefix(prefix)) }.from.map.kv(map)\n                assertThat(newConfig[size], equalTo(4))\n                assertThat(newConfig[maxSize], equalTo(8))\n                assertThat(newConfig[name], equalTo(\"buffer\"))\n                assertThat(newConfig[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                assertThat(newConfig[offset], equalTo(0))\n                assertThat(newConfig.toMap(), equalTo(subject.toMap()))\n            }\n        }\n        on(\"export values to hierarchical map\") {\n            fun prefixToMap(prefix: String, value: Map<String, Any>): Map<String, Any> {\n                return when {\n                    prefix.isEmpty() -> value\n                    prefix.contains('.') ->\n                        mapOf<String, Any>(\n                            prefix.substring(0, prefix.indexOf('.')) to\n                                prefixToMap(prefix.substring(prefix.indexOf('.') + 1), value)\n                        )\n                    else -> mapOf(prefix to value)\n                }\n            }\n            it(\"should not contain unset items in map\") {\n                assertThat(\n                    subject.toHierarchicalMap(),\n                    equalTo(\n                        prefixToMap(\n                            prefix,\n                            mapOf(\n                                \"name\" to \"buffer\",\n                                \"type\" to NetworkBuffer.Type.OFF_HEAP.name,\n                                \"offset\" to \"null\"\n                            )\n                        )\n                    )\n                )\n            }\n            it(\"should contain corresponding items in map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toHierarchicalMap()\n                assertThat(\n                    map,\n                    equalTo(\n                        prefixToMap(\n                            prefix,\n                            mapOf(\n                                \"size\" to 4,\n                                \"maxSize\" to 8,\n                                \"name\" to \"buffer\",\n                                \"type\" to NetworkBuffer.Type.ON_HEAP.name,\n                                \"offset\" to 0\n                            )\n                        )\n                    )\n                )\n            }\n            it(\"should recover all items when reloaded from map\") {\n                subject[size] = 4\n                subject[type] = NetworkBuffer.Type.ON_HEAP\n                subject[offset] = 0\n                val map = subject.toHierarchicalMap()\n                val newConfig = Config { addSpec(spec[spec.prefix].withPrefix(prefix)) }.from.map.hierarchical(map)\n                assertThat(newConfig[size], equalTo(4))\n                assertThat(newConfig[maxSize], equalTo(8))\n                assertThat(newConfig[name], equalTo(\"buffer\"))\n                assertThat(newConfig[type], equalTo(NetworkBuffer.Type.ON_HEAP))\n                assertThat(newConfig[offset], equalTo(0))\n                assertThat(newConfig.toMap(), equalTo(subject.toMap()))\n            }\n        }\n        on(\"object methods\") {\n            val map = mapOf(\n                qualify(name.name) to \"buffer\",\n                qualify(type.name) to NetworkBuffer.Type.OFF_HEAP.name,\n                qualify(offset.name) to \"null\"\n            )\n            it(\"should not equal to object of other class\") {\n                assertFalse(subject.equals(1))\n            }\n            it(\"should equal to itself\") {\n                assertThat(subject, equalTo(subject))\n            }\n            it(\"should convert to string in map-like format\") {\n                assertThat(subject.toString(), equalTo(\"Config(items=$map)\"))\n            }\n        }\n        on(\"lock config\") {\n            it(\"should be locked\") {\n                subject.lock { }\n            }\n        }\n        group(\"get operation\") {\n            on(\"get with valid item\") {\n                it(\"should return corresponding value\") {\n                    assertThat(subject[name], equalTo(\"buffer\"))\n                    assertTrue { name in subject }\n                    assertNull(subject[offset])\n                    assertTrue { offset in subject }\n                    assertNull(subject.getOrNull(maxSize))\n                    assertTrue { maxSize in subject }\n                }\n            }\n            on(\"get with invalid item\") {\n                it(\"should throw NoSuchItemException when using `get`\") {\n                    assertThat(\n                        { subject[invalidItem] },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n                it(\"should return null when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(invalidItem), absent())\n                    assertTrue { invalidItem !in subject }\n                }\n            }\n            on(\"get with valid name\") {\n                it(\"should return corresponding value\") {\n                    assertThat(subject(qualify(\"name\")), equalTo(\"buffer\"))\n                    assertThat(subject.getOrNull(qualify(\"name\")), equalTo(\"buffer\"))\n                    assertTrue { qualify(\"name\") in subject }\n                }\n            }\n            on(\"get with valid name which contains trailing whitespaces\") {\n                it(\"should return corresponding value\") {\n                    assertThat(subject(qualify(\"name \")), equalTo(\"buffer\"))\n                    assertThat(subject.getOrNull(qualify(\"name  \")), equalTo(\"buffer\"))\n                    assertTrue { qualify(\"name   \") in subject }\n                }\n            }\n            on(\"get with invalid name\") {\n                it(\"should throw NoSuchItemException when using `get`\") {\n                    assertThat(\n                        { subject<String>(qualify(invalidItem.name)) },\n                        throws(\n                            has(\n                                NoSuchItemException::name,\n                                equalTo(qualify(invalidItem.name))\n                            )\n                        )\n                    )\n                }\n                it(\"should return null when using `getOrNull`\") {\n                    assertThat(subject.getOrNull<String>(qualify(invalidItem.name)), absent())\n                    assertTrue { qualify(invalidItem.name) !in subject }\n                }\n            }\n            on(\"get unset item\") {\n                it(\"should throw UnsetValueException\") {\n                    assertThat(\n                        { subject[size] },\n                        throws(\n                            has(\n                                UnsetValueException::name,\n                                equalTo(size.asName)\n                            )\n                        )\n                    )\n                    assertThat(\n                        { subject[maxSize] },\n                        throws(\n                            has(\n                                UnsetValueException::name,\n                                equalTo(size.asName)\n                            )\n                        )\n                    )\n                    assertTrue { size in subject }\n                    assertTrue { maxSize in subject }\n                }\n            }\n            on(\"get with lazy item that returns null when the type is nullable\") {\n                it(\"should return null\") {\n                    val lazyItem by Spec.dummy.lazy<Int?> { null }\n                    subject.addItem(lazyItem, prefix)\n                    assertNull(subject[lazyItem])\n                }\n            }\n            on(\"get with lazy item that returns null when the type is not nullable\") {\n                it(\"should throw InvalidLazySetException\") {\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val thunk = { _: ItemContainer -> null } as (ItemContainer) -> Int\n                    val lazyItem by Spec.dummy.lazy(thunk = thunk)\n                    subject.addItem(lazyItem, prefix)\n                    assertThat({ subject[lazyItem] }, throws<InvalidLazySetException>())\n                }\n            }\n        }\n        group(\"set operation\") {\n            on(\"set with valid item when corresponding value is unset\") {\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[size], equalTo(1024))\n                }\n            }\n            on(\"set with valid item when corresponding value exists\") {\n                it(\"should contain the specified value\") {\n                    subject[name] = \"newName\"\n                    assertThat(subject[name], equalTo(\"newName\"))\n                    subject[offset] = 0\n                    assertThat(subject[offset], equalTo(0))\n                    subject[offset] = null\n                    assertNull(subject[offset])\n                }\n            }\n            on(\"raw set with valid item\") {\n                it(\"should contain the specified value\") {\n                    subject.rawSet(size, 2048)\n                    assertThat(subject[size], equalTo(2048))\n                }\n            }\n            on(\"set with valid item when corresponding value is lazy\") {\n                test(\n                    \"before set, the item should be lazy; after set,\" +\n                        \" the item should be no longer lazy, and it contains the specified value\"\n                ) {\n                    subject[size] = 1024\n                    assertThat(subject[maxSize], equalTo(subject[size] * 2))\n                    subject[maxSize] = 0\n                    assertThat(subject[maxSize], equalTo(0))\n                    subject[size] = 2048\n                    assertThat(subject[maxSize], !equalTo(subject[size] * 2))\n                    assertThat(subject[maxSize], equalTo(0))\n                }\n            }\n            on(\"set with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject[invalidItem] = 1024 },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"set with valid name\") {\n                subject[qualify(\"size\")] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[size], equalTo(1024))\n                }\n            }\n            on(\"set with valid name which contains trailing whitespaces\") {\n                subject[qualify(\"size  \")] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[size], equalTo(1024))\n                }\n            }\n            on(\"set with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject[invalidItemName] = 1024 },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n            on(\"set with incorrect type of value\") {\n                it(\"should throw ClassCastException\") {\n                    assertThat({ subject[qualify(size.name)] = \"1024\" }, throws<ClassCastException>())\n                    assertThat({ subject[qualify(size.name)] = null }, throws<ClassCastException>())\n                }\n            }\n            on(\"set when beforeSet subscriber is defined\") {\n                val childConfig = subject.withLayer()\n                subject[size] = 1\n                var counter = 0\n                val handler1 = childConfig.beforeSet { item, value ->\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(item, equalTo(size))\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(1))\n                    }\n                }\n                val handler2 = childConfig.beforeSet { item, value ->\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(item, equalTo(size))\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(1))\n                    }\n                }\n                val handler3 = size.beforeSet { _, value ->\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(1))\n                    }\n                }\n                val handler4 = size.beforeSet { _, value ->\n                    counter += 1\n                    it(\"should contain the old value\") {\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(1))\n                    }\n                }\n                subject[size] = 2\n                handler1.close()\n                handler2.close()\n                handler3.close()\n                handler4.close()\n                it(\"should notify subscriber\") {\n                    assertThat(counter, equalTo(4))\n                }\n            }\n            on(\"set when afterSet subscriber is defined\") {\n                val childConfig = subject.withLayer()\n                subject[size] = 1\n                var counter = 0\n                val handler1 = childConfig.afterSet { item, value ->\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(item, equalTo(size))\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(2))\n                    }\n                }\n                val handler2 = childConfig.afterSet { item, value ->\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(item, equalTo(size))\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(2))\n                    }\n                }\n                val handler3 = size.afterSet { _, value ->\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(2))\n                    }\n                }\n                val handler4 = size.afterSet { _, value ->\n                    counter += 1\n                    it(\"should contain the new value\") {\n                        assertThat(value, equalTo(2))\n                        assertThat(childConfig[size], equalTo(2))\n                    }\n                }\n                subject[size] = 2\n                handler1.close()\n                handler2.close()\n                handler3.close()\n                handler4.close()\n                it(\"should notify subscriber\") {\n                    assertThat(counter, equalTo(4))\n                }\n            }\n            on(\"set when onSet subscriber is defined\") {\n                var counter = 0\n                size.onSet { counter += 1 }.use {\n                    subject[size] = 1\n                    subject[size] = 16\n                    subject[size] = 256\n                    subject[size] = 1024\n                    it(\"should notify subscriber\") {\n                        assertThat(counter, equalTo(4))\n                    }\n                }\n            }\n            on(\"set when multiple onSet subscribers are defined\") {\n                var counter = 0\n                size.onSet { counter += 1 }.use {\n                    size.onSet { counter += 2 }.use {\n                        subject[size] = 1\n                        subject[size] = 16\n                        subject[size] = 256\n                        subject[size] = 1024\n                        it(\"should notify all subscribers\") {\n                            assertThat(counter, equalTo(12))\n                        }\n                    }\n                }\n            }\n            on(\"lazy set with valid item\") {\n                subject.lazySet(maxSize) { it[size] * 4 }\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[maxSize], equalTo(subject[size] * 4))\n                }\n            }\n            on(\"lazy set with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.lazySet(invalidItem) { 1024 } },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"lazy set with valid name\") {\n                subject.lazySet(qualify(maxSize.name)) { it[size] * 4 }\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[maxSize], equalTo(subject[size] * 4))\n                }\n            }\n            on(\"lazy set with valid name which contains trailing whitespaces\") {\n                subject.lazySet(qualify(maxSize.name + \"  \")) { it[size] * 4 }\n                subject[size] = 1024\n                it(\"should contain the specified value\") {\n                    assertThat(subject[maxSize], equalTo(subject[size] * 4))\n                }\n            }\n            on(\"lazy set with valid name and invalid value with incompatible type\") {\n                subject.lazySet(qualify(maxSize.name)) { \"string\" }\n                it(\"should throw InvalidLazySetException when getting\") {\n                    assertThat({ subject[qualify(maxSize.name)] }, throws<InvalidLazySetException>())\n                }\n            }\n            on(\"lazy set with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.lazySet(invalidItemName) { 1024 } },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n            on(\"unset with valid item\") {\n                subject.unset(type)\n                it(\"should contain `null` when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(type), absent())\n                }\n            }\n            on(\"unset with invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.unset(invalidItem) },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"unset with valid name\") {\n                subject.unset(qualify(type.name))\n                it(\"should contain `null` when using `getOrNull`\") {\n                    assertThat(subject.getOrNull(type), absent())\n                }\n            }\n            on(\"unset with invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        { subject.unset(invalidItemName) },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n        }\n        on(\"clear operation\") {\n            subject[size] = 1\n            subject[maxSize] = 4\n            it(\"should contain no value\") {\n                assertThat(subject[size], equalTo(1))\n                assertThat(subject[maxSize], equalTo(4))\n                subject.clear()\n                assertNull(subject.getOrNull(size))\n                assertNull(subject.getOrNull(maxSize))\n            }\n        }\n        on(\"clear all operation\") {\n            it(\"should contain no value\") {\n                assertTrue { name in subject && type in subject }\n                subject.clearAll()\n                assertTrue { name !in subject && type !in subject }\n            }\n        }\n        on(\"check whether all required items have values or not\") {\n            it(\"should return false when some required items don't have values\") {\n                assertFalse { subject.containsRequired() }\n            }\n            it(\"should return true when all required items have values\") {\n                subject[size] = 1\n                assertTrue { subject.containsRequired() }\n            }\n        }\n        on(\"validate whether all required items have values or not\") {\n            it(\"should throw UnsetValueException when some required items don't have values\") {\n                assertThat(\n                    {\n                        subject.validateRequired()\n                    },\n                    throws<UnsetValueException>()\n                )\n            }\n            it(\"should return itself when all required items have values\") {\n                subject[size] = 1\n                assertThat(subject, sameInstance(subject.validateRequired()))\n            }\n        }\n        group(\"item property\") {\n            on(\"declare a property by item\") {\n                var nameProperty by subject.property(name)\n                it(\"should behave same as `get`\") {\n                    assertThat(nameProperty, equalTo(subject[name]))\n                }\n                it(\"should support set operation as `set`\") {\n                    nameProperty = \"newName\"\n                    assertThat(nameProperty, equalTo(\"newName\"))\n                }\n            }\n            on(\"declare a property by invalid item\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        {\n                            @Suppress(\"UNUSED_VARIABLE\")\n                            var nameProperty by subject.property(invalidItem)\n                        },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItem.asName)))\n                    )\n                }\n            }\n            on(\"declare a property by name\") {\n                var nameProperty by subject.property<String>(qualify(name.name))\n                it(\"should behave same as `get`\") {\n                    assertThat(nameProperty, equalTo(subject[name]))\n                }\n                it(\"should support set operation as `set`\") {\n                    nameProperty = \"newName\"\n                    assertThat(nameProperty, equalTo(\"newName\"))\n                }\n            }\n            on(\"declare a property by invalid name\") {\n                it(\"should throw NoSuchItemException\") {\n                    assertThat(\n                        {\n                            @Suppress(\"UNUSED_VARIABLE\")\n                            var nameProperty by subject.property<Int>(invalidItemName)\n                        },\n                        throws(has(NoSuchItemException::name, equalTo(invalidItemName)))\n                    )\n                }\n            }\n        }\n    }\n}\n\nobject Service : ConfigSpec() {\n    val name by optional(\"test\")\n\n    object Backend : ConfigSpec() {\n        val host by optional(\"127.0.0.1\")\n        val port by optional(7777)\n\n        object Login : ConfigSpec() {\n            val user by optional(\"admin\")\n            val password by optional(\"123456\")\n        }\n    }\n\n    object UI : ConfigSpec() {\n        val host by optional(\"127.0.0.1\")\n        val port by optional(8888)\n    }\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/FeatureSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.UnknownPathsException\nimport com.uchuhimo.konf.source.asSource\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.junit.jupiter.api.assertThrows\nimport java.io.FileNotFoundException\n\nobject FailOnUnknownPathSpec : Spek({\n    //language=Json\n    val source =\n        \"\"\"\n        {\n            \"level1\": {\n              \"level2\": {\n                \"valid\": \"value1\",\n                \"invalid\": \"value2\"\n              }\n            }\n        }\n        \"\"\".trimIndent()\n    given(\"a config\") {\n        on(\"the feature is disabled\") {\n            val config = Config {\n                addSpec(Valid)\n            }\n            it(\"should ignore unknown paths\") {\n                val conf = config.from.disabled(Feature.FAIL_ON_UNKNOWN_PATH).json.string(source)\n                assertThat(conf[Valid.valid], equalTo(\"value1\"))\n            }\n        }\n        on(\"the feature is enabled on config\") {\n            val config = Config {\n                addSpec(Valid)\n            }.enable(Feature.FAIL_ON_UNKNOWN_PATH)\n            it(\"should throws UnknownPathsException and reports the unknown paths\") {\n                assertThat(\n                    { config.from.json.string(source) },\n                    throws(\n                        has(\n                            UnknownPathsException::paths,\n                            equalTo(listOf(\"level1.level2.invalid\"))\n                        )\n                    )\n                )\n            }\n        }\n        on(\"the feature is enabled on source\") {\n            val config = Config {\n                addSpec(Valid)\n            }\n            it(\"should throws UnknownPathsException and reports the unknown paths\") {\n                assertThat(\n                    {\n                        config.from.enabled(Feature.FAIL_ON_UNKNOWN_PATH).json.string(source)\n                    },\n                    throws(\n                        has(\n                            UnknownPathsException::paths,\n                            equalTo(listOf(\"level1.level2.invalid\"))\n                        )\n                    )\n                )\n            }\n        }\n    }\n})\n\nobject LoadKeysCaseInsensitivelySpec : Spek({\n    given(\"a config\") {\n        on(\"by default\") {\n            val source = mapOf(\"somekey\" to \"value\").asSource()\n            val config = Config().withSource(source)\n            it(\"should load keys case-sensitively\") {\n                val someKey by config.required<String>()\n                assertThrows<UnsetValueException> { someKey.isNotEmpty() }\n                val somekey by config.required<String>()\n                assertThat(somekey, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is disabled\") {\n            val source = mapOf(\"somekey\" to \"value\").asSource().disabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)\n            val config = Config().withSource(source)\n            it(\"should load keys case-sensitively\") {\n                val someKey by config.required<String>()\n                assertThrows<UnsetValueException> { someKey.isNotEmpty() }\n                val somekey by config.required<String>()\n                assertThat(somekey, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is enabled on config\") {\n            val source = mapOf(\"somekey\" to \"value\").asSource()\n            val config = Config().enable(Feature.LOAD_KEYS_CASE_INSENSITIVELY).withSource(source)\n            it(\"should load keys case-insensitively\") {\n                val someKey by config.required<String>()\n                assertThat(someKey, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is enabled on source\") {\n            val source = mapOf(\"somekey\" to \"value\").asSource().enabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)\n            val config = Config().withSource(source)\n            it(\"should load keys case-insensitively\") {\n                val someKey by config.required<String>()\n                assertThat(someKey, equalTo(\"value\"))\n            }\n        }\n    }\n})\n\nobject LoadKeysAsLittleCamelCaseSpec : Spek({\n    given(\"a config\") {\n        on(\"by default\") {\n            val source = mapOf(\n                \"some_key\" to \"value\",\n                \"some_key2_\" to \"value\",\n                \"_some_key3\" to \"value\",\n                \"SomeKey4\" to \"value\",\n                \"some_0key5\" to \"value\",\n                \"some__key6\" to \"value\",\n                \"some___key7\" to \"value\",\n                \"some_some_key8\" to \"value\",\n                \"some key9\" to \"value\",\n                \"SOMEKey10\" to \"value\"\n            ).asSource()\n            val config = Config().withSource(source)\n            it(\"should load keys as little camel case\") {\n                val someKey by config.required<String>()\n                assertThat(someKey, equalTo(\"value\"))\n                val someKey2 by config.required<String>()\n                assertThat(someKey2, equalTo(\"value\"))\n                val someKey3 by config.required<String>()\n                assertThat(someKey3, equalTo(\"value\"))\n                val someKey4 by config.required<String>()\n                assertThat(someKey4, equalTo(\"value\"))\n                val some0key5 by config.required<String>()\n                assertThat(some0key5, equalTo(\"value\"))\n                val someKey6 by config.required<String>()\n                assertThat(someKey6, equalTo(\"value\"))\n                val someKey7 by config.required<String>()\n                assertThat(someKey7, equalTo(\"value\"))\n                val someSomeKey8 by config.required<String>()\n                assertThat(someSomeKey8, equalTo(\"value\"))\n                val someKey9 by config.required<String>()\n                assertThat(someKey9, equalTo(\"value\"))\n                val someKey10 by config.required<String>()\n                assertThat(someKey10, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is enabled\") {\n            val source = mapOf(\"some_key\" to \"value\").asSource().enabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)\n            val config = Config().withSource(source)\n            it(\"should load keys as little camel case\") {\n                val someKey by config.required<String>()\n                assertThat(someKey, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is disabled on config\") {\n            val source = mapOf(\"some_key\" to \"value\").asSource()\n            val config = Config().disable(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE).withSource(source)\n            it(\"should load keys as usual\") {\n                val someKey by config.required<String>()\n                assertThrows<UnsetValueException> { someKey.isNotEmpty() }\n                val some_key by config.required<String>()\n                assertThat(some_key, equalTo(\"value\"))\n            }\n        }\n        on(\"the feature is disabled on source\") {\n            val source = mapOf(\"some_key\" to \"value\").asSource().disabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)\n            val config = Config().withSource(source)\n            it(\"should load keys as usual\") {\n                val someKey by config.required<String>()\n                assertThrows<UnsetValueException> { someKey.isNotEmpty() }\n                val some_key by config.required<String>()\n                assertThat(some_key, equalTo(\"value\"))\n            }\n        }\n    }\n})\n\nobject OptionalSourceByDefautSpec : Spek({\n    given(\"a config\") {\n        on(\"the feature is disabled\") {\n            val config = Config().disable(Feature.OPTIONAL_SOURCE_BY_DEFAULT)\n            it(\"should throw exception when file is not existed\") {\n                assertThrows<FileNotFoundException> { config.from.file(\"not_existed.json\") }\n            }\n        }\n        on(\"the feature is enabled on config\") {\n            val config = Config().enable(Feature.OPTIONAL_SOURCE_BY_DEFAULT)\n            it(\"should load empty source\") {\n                config.from.mapped {\n                    assertThat(it.tree.children, equalTo(mutableMapOf()))\n                    it\n                }.file(\"not_existed.json\")\n                config.from.mapped {\n                    assertThat(it.tree.children, equalTo(mutableMapOf()))\n                    it\n                }.json.file(\"not_existed.json\")\n            }\n        }\n    }\n})\n\nprivate object Valid : ConfigSpec(\"level1.level2\") {\n    val valid by required<String>()\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/MergedConfigSpek.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject FacadeConfigSpec : SubjectSpek<Config>({\n    subject { Config() + Config { addSpec(NetworkBuffer) } }\n\n    configTestSpec()\n})\n\nobject MultiLayerFacadeConfigSpec : SubjectSpek<Config>({\n    subject { (Config() + Config { addSpec(NetworkBuffer) }).withLayer(\"multi-layer\") }\n\n    configTestSpec()\n})\n\nobject FacadeMultiLayerConfigSpec : SubjectSpek<Config>({\n    subject { Config() + Config { addSpec(NetworkBuffer) }.withLayer(\"multi-layer\") }\n\n    configTestSpec()\n})\n\nobject FacadeConfigUsingWithFallbackSpec : SubjectSpek<Config>({\n    subject { Config { addSpec(NetworkBuffer) }.withFallback(Config()) }\n\n    configTestSpec()\n})\n\nobject FallbackConfigSpec : SubjectSpek<Config>({\n    subject { Config { addSpec(NetworkBuffer) } + Config() }\n\n    configTestSpec()\n})\n\nobject MultiLayerFallbackConfigSpec : SubjectSpek<Config>({\n    subject { (Config { addSpec(NetworkBuffer) } + Config()).withLayer(\"multi-layer\") }\n\n    configTestSpec()\n})\n\nobject FallbackMultiLayerConfigSpec : SubjectSpek<Config>({\n    subject { Config { addSpec(NetworkBuffer) }.withLayer(\"multi-layer\") + Config() }\n\n    configTestSpec()\n})\n\nobject FallbackConfigUsingWithFallbackSpec : SubjectSpek<Config>({\n    subject { Config().withFallback(Config { addSpec(NetworkBuffer) }) }\n\n    configTestSpec()\n})\n\nobject BothConfigSpec : SubjectSpek<Config>({\n    subject { Config { addSpec(NetworkBuffer) } + Config { addSpec(NetworkBuffer) } }\n\n    configTestSpec()\n\n    given(\"a merged config\") {\n        on(\"set item in the fallback config\") {\n            (subject as MergedConfig).fallback[NetworkBuffer.type] = NetworkBuffer.Type.ON_HEAP\n            it(\"should have higher priority than the default value\") {\n                assertThat((subject as MergedConfig)[NetworkBuffer.type], equalTo(NetworkBuffer.Type.ON_HEAP))\n            }\n        }\n    }\n})\n\nclass UpdateFallbackConfig(val config: MergedConfig) : MergedConfig(config.facade, config.fallback) {\n\n    override fun rawSet(item: Item<*>, value: Any?) {\n        if (item is LazyItem) {\n            facade.rawSet(item, value)\n        } else {\n            fallback.rawSet(item, value)\n        }\n    }\n\n    override fun unset(item: Item<*>) {\n        fallback.unset(item)\n    }\n\n    override fun addItem(item: Item<*>, prefix: String) {\n        fallback.addItem(item, prefix)\n    }\n\n    override fun addSpec(spec: Spec) {\n        fallback.addSpec(spec)\n    }\n}\n\nobject UpdateFallbackConfigSpec : SubjectSpek<Config>({\n    subject { UpdateFallbackConfig((Config { addSpec(NetworkBuffer) } + Config { addSpec(NetworkBuffer) }) as MergedConfig) }\n\n    configTestSpec()\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/MergedMapSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport kotlin.test.assertFalse\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject MergedMapSpec : SubjectSpek<MergedMap<String, Int>>({\n    subject {\n        val facadeMap = mutableMapOf(\"a\" to 1, \"b\" to 2)\n        val fallbackMap = mutableMapOf(\"b\" to 3, \"c\" to 4)\n        MergedMap(fallback = fallbackMap, facade = facadeMap)\n    }\n\n    given(\"a merged map\") {\n        val mergedMap = mapOf(\"a\" to 1, \"b\" to 2, \"c\" to 4)\n        on(\"get size\") {\n            it(\"should return the merged size\") {\n                assertThat(subject.size, equalTo(3))\n            }\n        }\n        on(\"query whether it contains a key\") {\n            it(\"should query in both maps\") {\n                assertTrue { \"a\" in subject }\n                assertTrue { \"c\" in subject }\n                assertFalse { \"d\" in subject }\n            }\n        }\n        on(\"query whether it contains a value\") {\n            it(\"should query in both maps\") {\n                assertTrue { subject.containsValue(1) }\n                assertTrue { subject.containsValue(4) }\n                assertFalse { subject.containsValue(5) }\n            }\n        }\n        on(\"get a value\") {\n            it(\"should query in both maps\") {\n                assertThat(subject[\"a\"], equalTo(1))\n                assertThat(subject[\"b\"], equalTo(2))\n                assertThat(subject[\"c\"], equalTo(4))\n                assertNull(subject[\"d\"])\n            }\n        }\n        on(\"query whether it is empty\") {\n            it(\"should query in both maps\") {\n                assertFalse { subject.isEmpty() }\n                assertFalse { MergedMap(mutableMapOf(\"a\" to 1), mutableMapOf()).isEmpty() }\n                assertFalse { MergedMap(mutableMapOf(), mutableMapOf(\"a\" to 1)).isEmpty() }\n                assertTrue { MergedMap<String, Int>(mutableMapOf(), mutableMapOf()).isEmpty() }\n            }\n        }\n        on(\"get entries\") {\n            it(\"should return entries in both maps\") {\n                assertThat(subject.entries, equalTo(mergedMap.entries))\n            }\n        }\n        on(\"get keys\") {\n            it(\"should return keys in both maps\") {\n                assertThat(subject.keys, equalTo(mergedMap.keys))\n            }\n        }\n        on(\"get values\") {\n            it(\"should return values in both maps\") {\n                assertThat(subject.values.toList(), equalTo(mergedMap.values.toList()))\n            }\n        }\n        on(\"clear\") {\n            subject.clear()\n            it(\"should clear both maps\") {\n                assertTrue { subject.isEmpty() }\n                assertTrue { subject.facade.isEmpty() }\n                assertTrue { subject.fallback.isEmpty() }\n            }\n        }\n        on(\"put new KV pair\") {\n            subject[\"d\"] = 5\n            it(\"should put it to the facade map\") {\n                assertThat(subject[\"d\"], equalTo(5))\n                assertThat(subject.facade[\"d\"], equalTo(5))\n                assertNull(subject.fallback[\"d\"])\n            }\n        }\n        on(\"put new KV pairs\") {\n            subject.putAll(mapOf(\"d\" to 5, \"e\" to 6))\n            it(\"should put them to the facade map\") {\n                assertThat(subject[\"d\"], equalTo(5))\n                assertThat(subject[\"e\"], equalTo(6))\n                assertThat(subject.facade[\"d\"], equalTo(5))\n                assertThat(subject.facade[\"e\"], equalTo(6))\n                assertNull(subject.fallback[\"d\"])\n                assertNull(subject.fallback[\"e\"])\n            }\n        }\n        on(\"remove key\") {\n            it(\"should remove the key from facade map if it contains the key\") {\n                subject.remove(\"a\")\n                assertFalse { \"a\" in subject }\n                assertFalse { \"a\" in subject.facade }\n            }\n            it(\"should remove the key from fallback map if it contains the key\") {\n                subject.remove(\"c\")\n                assertFalse { \"c\" in subject }\n                assertFalse { \"c\" in subject.fallback }\n            }\n            it(\"should remove the key from both maps if both of them contain the key\") {\n                subject.remove(\"b\")\n                assertFalse { \"b\" in subject }\n                assertFalse { \"b\" in subject.facade }\n                assertFalse { \"b\" in subject.fallback }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/MultiLayerConfigSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.deser.std.StdDeserializer\nimport com.fasterxml.jackson.databind.module.SimpleModule\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.LoadException\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject MultiLayerConfigSpec : SubjectSpek<Config>({\n\n    subject { Config { addSpec(NetworkBuffer) }.withLayer(\"multi-layer\") }\n\n    itBehavesLike(ConfigTestSpec)\n\n    group(\"multi-layer config\") {\n        it(\"should have specified name\") {\n            assertThat(subject.name, equalTo(\"multi-layer\"))\n        }\n        it(\"should contain same items with parent config\") {\n            assertThat(\n                subject[NetworkBuffer.name],\n                equalTo(subject.parent!![NetworkBuffer.name])\n            )\n            assertThat(\n                subject[NetworkBuffer.type],\n                equalTo(subject.parent!![NetworkBuffer.type])\n            )\n            assertThat(\n                subject[NetworkBuffer.offset],\n                equalTo(subject.parent!![NetworkBuffer.offset])\n            )\n        }\n        on(\"set with item\") {\n            subject[NetworkBuffer.name] = \"newName\"\n            it(\n                \"should contain the specified value in the top level,\" +\n                    \" and keep the rest levels unchanged\"\n            ) {\n                assertThat(subject[NetworkBuffer.name], equalTo(\"newName\"))\n                assertThat(subject.parent!![NetworkBuffer.name], equalTo(\"buffer\"))\n            }\n        }\n        on(\"set with name\") {\n            subject[subject.nameOf(NetworkBuffer.name)] = \"newName\"\n            it(\n                \"should contain the specified value in the top level,\" +\n                    \" and keep the rest levels unchanged\"\n            ) {\n                assertThat(subject[NetworkBuffer.name], equalTo(\"newName\"))\n                assertThat(subject.parent!![NetworkBuffer.name], equalTo(\"buffer\"))\n            }\n        }\n        on(\"set parent's value\") {\n            subject.parent!![NetworkBuffer.name] = \"newName\"\n            it(\"should contain the specified value in both top and parent level\") {\n                assertThat(subject[NetworkBuffer.name], equalTo(\"newName\"))\n                assertThat(subject.parent!![NetworkBuffer.name], equalTo(\"newName\"))\n            }\n        }\n        on(\"add spec\") {\n            val spec = object : ConfigSpec(NetworkBuffer.prefix) {\n                val minSize by optional(1)\n            }\n            subject.addSpec(spec)\n            it(\"should contain items in new spec, and keep the rest level unchanged\") {\n                assertThat(spec.minSize in subject, equalTo(true))\n                assertThat(subject.nameOf(spec.minSize) in subject, equalTo(true))\n                assertThat(spec.minSize !in subject.parent!!, equalTo(true))\n                assertThat(subject.nameOf(spec.minSize) !in subject.parent!!, equalTo(true))\n            }\n        }\n        on(\"add spec to parent\") {\n            val spec = object : ConfigSpec(NetworkBuffer.prefix) {\n                @Suppress(\"unused\")\n                val minSize by optional(1)\n            }\n            it(\"should throw LayerFrozenException\") {\n                assertThat({ subject.parent!!.addSpec(spec) }, throws<LayerFrozenException>())\n            }\n        }\n        on(\"add item to parent\") {\n            val minSize by Spec.dummy.optional(1)\n            it(\"should throw LayerFrozenException\") {\n                assertThat({ subject.parent!!.addItem(minSize) }, throws<LayerFrozenException>())\n            }\n        }\n        on(\"iterate items in config after adding spec\") {\n            val spec = object : ConfigSpec(NetworkBuffer.prefix) {\n                @Suppress(\"unused\")\n                val minSize by optional(1)\n            }\n            subject.addSpec(spec)\n            it(\"should cover all items in config\") {\n                assertThat(\n                    subject.iterator().asSequence().toSet(),\n                    equalTo((NetworkBuffer.items + spec.items).toSet())\n                )\n            }\n        }\n        on(\"add custom deserializer to mapper in parent\") {\n            it(\"should throw LoadException before adding deserializer\") {\n                val spec = object : ConfigSpec() {\n                    @Suppress(\"unused\")\n                    val item by required<StringWrapper>()\n                }\n                val parent = Config { addSpec(spec) }\n                val child = parent.withLayer(\"child\")\n\n                assertThat(parent.mapper, sameInstance(child.mapper))\n                assertThat(\n                    { child.from.map.kv(mapOf(\"item\" to \"string\")) },\n                    throws<LoadException>()\n                )\n            }\n            it(\"should be able to use the specified deserializer after adding\") {\n                val spec = object : ConfigSpec() {\n                    val item by required<StringWrapper>()\n                }\n                val parent = Config { addSpec(spec) }\n                val child = parent.withLayer(\"child\")\n\n                assertThat(parent.mapper, sameInstance(child.mapper))\n                parent.mapper.registerModule(\n                    SimpleModule().apply {\n                        addDeserializer(StringWrapper::class.java, StringWrapperDeserializer())\n                    }\n                )\n                val afterLoad = child.from.map.kv(mapOf(\"item\" to \"string\"))\n                assertThat(child.mapper, sameInstance(afterLoad.mapper))\n                assertThat(afterLoad[spec.item], equalTo(StringWrapper(\"string\")))\n            }\n        }\n    }\n})\n\nprivate data class StringWrapper(val string: String)\n\nprivate class StringWrapperDeserializer :\n    StdDeserializer<StringWrapper>(StringWrapper::class.java) {\n    override fun deserialize(\n        jp: JsonParser,\n        ctxt: DeserializationContext\n    ): StringWrapper {\n        val node = jp.codec.readTree<JsonNode>(jp)\n        return StringWrapper(node.textValue())\n    }\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/NetworkBuffer.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nclass NetworkBuffer {\n    companion object : ConfigSpec(\"network.buffer\") {\n        val size by required<Int>(description = \"size of buffer in KB\")\n\n        val maxSize by lazy(description = \"max size of buffer in KB\") { it[size] * 2 }\n\n        val name by optional(\"buffer\", description = \"name of buffer\")\n\n        val type by optional(\n            Type.OFF_HEAP,\n            description =\n            \"\"\"\n                | type of network buffer.\n                | two type:\n                | - on-heap\n                | - off-heap\n                | buffer is off-heap by default.\n                \"\"\".trimMargin(\"| \")\n        )\n\n        val offset by optional<Int?>(null, description = \"initial offset of buffer\")\n    }\n\n    enum class Type {\n        ON_HEAP, OFF_HEAP\n    }\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/ParseDurationSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.ParseException\nimport com.uchuhimo.konf.source.toDuration\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.time.Duration\n\nobject ParseDurationSpec : Spek({\n    on(\"parse empty string\") {\n        it(\"throws ParseException\") {\n            assertThat({ \"\".toDuration() }, throws<ParseException>())\n        }\n    }\n    on(\"parse string without unit\") {\n        it(\"parse as milliseconds\") {\n            assertThat(\"1\".toDuration(), equalTo(Duration.ofMillis(1)))\n        }\n    }\n    on(\"parse string with unit 'ms'\") {\n        it(\"parse as milliseconds\") {\n            assertThat(\"1ms\".toDuration(), equalTo(Duration.ofMillis(1)))\n        }\n    }\n    on(\"parse string with unit 'millis'\") {\n        it(\"parse as milliseconds\") {\n            assertThat(\"1 millis\".toDuration(), equalTo(Duration.ofMillis(1)))\n        }\n    }\n    on(\"parse string with unit 'milliseconds'\") {\n        it(\"parse as milliseconds\") {\n            assertThat(\"1 milliseconds\".toDuration(), equalTo(Duration.ofMillis(1)))\n        }\n    }\n    on(\"parse string with unit 'us'\") {\n        it(\"parse as microseconds\") {\n            assertThat(\"1us\".toDuration(), equalTo(Duration.ofNanos(1000)))\n        }\n    }\n    on(\"parse string with unit 'micros'\") {\n        it(\"parse as microseconds\") {\n            assertThat(\"1 micros\".toDuration(), equalTo(Duration.ofNanos(1000)))\n        }\n    }\n    on(\"parse string with unit 'microseconds'\") {\n        it(\"parse as microseconds\") {\n            assertThat(\"1 microseconds\".toDuration(), equalTo(Duration.ofNanos(1000)))\n        }\n    }\n    on(\"parse string with unit 'ns'\") {\n        it(\"parse as nanoseconds\") {\n            assertThat(\"1ns\".toDuration(), equalTo(Duration.ofNanos(1)))\n        }\n    }\n    on(\"parse string with unit 'nanos'\") {\n        it(\"parse as nanoseconds\") {\n            assertThat(\"1 nanos\".toDuration(), equalTo(Duration.ofNanos(1)))\n        }\n    }\n    on(\"parse string with unit 'nanoseconds'\") {\n        it(\"parse as nanoseconds\") {\n            assertThat(\"1 nanoseconds\".toDuration(), equalTo(Duration.ofNanos(1)))\n        }\n    }\n    on(\"parse string with unit 'd'\") {\n        it(\"parse as days\") {\n            assertThat(\"1d\".toDuration(), equalTo(Duration.ofDays(1)))\n        }\n    }\n    on(\"parse string with unit 'days'\") {\n        it(\"parse as days\") {\n            assertThat(\"1 days\".toDuration(), equalTo(Duration.ofDays(1)))\n        }\n    }\n    on(\"parse string with unit 'h'\") {\n        it(\"parse as hours\") {\n            assertThat(\"1h\".toDuration(), equalTo(Duration.ofHours(1)))\n        }\n    }\n    on(\"parse string with unit 'hours'\") {\n        it(\"parse as hours\") {\n            assertThat(\"1 hours\".toDuration(), equalTo(Duration.ofHours(1)))\n        }\n    }\n    on(\"parse string with unit 's'\") {\n        it(\"parse as seconds\") {\n            assertThat(\"1s\".toDuration(), equalTo(Duration.ofSeconds(1)))\n        }\n    }\n    on(\"parse string with unit 'seconds'\") {\n        it(\"parse as seconds\") {\n            assertThat(\"1 seconds\".toDuration(), equalTo(Duration.ofSeconds(1)))\n        }\n    }\n    on(\"parse string with unit 'm'\") {\n        it(\"parse as minutes\") {\n            assertThat(\"1m\".toDuration(), equalTo(Duration.ofMinutes(1)))\n        }\n    }\n    on(\"parse string with unit 'minutes'\") {\n        it(\"parse as minutes\") {\n            assertThat(\"1 minutes\".toDuration(), equalTo(Duration.ofMinutes(1)))\n        }\n    }\n    on(\"parse string with float number\") {\n        it(\"parse and convert from double to long\") {\n            assertThat(\"1.5ms\".toDuration(), equalTo(Duration.ofNanos(1_500_000)))\n        }\n    }\n    on(\"parse string with invalid unit\") {\n        it(\"throws ParseException\") {\n            assertThat({ \"1x\".toDuration() }, throws<ParseException>())\n        }\n    }\n    on(\"parse string with invalid number\") {\n        it(\"throws ParseException\") {\n            assertThat({ \"*1s\".toDuration() }, throws<ParseException>())\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/RelocatedConfigSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.sameInstance\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject RelocatedConfigSpec : SubjectSpek<Config>({\n\n    subject { Prefix(\"network.buffer\") + Config { addSpec(NetworkBuffer) }.at(\"network.buffer\") }\n\n    configTestSpec()\n})\n\nobject RollUpConfigSpec : SubjectSpek<Config>({\n\n    subject { Prefix(\"prefix\") + Config { addSpec(NetworkBuffer) } }\n\n    configTestSpec(\"prefix.network.buffer\")\n\n    on(\"prefix is empty string\") {\n        it(\"should return itself\") {\n            assertThat(Prefix() + subject, sameInstance(subject))\n        }\n    }\n})\n\nobject MultiLayerRollUpConfigSpec : SubjectSpek<Config>({\n\n    subject { (Prefix(\"prefix\") + Config { addSpec(NetworkBuffer) }).withLayer(\"multi-layer\") }\n\n    configTestSpec(\"prefix.network.buffer\")\n})\n\nobject RollUpMultiLayerConfigSpec : SubjectSpek<Config>({\n\n    subject { Prefix(\"prefix\") + Config { addSpec(NetworkBuffer) }.withLayer(\"multi-layer\") }\n\n    configTestSpec(\"prefix.network.buffer\")\n})\n\nobject DrillDownConfigSpec : SubjectSpek<Config>({\n\n    subject { Config { addSpec(NetworkBuffer) }.at(\"network\") }\n\n    configTestSpec(\"buffer\")\n\n    on(\"path is empty string\") {\n        it(\"should return itself\") {\n            assertThat(subject.at(\"\"), sameInstance(subject))\n        }\n    }\n})\n\nobject MultiLayerDrillDownConfigSpec : SubjectSpek<Config>({\n\n    subject { Config { addSpec(NetworkBuffer) }.at(\"network\").withLayer(\"multi-layer\") }\n\n    configTestSpec(\"buffer\")\n})\n\nobject DrillDownMultiLayerConfigSpec : SubjectSpek<Config>({\n\n    subject { Config { addSpec(NetworkBuffer) }.withLayer(\"multi-layer\").at(\"network\") }\n\n    configTestSpec(\"buffer\")\n})\n\nobject MultiLayerFacadeDrillDownConfigSpec : SubjectSpek<Config>({\n    subject {\n        (\n            Config() + Config {\n                addSpec(NetworkBuffer)\n            }.withLayer(\"layer1\")\n                .at(\"network\")\n                .withLayer(\"layer2\")\n            ).withLayer(\"layer3\")\n    }\n\n    configTestSpec(\"buffer\")\n})\n\nobject MultiLayerRollUpFallbackConfigSpec : SubjectSpek<Config>({\n    subject {\n        (\n            (\n                Prefix(\"prefix\") +\n                    Config { addSpec(NetworkBuffer) }.withLayer(\"layer1\")\n                ).withLayer(\"layer2\") +\n                Config()\n            ).withLayer(\"layer3\")\n    }\n\n    configTestSpec(\"prefix.network.buffer\")\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/SizeInBytesSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.ParseException\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject SizeInBytesSpec : Spek({\n    on(\"parse valid string\") {\n        it(\"parse as valid size in bytes\") {\n            assertThat(SizeInBytes.parse(\"1k\").bytes, equalTo(1024L))\n        }\n    }\n    on(\"init with negative number\") {\n        it(\"should throw IllegalArgumentException\") {\n            assertThat({ SizeInBytes(-1L) }, throws<IllegalArgumentException>())\n        }\n    }\n    on(\"parse string without number part\") {\n        it(\"should throw ParseException\") {\n            assertThat({ SizeInBytes.parse(\"m\") }, throws<ParseException>())\n        }\n    }\n    on(\"parse string with float number\") {\n        it(\"parse and convert from double to long\") {\n            assertThat(SizeInBytes.parse(\"1.5kB\").bytes, equalTo(1500L))\n        }\n    }\n    on(\"parse string with invalid unit\") {\n        it(\"throws ParseException\") {\n            assertThat({ SizeInBytes.parse(\"1kb\") }, throws<ParseException>())\n        }\n    }\n    on(\"parse string with invalid number\") {\n        it(\"throws ParseException\") {\n            assertThat({ SizeInBytes.parse(\"*1k\") }, throws<ParseException>())\n        }\n    }\n    on(\"parse number out of range for a 64-bit long\") {\n        it(\"throws ParseException\") {\n            assertThat({ SizeInBytes.parse(\"1z\") }, throws<ParseException>())\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/TreeNodeSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.asSource\nimport com.uchuhimo.konf.source.asTree\nimport com.uchuhimo.konf.source.base.toHierarchical\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject TreeNodeSpec : SubjectSpek<TreeNode>({\n    subject {\n        ContainerNode(\n            mutableMapOf(\n                \"level1\" to ContainerNode(\n                    mutableMapOf(\"level2\" to EmptyNode)\n                )\n            )\n        )\n    }\n    given(\"a tree node\") {\n        on(\"convert to tree\") {\n            it(\"should return itself\") {\n                assertThat(subject.asTree(), sameInstance(subject))\n            }\n        }\n        on(\"convert to source\") {\n            it(\"should be the tree in the source\") {\n                assertThat(subject.asSource().tree, sameInstance(subject))\n            }\n        }\n        on(\"set with an invalid path\") {\n            it(\"should throw InvalidPathException\") {\n                assertThat(\n                    { subject[\"\"] = EmptyNode },\n                    throws(has(PathConflictException::path, equalTo(\"\")))\n                )\n                assertThat(\n                    { subject[\"level1.level2.level3\"] = EmptyNode },\n                    throws(has(PathConflictException::path, equalTo(\"level1.level2.level3\")))\n                )\n            }\n        }\n        on(\"minus itself\") {\n            it(\"should return an empty node\") {\n                assertThat(subject - subject, equalTo<TreeNode>(EmptyNode))\n            }\n        }\n        on(\"minus a leaf node\") {\n            it(\"should return an empty node\") {\n                assertThat(subject - EmptyNode, equalTo<TreeNode>(EmptyNode))\n            }\n        }\n        on(\"merge two trees\") {\n            val facadeNode = 1.asTree()\n            val facade = mapOf(\n                \"key1\" to facadeNode,\n                \"key2\" to EmptyNode,\n                \"key4\" to mapOf(\"level2\" to facadeNode)\n            ).asTree()\n            val fallbackNode = 2.asTree()\n            val fallback = mapOf(\n                \"key1\" to EmptyNode,\n                \"key2\" to fallbackNode,\n                \"key3\" to fallbackNode,\n                \"key4\" to mapOf(\"level2\" to fallbackNode)\n            ).asTree()\n            it(\"should return the merged tree when valid\") {\n                assertThat(\n                    (fallback + facade).toHierarchical(),\n                    equalTo(\n                        mapOf(\n                            \"key1\" to facadeNode,\n                            \"key2\" to EmptyNode,\n                            \"key3\" to fallbackNode,\n                            \"key4\" to mapOf(\"level2\" to facadeNode)\n                        ).asTree().toHierarchical()\n                    )\n                )\n                assertThat(\n                    facade.withFallback(fallback).toHierarchical(),\n                    equalTo(\n                        mapOf(\n                            \"key1\" to facadeNode,\n                            \"key2\" to EmptyNode,\n                            \"key3\" to fallbackNode,\n                            \"key4\" to mapOf(\"level2\" to facadeNode)\n                        ).asTree().toHierarchical()\n                    )\n                )\n                assertThat(\n                    (EmptyNode + facade).toHierarchical(),\n                    equalTo(facade.toHierarchical())\n                )\n                assertThat(\n                    (fallback + EmptyNode).toHierarchical(),\n                    equalTo(EmptyNode.toHierarchical())\n                )\n                assertThat(\n                    (fallback + mapOf(\"key1\" to mapOf(\"key2\" to EmptyNode)).asTree()).toHierarchical(),\n                    equalTo(\n                        mapOf(\n                            \"key1\" to mapOf(\n                                \"key2\" to EmptyNode\n                            ),\n                            \"key2\" to fallbackNode,\n                            \"key3\" to fallbackNode,\n                            \"key4\" to mapOf(\"level2\" to fallbackNode)\n                        ).asTree().toHierarchical()\n                    )\n                )\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/CustomDeserializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize\nimport com.fasterxml.jackson.databind.deser.std.StdDeserializer\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\n@JsonDeserialize(using = SealedClassDeserializer::class)\nsealed class SealedClass\n\ndata class VariantA(val int: Int) : SealedClass()\ndata class VariantB(val double: Double) : SealedClass()\n\nclass SealedClassDeserializer : StdDeserializer<SealedClass>(SealedClass::class.java) {\n    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SealedClass {\n        val node: JsonNode = p.codec.readTree(p)\n        return if (node.has(\"int\")) {\n            VariantA(node.get(\"int\").asInt())\n        } else {\n            VariantB(node.get(\"double\").asDouble())\n        }\n    }\n}\n\nobject CustomDeserializerConfig : ConfigSpec(\"level1.level2\") {\n    val variantA by required<SealedClass>()\n    val variantB by required<SealedClass>()\n}\n\nobject CustomDeserializerSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(CustomDeserializerConfig)\n        }.from.map.kv(loadContent)\n    }\n    given(\"a source\") {\n        on(\"load the source into config\") {\n            it(\"should contain every value specified in the source\") {\n                val variantA = VariantA(1)\n                val variantB = VariantB(2.0)\n                assertThat(subject[CustomDeserializerConfig.variantA], equalTo<SealedClass>(variantA))\n                assertThat(subject[CustomDeserializerConfig.variantB], equalTo<SealedClass>(variantB))\n            }\n        }\n    }\n})\n\nprivate val loadContent = mapOf<String, Any>(\n    \"variantA\" to mapOf(\"int\" to 1),\n    \"variantB\" to mapOf(\"double\" to 2.0)\n).mapKeys { (key, _) -> \"level1.level2.$key\" }\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultLoadersSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport com.uchuhimo.konf.tempFileOf\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport spark.Service\nimport java.net.URL\nimport java.util.UUID\nimport java.util.concurrent.TimeUnit\n\nobject DefaultLoadersSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from environment-like map\") {\n            val config = subject.envMap(mapOf(\"SOURCE_TEST_TYPE\" to \"env\"))\n            it(\"should return a config which contains value from environment-like map\") {\n                assertThat(config[item], equalTo(\"env\"))\n            }\n        }\n        on(\"load from system environment\") {\n            val config = subject.env()\n            it(\"should return a config which contains value from system environment\") {\n                assertThat(config[item], equalTo(\"env\"))\n            }\n        }\n        on(\"load from system properties\") {\n            System.setProperty(DefaultLoadersConfig.qualify(DefaultLoadersConfig.type), \"system\")\n            val config = subject.systemProperties()\n            it(\"should return a config which contains value from system properties\") {\n                assertThat(config[item], equalTo(\"system\"))\n            }\n        }\n        on(\"dispatch loader based on extension\") {\n            it(\"should throw UnsupportedExtensionException when the extension is unsupported\") {\n                assertThat({ subject.dispatchExtension(\"txt\") }, throws<UnsupportedExtensionException>())\n            }\n            it(\"should return the corresponding loader when the extension is registered\") {\n                val extension = UUID.randomUUID().toString()\n                Provider.registerExtension(extension, PropertiesProvider)\n                subject.dispatchExtension(extension)\n                Provider.unregisterExtension(extension)\n            }\n        }\n        on(\"load from provider\") {\n            val config = subject.source(PropertiesProvider).file(tempFileOf(propertiesContent, suffix = \".properties\"))\n            it(\"should load with the provider\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n            it(\"should build a new layer on the parent config\") {\n                assertThat(config.parent!!, sameInstance(subject.config))\n            }\n        }\n        on(\"load from URL\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> propertiesContent }\n            service.awaitInitialization()\n            val config = subject.url(URL(\"http://localhost:${service.port()}/source.properties\"))\n            it(\"should load as auto-detected URL format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n            service.stop()\n        }\n        on(\"load from URL string\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> propertiesContent }\n            service.awaitInitialization()\n            val config = subject.url(\"http://localhost:${service.port()}/source.properties\")\n            it(\"should load as auto-detected URL format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n            service.stop()\n        }\n        on(\"load from file\") {\n            val config = subject.file(tempFileOf(propertiesContent, suffix = \".properties\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n        }\n        on(\"load from file path\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.file(file.path)\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n        }\n        on(\"load from watched file\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.watchFile(file, 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected file format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file with default delay time\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.watchFile(file, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected file format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file with listener\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            var newValue = \"\"\n            subject.watchFile(\n                file,\n                1,\n                unit = TimeUnit.SECONDS,\n                context = Dispatchers.Sequential\n            ) { config, _ ->\n                newValue = config[item]\n            }\n            file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file path\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.watchFile(file.path, 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected file format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file path with default delay time\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.watchFile(file.path, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected file format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched URL\") {\n            var content = propertiesContent\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source.properties\"\n            val config = subject.watchUrl(URL(url), period = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            content = propertiesContent.replace(\"properties\", \"newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected URL format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched URL with default delay time\") {\n            var content = propertiesContent\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source.properties\"\n            val config = subject.watchUrl(URL(url), context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            content = propertiesContent.replace(\"properties\", \"newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected URL format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched URL string\") {\n            var content = propertiesContent\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source.properties\"\n            val config = subject.watchUrl(url, period = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            content = propertiesContent.replace(\"properties\", \"newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected URL format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched URL string with default delay time\") {\n            var content = propertiesContent\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source.properties\"\n            val config = subject.watchUrl(url, context = Dispatchers.Sequential)\n            val originalValue = config[item]\n            content = propertiesContent.replace(\"properties\", \"newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[item]\n            it(\"should load as auto-detected URL format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched URL string with listener\") {\n            var content = propertiesContent\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source.properties\"\n            var newValue = \"\"\n            val config = subject.watchUrl(\n                url,\n                period = 1,\n                unit = TimeUnit.SECONDS,\n                context = Dispatchers.Sequential\n            ) { config, _ ->\n                newValue = config[item]\n            }\n            val originalValue = config[item]\n            content = propertiesContent.replace(\"properties\", \"newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            it(\"should load as auto-detected URL format\") {\n                assertThat(originalValue, equalTo(\"properties\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from map\") {\n            it(\"should use the same config\") {\n                assertThat(subject.config, sameInstance(subject.map.config))\n            }\n        }\n    }\n})\n\nobject DefaultLoadersWithFlattenEnvSpec : Spek({\n    given(\"a loader\") {\n        on(\"load as flatten format from system environment\") {\n            val config = Config {\n                addSpec(FlattenDefaultLoadersConfig)\n            }.from.env(nested = false)\n            it(\"should return a config which contains value from system environment\") {\n                assertThat(config[FlattenDefaultLoadersConfig.SOURCE_TEST_TYPE], equalTo(\"env\"))\n            }\n        }\n    }\n})\n\nobject MappedDefaultLoadersSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig[\"source\"])\n        }.from.mapped { it[\"source\"] }\n    }\n\n    itBehavesLike(DefaultLoadersSpec)\n})\n\nobject PrefixedDefaultLoadersSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig.withPrefix(\"prefix\"))\n        }.from.prefixed(\"prefix\")\n    }\n\n    itBehavesLike(DefaultLoadersSpec)\n})\n\nobject ScopedDefaultLoadersSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig[\"source\"])\n        }.from.scoped(\"source\")\n    }\n\n    itBehavesLike(DefaultLoadersSpec)\n})\n\nobject FlattenDefaultLoadersConfig : ConfigSpec(\"\") {\n    val SOURCE_TEST_TYPE by required<String>()\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultProvidersSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport spark.Service\nimport java.net.URL\nimport java.util.UUID\n\nobject DefaultProvidersSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provide source from system environment\") {\n            val config = subject.env().toConfig()\n            it(\"should return a source which contains value from system environment\") {\n                assertThat(config[item], equalTo(\"env\"))\n            }\n        }\n        on(\"provide flatten source from system environment\") {\n            val config = subject.env(nested = false).toFlattenConfig()\n            it(\"should return a source which contains value from system environment\") {\n                assertThat(config[FlattenDefaultLoadersConfig.SOURCE_TEST_TYPE], equalTo(\"env\"))\n            }\n        }\n        on(\"provide source from system properties\") {\n            System.setProperty(DefaultLoadersConfig.qualify(DefaultLoadersConfig.type), \"system\")\n            val config = subject.systemProperties().toConfig()\n            it(\"should return a source which contains value from system properties\") {\n                assertThat(config[item], equalTo(\"system\"))\n            }\n        }\n        on(\"dispatch provider based on extension\") {\n            it(\"should throw UnsupportedExtensionException when the extension is unsupported\") {\n                assertThat({ subject.dispatchExtension(\"txt\") }, throws<UnsupportedExtensionException>())\n            }\n            it(\"should return the corresponding provider when the extension is registered\") {\n                val extension = UUID.randomUUID().toString()\n                Provider.registerExtension(extension, PropertiesProvider)\n                assertThat(subject.dispatchExtension(extension), sameInstance(PropertiesProvider as Provider))\n                Provider.unregisterExtension(extension)\n            }\n        }\n        on(\"provide source from URL\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> propertiesContent }\n            service.awaitInitialization()\n            val config = subject.url(URL(\"http://localhost:${service.port()}/source.properties\")).toConfig()\n            it(\"should provide as auto-detected URL format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n            service.stop()\n        }\n        on(\"provide source from URL string\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source.properties\") { _, _ -> propertiesContent }\n            service.awaitInitialization()\n            val config = subject.url(\"http://localhost:${service.port()}/source.properties\").toConfig()\n            it(\"should provide as auto-detected URL format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n            service.stop()\n        }\n        on(\"provide source from file\") {\n            val config = subject.file(tempFileOf(propertiesContent, suffix = \".properties\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n        }\n        on(\"provide source from file path\") {\n            val file = tempFileOf(propertiesContent, suffix = \".properties\")\n            val config = subject.file(file.path).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"properties\"))\n            }\n        }\n    }\n})\n\nfun Source.toFlattenConfig(): Config = Config {\n    addSpec(FlattenDefaultLoadersConfig)\n}.withSource(this)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/FacadeSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.name\nimport com.uchuhimo.konf.source.base.asKVSource\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport kotlin.test.assertFalse\nimport kotlin.test.assertTrue\n\nobject FacadeSourceSpec : Spek({\n    given(\"a source with facade\") {\n        it(\"contains facade & fallback info\") {\n            val facadeSource = 1.asSource()\n            val fallbackSource = 2.asSource()\n            val source = fallbackSource + facadeSource\n            assertThat(source.info[\"facade\"], equalTo(facadeSource.description))\n            assertThat(source.info[\"fallback\"], equalTo(fallbackSource.description))\n        }\n        on(\"path/key is in facade source\") {\n            val path = listOf(\"a\", \"b\")\n            val key = path.name\n            val fallbackSource = mapOf(key to \"fallback\").asKVSource()\n            val facadeSource = mapOf(key to \"facade\").asKVSource()\n            val source = fallbackSource + facadeSource\n            it(\"gets value from facade source\") {\n                assertTrue(path in source)\n                assertTrue(key in source)\n                assertThat(source[path].asValue<String>(), equalTo(facadeSource[path].asValue<String>()))\n                assertThat(source[key].asValue<String>(), equalTo(facadeSource[key].asValue<String>()))\n                assertThat(\n                    source.getOrNull(path)?.asValue<String>(),\n                    equalTo(facadeSource.getOrNull(path)?.asValue<String>())\n                )\n                assertThat(\n                    source.getOrNull(key)?.asValue<String>(),\n                    equalTo(facadeSource.getOrNull(key)?.asValue<String>())\n                )\n            }\n        }\n        on(\"path/key is in fallback source\") {\n            val path = listOf(\"a\", \"b\")\n            val key = path.name\n            val fallbackSource = mapOf(key to \"fallback\").asKVSource()\n            val facadePath = listOf(\"a\", \"c\")\n            val facadeKey = facadePath.name\n            val facadeSource = mapOf(facadeKey to \"facade\").asKVSource()\n            val source = fallbackSource + facadeSource\n            it(\"gets value from fallback source\") {\n                assertTrue(path in source)\n                assertTrue(key in source)\n                assertThat(source[path].asValue<String>(), equalTo(fallbackSource[path].asValue<String>()))\n                assertThat(source[key].asValue<String>(), equalTo(fallbackSource[key].asValue<String>()))\n                assertThat(\n                    source.getOrNull(path)?.asValue<String>(),\n                    equalTo(fallbackSource.getOrNull(path)?.asValue<String>())\n                )\n                assertThat(\n                    source.getOrNull(key)?.asValue<String>(),\n                    equalTo(fallbackSource.getOrNull(key)?.asValue<String>())\n                )\n            }\n            it(\"contains value in facade source\") {\n                assertTrue(facadePath in source)\n                assertTrue(facadeKey in source)\n            }\n            it(\"does not contain value which is not existed in both facade source and fallback source\") {\n                assertFalse(\"a.d\".toPath() in source)\n                assertFalse(\"a.d\" in source)\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/FallbackSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.name\nimport com.uchuhimo.konf.source.base.asKVSource\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport kotlin.test.assertFalse\nimport kotlin.test.assertTrue\n\nobject FallbackSourceSpec : Spek({\n    given(\"a source with fallback\") {\n        it(\"contains facade & fallback info\") {\n            val facadeSource = 1.asSource()\n            val fallbackSource = 2.asSource()\n            val source = facadeSource.withFallback(fallbackSource)\n            assertThat(source.info[\"facade\"], equalTo(facadeSource.description))\n            assertThat(source.info[\"fallback\"], equalTo(fallbackSource.description))\n        }\n        on(\"path/key is in facade source\") {\n            val path = listOf(\"a\", \"b\")\n            val key = path.name\n            val fallbackSource = mapOf(key to \"fallback\").asKVSource()\n            val facadeSource = mapOf(key to \"facade\").asKVSource()\n            val source = facadeSource.withFallback(fallbackSource)\n            it(\"gets value from facade source\") {\n                assertTrue(path in source)\n                assertTrue(key in source)\n                assertThat(source[path].asValue<String>(), equalTo(facadeSource[path].asValue<String>()))\n                assertThat(source[key].asValue<String>(), equalTo(facadeSource[key].asValue<String>()))\n                assertThat(\n                    source.getOrNull(path)?.asValue<String>(),\n                    equalTo(facadeSource.getOrNull(path)?.asValue<String>())\n                )\n                assertThat(\n                    source.getOrNull(key)?.asValue<String>(),\n                    equalTo(facadeSource.getOrNull(key)?.asValue<String>())\n                )\n            }\n        }\n        on(\"path/key is in fallback source\") {\n            val path = listOf(\"a\", \"b\")\n            val key = path.name\n            val fallbackSource = mapOf(key to \"fallback\").asKVSource()\n            val facadePath = listOf(\"a\", \"c\")\n            val facadeKey = facadePath.name\n            val facadeSource = mapOf(facadeKey to \"facade\").asKVSource()\n            val source = facadeSource.withFallback(fallbackSource)\n            it(\"gets value from fallback source\") {\n                assertTrue(path in source)\n                assertTrue(key in source)\n                assertThat(source[path].asValue<String>(), equalTo(fallbackSource[path].asValue<String>()))\n                assertThat(source[key].asValue<String>(), equalTo(fallbackSource[key].asValue<String>()))\n                assertThat(\n                    source.getOrNull(path)?.asValue<String>(),\n                    equalTo(fallbackSource.getOrNull(path)?.asValue<String>())\n                )\n                assertThat(\n                    source.getOrNull(key)?.asValue<String>(),\n                    equalTo(fallbackSource.getOrNull(key)?.asValue<String>())\n                )\n            }\n            it(\"contains value in facade source\") {\n                assertTrue(facadePath in source)\n                assertTrue(facadeKey in source)\n            }\n            it(\"does not contain value which is not existed in both facade source and fallback source\") {\n                assertFalse(\"a.d\".toPath() in source)\n                assertFalse(\"a.d\" in source)\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/LoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.tempFileOf\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport spark.Service\nimport java.util.concurrent.TimeUnit\n\nobject LoaderSpec : SubjectSpek<Loader>({\n    val parentConfig = Config {\n        addSpec(SourceType)\n    }\n    subject {\n        parentConfig.from.properties\n    }\n\n    given(\"a loader\") {\n        it(\"should fork from parent config\") {\n            assertThat(subject.config, equalTo(parentConfig))\n        }\n        on(\"load from reader\") {\n            val config = subject.reader(\"type = reader\".reader())\n            it(\"should return a config which contains value from reader\") {\n                assertThat(config[SourceType.type], equalTo(\"reader\"))\n            }\n        }\n        on(\"load from input stream\") {\n            val config = subject.inputStream(\n                tempFileOf(\"type = inputStream\").inputStream()\n            )\n            it(\"should return a config which contains value from input stream\") {\n                assertThat(config[SourceType.type], equalTo(\"inputStream\"))\n            }\n        }\n        on(\"load from file\") {\n            val config = subject.file(tempFileOf(\"type = file\"))\n            it(\"should return a config which contains value in file\") {\n                assertThat(config[SourceType.type], equalTo(\"file\"))\n            }\n        }\n        on(\"load from file path\") {\n            val config = subject.file(tempFileOf(\"type = file\").toString())\n            it(\"should return a config which contains value in file\") {\n                assertThat(config[SourceType.type], equalTo(\"file\"))\n            }\n        }\n        on(\"load from watched file\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchFile(file, delayTime = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in file\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file on macOS\") {\n            val os = System.getProperty(\"os.name\")\n            System.setProperty(\"os.name\", \"mac\")\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchFile(file, delayTime = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in file\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n            System.setProperty(\"os.name\", os)\n        }\n        on(\"load from watched file with default delay time\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchFile(file, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in file\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file with listener\") {\n            val file = tempFileOf(\"type = originalValue\")\n            var newValue = \"\"\n            subject.watchFile(\n                file,\n                delayTime = 1,\n                unit = TimeUnit.SECONDS,\n                context = Dispatchers.Sequential\n            ) { config, _ ->\n                newValue = config[SourceType.type]\n            }\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file path\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchFile(file.toString(), delayTime = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in file\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file path with default delay time\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchFile(file.toString(), context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in file\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value when file has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from string\") {\n            val config = subject.string(\"type = string\")\n            it(\"should return a config which contains value in string\") {\n                assertThat(config[SourceType.type], equalTo(\"string\"))\n            }\n        }\n        on(\"load from byte array\") {\n            val config = subject.bytes(\"type = bytes\".toByteArray())\n            it(\"should return a config which contains value in byte array\") {\n                assertThat(config[SourceType.type], equalTo(\"bytes\"))\n            }\n        }\n        on(\"load from byte array slice\") {\n            val config = subject.bytes(\"|type = slice|\".toByteArray(), 1, 12)\n            it(\"should return a config which contains value in byte array slice\") {\n                assertThat(config[SourceType.type], equalTo(\"slice\"))\n            }\n        }\n        on(\"load from HTTP URL\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source\") { _, _ -> \"type = http\" }\n            service.awaitInitialization()\n            val config = subject.url(\"http://localhost:${service.port()}/source\")\n            it(\"should return a config which contains value in URL\") {\n                assertThat(config[SourceType.type], equalTo(\"http\"))\n            }\n            service.stop()\n        }\n        on(\"load from file URL\") {\n            val file = tempFileOf(\"type = fileUrl\")\n            val config = subject.url(file.toURI().toURL())\n            it(\"should return a config which contains value in URL\") {\n                assertThat(config[SourceType.type], equalTo(\"fileUrl\"))\n            }\n        }\n        on(\"load from file URL string\") {\n            val url = tempFileOf(\"type = fileUrl\").toURI().toURL().toString()\n            val config = subject.url(url)\n            it(\"should return a config which contains value in URL\") {\n                assertThat(config[SourceType.type], equalTo(\"fileUrl\"))\n            }\n        }\n        on(\"load from watched HTTP URL\") {\n            var content = \"type = originalValue\"\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source\") { _, _ -> content }\n            service.awaitInitialization()\n            val url = \"http://localhost:${service.port()}/source\"\n            val config = subject.watchUrl(url, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            content = \"type = newValue\"\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file URL\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchUrl(file.toURI().toURL(), period = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file URL with default delay time\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val config = subject.watchUrl(file.toURI().toURL(), context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file URL with listener\") {\n            val file = tempFileOf(\"type = originalValue\")\n            var newValue = \"\"\n            val config = subject.watchUrl(\n                file.toURI().toURL(),\n                period = 1,\n                unit = TimeUnit.SECONDS,\n                context = Dispatchers.Sequential\n            ) { config, _ ->\n                newValue = config[SourceType.type]\n            }\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file URL string\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val url = file.toURI().toURL()\n            val config = subject.watchUrl(url.toString(), period = 1, unit = TimeUnit.SECONDS, context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(1))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from watched file URL string with default delay time\") {\n            val file = tempFileOf(\"type = originalValue\")\n            val url = file.toURI().toURL()\n            val config = subject.watchUrl(url.toString(), context = Dispatchers.Sequential)\n            val originalValue = config[SourceType.type]\n            file.writeText(\"type = newValue\")\n            runBlocking(Dispatchers.Sequential) {\n                delay(TimeUnit.SECONDS.toMillis(5))\n            }\n            val newValue = config[SourceType.type]\n            it(\"should return a config which contains value in URL\") {\n                assertThat(originalValue, equalTo(\"originalValue\"))\n            }\n            it(\"should load new value after URL content has been changed\") {\n                assertThat(newValue, equalTo(\"newValue\"))\n            }\n        }\n        on(\"load from resource\") {\n            val config = subject.resource(\"source/provider.properties\")\n            it(\"should return a config which contains value in resource\") {\n                assertThat(config[SourceType.type], equalTo(\"resource\"))\n            }\n        }\n        on(\"load from non-existed resource\") {\n            it(\"should throw SourceNotFoundException\") {\n                assertThat(\n                    { subject.resource(\"source/no-provider.properties\") },\n                    throws<SourceNotFoundException>()\n                )\n            }\n        }\n    }\n})\n\nprivate object SourceType : ConfigSpec(\"\") {\n    val type by required<String>()\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/MergedSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport com.uchuhimo.konf.toSizeInBytes\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZonedDateTime\nimport java.util.Date\n\nobject MergedSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.withSource(fallbackContent.asSource() + facadeContent.asSource())\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject MergedSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.withSource(fallbackContent.asSource() + facadeContent.asSource())\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.map.hierarchical(config.toHierarchicalMap())\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nprivate val facadeContent = mapOf(\n    \"level1\" to mapOf(\n        \"level2\" to\n            mapOf<String, Any>(\n                \"empty\" to \"null\",\n                \"literalEmpty\" to \"null\",\n                \"present\" to 1,\n\n                \"boolean\" to false,\n\n                \"double\" to 1.5,\n                \"float\" to -1.5f,\n                \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n                \"char\" to 'a',\n\n                \"enum\" to \"LABEL2\",\n\n                \"array\" to mapOf(\n                    \"boolean\" to listOf(true, false),\n                    \"byte\" to listOf<Byte>(1, 2, 3),\n                    \"short\" to listOf<Short>(1, 2, 3),\n                    \"int\" to listOf(1, 2, 3),\n\n                    \"object\" to mapOf(\n                        \"boolean\" to listOf(true, false),\n                        \"int\" to listOf(1, 2, 3),\n                        \"string\" to listOf(\"one\", \"two\", \"three\")\n                    )\n                ),\n\n                \"list\" to listOf(1, 2, 3),\n                \"mutableList\" to listOf(1, 2, 3),\n                \"listOfList\" to listOf(listOf(1, 2), listOf(3, 4)),\n\n                \"map\" to mapOf(\n                    \"a\" to 1,\n                    \"c\" to 3\n                ),\n                \"intMap\" to mapOf(\n                    1 to \"a\",\n                    3 to \"c\"\n                ),\n                \"sortedMap\" to mapOf(\n                    \"c\" to 3,\n                    \"a\" to 1\n                ),\n                \"listOfMap\" to listOf(\n                    mapOf(\"a\" to 1, \"b\" to 2),\n                    mapOf(\"a\" to 3, \"b\" to 4)\n                ),\n\n                \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1)))),\n\n                \"pair\" to mapOf(\"first\" to 1, \"second\" to 2),\n\n                \"clazz\" to mapOf(\n                    \"boolean\" to false,\n\n                    \"int\" to 1,\n                    \"short\" to 2.toShort(),\n                    \"byte\" to 3.toByte(),\n                    \"bigInteger\" to BigInteger.valueOf(4),\n                    \"long\" to 4L,\n\n                    \"char\" to 'a',\n\n                    \"string\" to \"string\",\n                    \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n                    \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n                    \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n                    \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n                    \"localTime\" to LocalTime.parse(\"10:15:30\"),\n                    \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n                    \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n                    \"year\" to Year.parse(\"2007\"),\n                    \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n                    \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n                    \"duration\" to \"P2DT3H4M\".toDuration(),\n                    \"simpleDuration\" to \"200millis\".toDuration(),\n                    \"size\" to \"10k\".toSizeInBytes(),\n\n                    \"enum\" to \"LABEL2\",\n\n                    \"booleanArray\" to listOf(true, false),\n\n                    \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1))))\n                )\n            )\n    )\n)\n\nprivate val fallbackContent = mapOf(\n    \"level1\" to mapOf(\n        \"level2\" to\n            mapOf<String, Any>(\n                \"boolean\" to true,\n\n                \"int\" to 1,\n                \"short\" to 2.toShort(),\n                \"byte\" to 3.toByte(),\n                \"bigInteger\" to BigInteger.valueOf(4),\n                \"long\" to 4L,\n\n                \"char\" to 'b',\n\n                \"string\" to \"string\",\n                \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n                \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n                \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n                \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n                \"localTime\" to LocalTime.parse(\"10:15:30\"),\n                \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n                \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n                \"year\" to Year.parse(\"2007\"),\n                \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n                \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n                \"duration\" to \"P2DT3H4M\".toDuration(),\n                \"simpleDuration\" to \"200millis\".toDuration(),\n                \"size\" to \"10k\".toSizeInBytes(),\n\n                \"enum\" to \"LABEL3\",\n\n                \"array\" to mapOf(\n                    \"int\" to listOf(3, 2, 1),\n                    \"long\" to listOf(4L, 5L, 6L),\n                    \"float\" to listOf(-1.0F, 0.0F, 1.0F),\n                    \"double\" to listOf(-1.0, 0.0, 1.0),\n                    \"char\" to listOf('a', 'b', 'c'),\n\n                    \"object\" to mapOf(\n                        \"string\" to listOf(\"three\", \"two\", \"one\"),\n                        \"enum\" to listOf(\"LABEL1\", \"LABEL2\", \"LABEL3\")\n                    )\n                ),\n\n                \"listOfList\" to listOf(listOf(1, 2)),\n                \"set\" to listOf(1, 2, 1),\n                \"sortedSet\" to listOf(2, 1, 1, 3),\n\n                \"map\" to mapOf(\n                    \"b\" to 2,\n                    \"c\" to 3\n                ),\n                \"intMap\" to mapOf(\n                    2 to \"b\",\n                    3 to \"c\"\n                ),\n                \"sortedMap\" to mapOf(\n                    \"b\" to 2,\n                    \"a\" to 1\n                ),\n                \"listOfMap\" to listOf(\n                    mapOf(\"a\" to 1, \"b\" to 2),\n                    mapOf(\"a\" to 3, \"b\" to 4)\n                ),\n\n                \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1)))),\n\n                \"pair\" to mapOf(\"first\" to 1, \"second\" to 2),\n\n                \"clazz\" to mapOf(\n                    \"empty\" to \"null\",\n                    \"literalEmpty\" to \"null\",\n                    \"present\" to 1,\n\n                    \"boolean\" to true,\n\n                    \"double\" to 1.5,\n                    \"float\" to -1.5f,\n                    \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n                    \"char\" to 'c',\n\n                    \"enum\" to \"LABEL1\",\n\n                    \"booleanArray\" to listOf(true, false),\n\n                    \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1))))\n                )\n            )\n    )\n)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/ProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport org.junit.jupiter.api.assertThrows\nimport spark.Service\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.IOException\nimport java.net.URL\nimport kotlin.test.assertTrue\n\nobject ProviderSpec : SubjectSpek<Provider>({\n    subject { PropertiesProvider }\n\n    given(\"a provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"type = reader\".reader())\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"type = inputStream\").inputStream()\n            )\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from file\") {\n            val file = tempFileOf(\"type = file\")\n            val source = subject.file(file)\n            it(\"should create from the specified file\") {\n                assertThat(source.info[\"file\"], equalTo(file.toString()))\n            }\n            it(\"should return a source which contains value in file\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"file\"))\n            }\n            it(\"should not lock the file\") {\n                assertTrue { file.delete() }\n            }\n        }\n        on(\"create source from not-existed file\") {\n            it(\"should throw exception\") {\n                assertThrows<FileNotFoundException> { subject.file(File(\"not_existed.json\")) }\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.file(File(\"not_existed.json\"), optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n        on(\"create source from file path\") {\n            val file = tempFileOf(\"type = file\").toString()\n            val source = subject.file(file)\n            it(\"should create from the specified file path\") {\n                assertThat(source.info[\"file\"], equalTo(file))\n            }\n            it(\"should return a source which contains value in file\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"file\"))\n            }\n        }\n        on(\"create source from not-existed file path\") {\n            it(\"should throw exception\") {\n                assertThrows<FileNotFoundException> { subject.file(\"not_existed.json\") }\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.file(\"not_existed.json\", optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n        on(\"create source from string\") {\n            val content = \"type = string\"\n            val source = subject.string(content)\n            it(\"should create from the specified string\") {\n                assertThat(source.info[\"content\"], equalTo(\"\\\"\\n$content\\n\\\"\"))\n            }\n            it(\"should return a source which contains value in string\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"string\"))\n            }\n        }\n        on(\"create source from byte array\") {\n            val source = subject.bytes(\"type = bytes\".toByteArray())\n            it(\"should return a source which contains value in byte array\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"bytes\"))\n            }\n        }\n        on(\"create source from byte array slice\") {\n            val source = subject.bytes(\"|type = slice|\".toByteArray(), 1, 12)\n            it(\"should return a source which contains value in byte array slice\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"slice\"))\n            }\n        }\n        on(\"create source from HTTP URL\") {\n            val service = Service.ignite()\n            service.port(0)\n            service.get(\"/source\") { _, _ -> \"type = http\" }\n            service.awaitInitialization()\n            val urlPath = \"http://localhost:${service.port()}/source\"\n            val source = subject.url(URL(urlPath))\n            it(\"should create from the specified URL\") {\n                assertThat(source.info[\"url\"], equalTo(urlPath))\n            }\n            it(\"should return a source which contains value in URL\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"http\"))\n            }\n            service.stop()\n        }\n        on(\"create source from not-existed HTTP URL\") {\n            it(\"should throw exception\") {\n                assertThrows<IOException> { subject.url(URL(\"http://localhost/not_existed.json\")) }\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.url(URL(\"http://localhost/not_existed.json\"), optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n        on(\"create source from file URL\") {\n            val file = tempFileOf(\"type = fileUrl\")\n            val url = file.toURI().toURL()\n            val source = subject.url(url)\n            it(\"should create from the specified URL\") {\n                assertThat(source.info[\"url\"], equalTo(url.toString()))\n            }\n            it(\"should return a source which contains value in URL\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"fileUrl\"))\n            }\n            it(\"should not lock the file\") {\n                assertTrue { file.delete() }\n            }\n        }\n        on(\"create source from not-existed file URL\") {\n            it(\"should throw exception\") {\n                assertThrows<FileNotFoundException> { subject.url(URL(\"file://localhost/not_existed.json\")) }\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.url(URL(\"file://localhost/not_existed.json\"), optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n        on(\"create source from file URL string\") {\n            val file = tempFileOf(\"type = fileUrl\")\n            val url = file.toURI().toURL().toString()\n            val source = subject.url(url)\n            it(\"should create from the specified URL string\") {\n                assertThat(source.info[\"url\"], equalTo(url))\n            }\n            it(\"should return a source which contains value in URL\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"fileUrl\"))\n            }\n        }\n        on(\"create source from not-existed file URL string\") {\n            it(\"should throw exception\") {\n                assertThrows<FileNotFoundException> { subject.url(\"file://localhost/not_existed.json\") }\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.url(\"file://localhost/not_existed.json\", optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n        on(\"create source from resource\") {\n            val resource = \"source/provider.properties\"\n            val source = subject.resource(resource)\n            it(\"should create from the specified resource\") {\n                assertThat(source.info[\"resource\"], equalTo(resource))\n            }\n            it(\"should return a source which contains value in resource\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"resource\"))\n            }\n        }\n        on(\"create source from non-existed resource\") {\n            it(\"should throw SourceNotFoundException\") {\n                assertThat(\n                    { subject.resource(\"source/no-provider.properties\") },\n                    throws<SourceNotFoundException>()\n                )\n            }\n            it(\"should return an empty source if optional\") {\n                assertThat(\n                    subject.resource(\"source/no-provider.properties\", optional = true).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject MappedProviderSpec : SubjectSpek<Provider>({\n    subject { PropertiesProvider.map { source -> source.withPrefix(\"prefix\")[\"prefix\"] } }\n\n    itBehavesLike(ProviderSpec)\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/SourceInfoSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject SourceInfoSpec : SubjectSpek<SourceInfo>({\n    subject { SourceInfo(\"a\" to \"1\") }\n\n    given(\"a source info\") {\n        on(\"use as a map\") {\n            it(\"should behave like a map\") {\n                assertThat(subject.toMap(), equalTo(mapOf(\"a\" to \"1\")))\n            }\n        }\n        on(\"with new KV pairs\") {\n            it(\"should contain the new KV pairs\") {\n                assertThat(\n                    subject.with(\"b\" to \"2\", \"c\" to \"3\").toMap(),\n                    equalTo(mapOf(\"a\" to \"1\", \"b\" to \"2\", \"c\" to \"3\"))\n                )\n            }\n        }\n        on(\"with another source info\") {\n            it(\"should contain the new KV pairs in another source info\") {\n                assertThat(\n                    subject.with(SourceInfo(\"b\" to \"2\", \"c\" to \"3\")).toMap(),\n                    equalTo(mapOf(\"a\" to \"1\", \"b\" to \"2\", \"c\" to \"3\"))\n                )\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/SourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFile\nimport com.uchuhimo.konf.toSizeInBytes\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZonedDateTime\nimport java.util.Date\n\nobject SourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.map.kv(loadContent)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject SourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.map.kv(loadContent)\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.map.kv(config.toMap())\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject SourceReloadFromDiskSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.map.kv(loadContent)\n        val map = config.toMap()\n        val newMap = tempFile().run {\n            ObjectOutputStream(outputStream()).use {\n                it.writeObject(map)\n            }\n            ObjectInputStream(inputStream()).use {\n                @Suppress(\"UNCHECKED_CAST\")\n                it.readObject() as Map<String, Any>\n            }\n        }\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.map.kv(newMap)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject KVSourceFromDefaultProvidersSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n        }.withSource(Source.from.map.kv(loadContent))\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nprivate val loadContent = mapOf<String, Any>(\n    \"empty\" to \"null\",\n    \"literalEmpty\" to \"null\",\n    \"present\" to 1,\n\n    \"boolean\" to false,\n\n    \"int\" to 1,\n    \"short\" to 2.toShort(),\n    \"byte\" to 3.toByte(),\n    \"bigInteger\" to BigInteger.valueOf(4),\n    \"long\" to 4L,\n\n    \"double\" to 1.5,\n    \"float\" to -1.5f,\n    \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n    \"char\" to 'a',\n\n    \"string\" to \"string\",\n    \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n    \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n    \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n    \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n    \"localTime\" to LocalTime.parse(\"10:15:30\"),\n    \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n    \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n    \"year\" to Year.parse(\"2007\"),\n    \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n    \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n    \"duration\" to \"P2DT3H4M\".toDuration(),\n    \"simpleDuration\" to \"200millis\".toDuration(),\n    \"size\" to \"10k\".toSizeInBytes(),\n\n    \"enum\" to \"LABEL2\",\n\n    \"array.boolean\" to listOf(true, false),\n    \"array.byte\" to listOf<Byte>(1, 2, 3),\n    \"array.short\" to listOf<Short>(1, 2, 3),\n    \"array.int\" to listOf(1, 2, 3),\n    \"array.long\" to listOf(4L, 5L, 6L),\n    \"array.float\" to listOf(-1.0F, 0.0F, 1.0F),\n    \"array.double\" to listOf(-1.0, 0.0, 1.0),\n    \"array.char\" to listOf('a', 'b', 'c'),\n\n    \"array.object.boolean\" to listOf(true, false),\n    \"array.object.int\" to listOf(1, 2, 3),\n    \"array.object.string\" to listOf(\"one\", \"two\", \"three\"),\n    \"array.object.enum\" to listOf(\"LABEL1\", \"LABEL2\", \"LABEL3\"),\n\n    \"list\" to listOf(1, 2, 3),\n    \"mutableList\" to listOf(1, 2, 3),\n    \"listOfList\" to listOf(listOf(1, 2), listOf(3, 4)),\n    \"set\" to listOf(1, 2, 1),\n    \"sortedSet\" to listOf(2, 1, 1, 3),\n\n    \"map\" to mapOf(\n        \"a\" to 1,\n        \"b\" to 2,\n        \"c\" to 3\n    ),\n    \"intMap\" to mapOf(\n        1 to \"a\",\n        2 to \"b\",\n        3 to \"c\"\n    ),\n    \"sortedMap\" to mapOf(\n        \"c\" to 3,\n        \"b\" to 2,\n        \"a\" to 1\n    ),\n    \"listOfMap\" to listOf(\n        mapOf(\"a\" to 1, \"b\" to 2),\n        mapOf(\"a\" to 3, \"b\" to 4)\n    ),\n\n    \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1)))),\n\n    \"pair\" to mapOf(\"first\" to 1, \"second\" to 2),\n\n    \"clazz\" to mapOf(\n        \"empty\" to \"null\",\n        \"literalEmpty\" to \"null\",\n        \"present\" to 1,\n\n        \"boolean\" to false,\n\n        \"int\" to 1,\n        \"short\" to 2.toShort(),\n        \"byte\" to 3.toByte(),\n        \"bigInteger\" to BigInteger.valueOf(4),\n        \"long\" to 4L,\n\n        \"double\" to 1.5,\n        \"float\" to -1.5f,\n        \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n        \"char\" to 'a',\n\n        \"string\" to \"string\",\n        \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n        \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n        \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n        \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n        \"localTime\" to LocalTime.parse(\"10:15:30\"),\n        \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n        \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n        \"year\" to Year.parse(\"2007\"),\n        \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n        \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n        \"duration\" to \"P2DT3H4M\".toDuration(),\n        \"simpleDuration\" to \"200millis\".toDuration(),\n        \"size\" to \"10k\".toSizeInBytes(),\n\n        \"enum\" to \"LABEL2\",\n\n        \"booleanArray\" to listOf(true, false),\n\n        \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1))))\n    )\n).mapKeys { (key, _) -> \"level1.level2.$key\" }\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/SourceNodeSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.EmptyNode\nimport com.uchuhimo.konf.TreeNode\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject ListSourceNodeSpec : SubjectSpek<ListSourceNode>({\n    subject { ListSourceNode(listOf(EmptyNode, EmptyNode)) }\n    on(\"get children\") {\n        it(\"should return a map indexed by integer\") {\n            assertThat(\n                subject.children,\n                equalTo(\n                    mutableMapOf<String, TreeNode>(\n                        \"0\" to EmptyNode,\n                        \"1\" to EmptyNode\n                    )\n                )\n            )\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/SourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.absent\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.NetworkBuffer\nimport com.uchuhimo.konf.Prefix\nimport com.uchuhimo.konf.SizeInBytes\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.name\nimport com.uchuhimo.konf.source.base.ValueSource\nimport com.uchuhimo.konf.source.base.asKVSource\nimport com.uchuhimo.konf.source.base.toHierarchical\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.junit.jupiter.api.assertThrows\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Duration\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZoneOffset\nimport java.time.ZonedDateTime\nimport java.util.Date\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.test.assertFalse\nimport kotlin.test.assertTrue\n\nobject SourceSpec : Spek({\n    given(\"a source\") {\n        group(\"get operation\") {\n            val value: Source = ValueSource(Unit)\n            val tree = value.tree\n            val validPath = \"a.b\".toPath()\n            val invalidPath = \"a.c\".toPath()\n            val validKey = \"a\"\n            val invalidKey = \"b\"\n            val sourceForPath by memoized { mapOf(validPath.name to value).asKVSource() }\n            val sourceForKey by memoized { mapOf(validKey to value).asSource() }\n            on(\"find a valid path\") {\n                it(\"should contain the value\") {\n                    assertTrue(validPath in sourceForPath)\n                }\n            }\n            on(\"find an invalid path\") {\n                it(\"should not contain the value\") {\n                    assertTrue(invalidPath !in sourceForPath)\n                }\n            }\n            on(\"get by a valid path using `getOrNull`\") {\n                it(\"should return the corresponding value\") {\n                    assertThat(sourceForPath.getOrNull(validPath)?.tree, equalTo(tree))\n                }\n            }\n            on(\"get by an invalid path using `getOrNull`\") {\n                it(\"should return null\") {\n                    assertThat(sourceForPath.getOrNull(invalidPath), absent())\n                }\n            }\n\n            on(\"get by a valid path using `get`\") {\n                it(\"should return the corresponding value\") {\n                    assertThat(sourceForPath[validPath].tree, equalTo(tree))\n                }\n            }\n            on(\"get by an invalid path using `get`\") {\n                it(\"should throw NoSuchPathException\") {\n                    assertThat(\n                        { sourceForPath[invalidPath] },\n                        throws(has(NoSuchPathException::path, equalTo(invalidPath)))\n                    )\n                }\n            }\n\n            on(\"find a valid key\") {\n                it(\"should contain the value\") {\n                    assertTrue(validKey in sourceForKey)\n                }\n            }\n            on(\"find an invalid key\") {\n                it(\"should not contain the value\") {\n                    assertTrue(invalidKey !in sourceForKey)\n                }\n            }\n\n            on(\"get by a valid key using `getOrNull`\") {\n                it(\"should return the corresponding value\") {\n                    assertThat(sourceForKey.getOrNull(validKey)?.tree, equalTo(tree))\n                }\n            }\n            on(\"get by an invalid key using `getOrNull`\") {\n                it(\"should return null\") {\n                    assertThat(sourceForKey.getOrNull(invalidKey), absent())\n                }\n            }\n\n            on(\"get by a valid key using `get`\") {\n                it(\"should return the corresponding value\") {\n                    assertThat(sourceForKey[validKey].tree, equalTo(tree))\n                }\n            }\n            on(\"get by an invalid key using `get`\") {\n                it(\"should throw NoSuchPathException\") {\n                    assertThat(\n                        { sourceForKey[invalidKey] },\n                        throws(has(NoSuchPathException::path, equalTo(invalidKey.toPath())))\n                    )\n                }\n            }\n        }\n        group(\"cast operation\") {\n            on(\"cast int to long\") {\n                val source = 1.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Long>(), equalTo(1L))\n                }\n            }\n            on(\"cast short to int\") {\n                val source = 1.toShort().asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Int>(), equalTo(1))\n                }\n            }\n            on(\"cast byte to short\") {\n                val source = 1.toByte().asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Short>(), equalTo(1.toShort()))\n                }\n            }\n            on(\"cast long to BigInteger\") {\n                val source = 1L.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<BigInteger>(), equalTo(BigInteger.valueOf(1)))\n                }\n            }\n\n            on(\"cast double to BigDecimal\") {\n                val source = 1.5.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<BigDecimal>(), equalTo(BigDecimal.valueOf(1.5)))\n                }\n            }\n\n            on(\"cast long in range of int to int\") {\n                val source = 1L.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Int>(), equalTo(1))\n                }\n            }\n            on(\"cast long out of range of int to int\") {\n                it(\"should throw ParseException\") {\n                    assertThat({ Long.MAX_VALUE.asSource().asValue<Int>() }, throws<ParseException>())\n                    assertThat({ Long.MIN_VALUE.asSource().asValue<Int>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast int in range of short to short\") {\n                val source = 1.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Short>(), equalTo(1.toShort()))\n                }\n            }\n            on(\"cast int out of range of short to short\") {\n                it(\"should throw ParseException\") {\n                    assertThat({ Int.MAX_VALUE.asSource().asValue<Short>() }, throws<ParseException>())\n                    assertThat({ Int.MIN_VALUE.asSource().asValue<Short>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast short in range of byte to byte\") {\n                val source = 1.toShort().asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Byte>(), equalTo(1.toByte()))\n                }\n            }\n            on(\"cast short out of range of byte to byte\") {\n                it(\"should throw ParseException\") {\n                    assertThat({ Short.MAX_VALUE.asSource().asValue<Byte>() }, throws<ParseException>())\n                    assertThat({ Short.MIN_VALUE.asSource().asValue<Byte>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast long in range of byte to byte\") {\n                val source = 1L.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Byte>(), equalTo(1L.toByte()))\n                }\n            }\n            on(\"cast long out of range of byte to byte\") {\n                it(\"should throw ParseException\") {\n                    assertThat({ Long.MAX_VALUE.asSource().asValue<Byte>() }, throws<ParseException>())\n                    assertThat({ Long.MIN_VALUE.asSource().asValue<Byte>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast double to float\") {\n                val source = 1.5.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Float>(), equalTo(1.5f))\n                }\n            }\n\n            on(\"cast char to string\") {\n                val source = 'a'.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<String>(), equalTo(\"a\"))\n                }\n            }\n            on(\"cast string containing single char to char\") {\n                val source = \"a\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Char>(), equalTo('a'))\n                }\n            }\n            on(\"cast string containing multiple chars to char\") {\n                val source = \"ab\".asSource()\n                it(\"should throw ParseException\") {\n                    assertThat({ source.asValue<Char>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast \\\"true\\\" to Boolean\") {\n                val source = \"true\".asSource()\n                it(\"should succeed\") {\n                    assertTrue { source.asValue() }\n                }\n            }\n            on(\"cast \\\"false\\\" to Boolean\") {\n                val source = \"false\".asSource()\n                it(\"should succeed\") {\n                    assertFalse { source.asValue() }\n                }\n            }\n            on(\"cast string with invalid format to Boolean\") {\n                val source = \"yes\".asSource()\n                it(\"should throw ParseException\") {\n                    assertThat({ source.asValue<Boolean>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast string to Byte\") {\n                val source = \"1\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Byte>(), equalTo(1.toByte()))\n                }\n            }\n            on(\"cast string to Short\") {\n                val source = \"1\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Short>(), equalTo(1.toShort()))\n                }\n            }\n            on(\"cast string to Int\") {\n                val source = \"1\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Int>(), equalTo(1))\n                }\n            }\n            on(\"cast string to Long\") {\n                val source = \"1\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Long>(), equalTo(1L))\n                }\n            }\n            on(\"cast string to Float\") {\n                val source = \"1.5\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Float>(), equalTo(1.5F))\n                }\n            }\n            on(\"cast string to Double\") {\n                val source = \"1.5F\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Double>(), equalTo(1.5))\n                }\n            }\n            on(\"cast string to BigInteger\") {\n                val source = \"1\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<BigInteger>(), equalTo(1.toBigInteger()))\n                }\n            }\n            on(\"cast string to BigDecimal\") {\n                val source = \"1.5\".asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<BigDecimal>(), equalTo(1.5.toBigDecimal()))\n                }\n            }\n\n            on(\"cast string to OffsetTime\") {\n                val text = \"10:15:30+01:00\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<OffsetTime>(), equalTo(OffsetTime.parse(text)))\n                }\n            }\n            on(\"cast string with invalid format to OffsetTime\") {\n                val text = \"10:15:30\"\n                val source = text.asSource()\n                it(\"should throw ParseException\") {\n                    assertThat({ source.asValue<OffsetTime>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast string to OffsetDateTime\") {\n                val text = \"2007-12-03T10:15:30+01:00\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<OffsetDateTime>(), equalTo(OffsetDateTime.parse(text)))\n                }\n            }\n\n            on(\"cast string to ZonedDateTime\") {\n                val text = \"2007-12-03T10:15:30+01:00[Europe/Paris]\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<ZonedDateTime>(), equalTo(ZonedDateTime.parse(text)))\n                }\n            }\n\n            on(\"cast string to LocalDate\") {\n                val text = \"2007-12-03\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<LocalDate>(), equalTo(LocalDate.parse(text)))\n                }\n            }\n\n            on(\"cast string to LocalTime\") {\n                val text = \"10:15:30\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<LocalTime>(), equalTo(LocalTime.parse(text)))\n                }\n            }\n\n            on(\"cast string to LocalDateTime\") {\n                val text = \"2007-12-03T10:15:30\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<LocalDateTime>(), equalTo(LocalDateTime.parse(text)))\n                }\n            }\n\n            on(\"cast string to Year\") {\n                val text = \"2007\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Year>(), equalTo(Year.parse(text)))\n                }\n            }\n\n            on(\"cast string to YearMonth\") {\n                val text = \"2007-12\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<YearMonth>(), equalTo(YearMonth.parse(text)))\n                }\n            }\n\n            on(\"cast string to Instant\") {\n                val text = \"2007-12-03T10:15:30.00Z\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Instant>(), equalTo(Instant.parse(text)))\n                }\n            }\n\n            on(\"cast string to Date\") {\n                val text = \"2007-12-03T10:15:30.00Z\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Date>(), equalTo(Date.from(Instant.parse(text))))\n                }\n            }\n\n            on(\"cast LocalDateTime string to Date\") {\n                val text = \"2007-12-03T10:15:30\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(\n                        source.asValue<Date>(),\n                        equalTo(Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC)))\n                    )\n                }\n            }\n\n            on(\"cast LocalDate string to Date\") {\n                val text = \"2007-12-03\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(\n                        source.asValue<Date>(),\n                        equalTo(Date.from(LocalDate.parse(text).atStartOfDay().toInstant(ZoneOffset.UTC)))\n                    )\n                }\n            }\n\n            on(\"cast string to Duration\") {\n                val text = \"P2DT3H4M\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Duration>(), equalTo(Duration.parse(text)))\n                }\n            }\n\n            on(\"cast string with simple unit to Duration\") {\n                val text = \"200ms\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Duration>(), equalTo(Duration.ofMillis(200)))\n                }\n            }\n            on(\"cast string with invalid format to Duration\") {\n                val text = \"2 year\"\n                val source = text.asSource()\n                it(\"should throw ParseException\") {\n                    assertThat({ source.asValue<Duration>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast string to SizeInBytes\") {\n                val text = \"10k\"\n                val source = text.asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<SizeInBytes>().bytes, equalTo(10240L))\n                }\n            }\n            on(\"cast string with invalid format to SizeInBytes\") {\n                val text = \"10u\"\n                val source = text.asSource()\n                it(\"should throw ParseException\") {\n                    assertThat({ source.asValue<SizeInBytes>() }, throws<ParseException>())\n                }\n            }\n\n            on(\"cast set to list\") {\n                val source = setOf(1).asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<List<Int>>(), equalTo(listOf(1)))\n                }\n            }\n            on(\"cast array to list\") {\n                val source = arrayOf(1).asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<List<Int>>(), equalTo(listOf(1)))\n                }\n            }\n            on(\"cast array to set\") {\n                val source = arrayOf(1).asSource()\n                it(\"should succeed\") {\n                    assertThat(source.asValue<Set<Int>>(), equalTo(setOf(1)))\n                }\n            }\n        }\n        group(\"load operation\") {\n            on(\"load from valid source\") {\n                it(\"should load successfully\") {\n                    val config = load<Int>(1)\n                    assertThat(config(\"item\"), equalTo(1))\n                }\n            }\n            on(\"load concrete map type\") {\n                it(\"should load successfully\") {\n                    val config = load<ConcurrentHashMap<String, Int>>(mapOf(\"1\" to 1))\n                    assertThat(config<ConcurrentHashMap<String, Int>>(\"item\"), equalTo(mapOf(\"1\" to 1)))\n                }\n            }\n            on(\"load invalid enum value\") {\n                it(\"should throw LoadException caused by ParseException\") {\n                    assertCausedBy<ParseException> {\n                        load<NetworkBuffer.Type>(\"NO_HEAP\")\n                    }\n                }\n            }\n            on(\"load unsupported simple type value\") {\n                it(\"should throw LoadException caused by ObjectMappingException\") {\n                    assertCausedBy<ObjectMappingException> {\n                        load<Person>(mapOf(\"invalid\" to \"anon\"))\n                    }\n                }\n            }\n            on(\"load map with unsupported key type\") {\n                it(\"should throw LoadException caused by UnsupportedMapKeyException\") {\n                    assertCausedBy<UnsupportedMapKeyException> {\n                        load<Map<Pair<Int, Int>, String>>(mapOf((1 to 1) to \"1\"))\n                    }\n                }\n            }\n            on(\"load invalid enum value\") {\n                it(\"should throw LoadException caused by ParseException\") {\n                    assertCausedBy<ParseException> {\n                        load<NetworkBuffer.Type>(\"NO_HEAP\")\n                    }\n                }\n            }\n            on(\"load invalid POJO value\") {\n                it(\"should throw LoadException caused by ObjectMappingException\") {\n                    assertCausedBy<ObjectMappingException> {\n                        load<Person>(mapOf(\"name\" to Source()))\n                    }\n                }\n            }\n            on(\"load when SUBSTITUTE_SOURCE_WHEN_LOADED is disabled on config\") {\n                val source = mapOf(\"item\" to mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${item.key1}\")).asSource()\n                val config = Config {\n                    addSpec(\n                        object : ConfigSpec() {\n                            @Suppress(\"unused\")\n                            val item by required<Map<String, String>>()\n                        }\n                    )\n                }.disable(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)\n                    .withSource(source)\n                it(\"should not substitute path variables before loaded\") {\n                    assertThat(\n                        config<Map<String, String>>(\"item\"),\n                        equalTo(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${item.key1}\"))\n                    )\n                }\n            }\n            on(\"load when SUBSTITUTE_SOURCE_WHEN_LOADED is disabled on source\") {\n                val source = mapOf(\"item\" to mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${item.key1}\")).asSource()\n                    .disabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED)\n                val config = Config {\n                    addSpec(\n                        object : ConfigSpec() {\n                            @Suppress(\"unused\")\n                            val item by required<Map<String, String>>()\n                        }\n                    )\n                }.withSource(source)\n                it(\"should substitute path variables before loaded\") {\n                    assertThat(\n                        config<Map<String, String>>(\"item\"),\n                        equalTo(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${item.key1}\"))\n                    )\n                }\n            }\n            on(\"load when SUBSTITUTE_SOURCE_WHEN_LOADED is enabled\") {\n                val source = mapOf(\"item\" to mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${item.key1}\")).asSource()\n                val config = Config {\n                    addSpec(\n                        object : ConfigSpec() {\n                            @Suppress(\"unused\")\n                            val item by required<Map<String, String>>()\n                        }\n                    )\n                }.withSource(source)\n                it(\"should substitute path variables\") {\n                    assertTrue { Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED.enabledByDefault }\n                    assertThat(\n                        config<Map<String, String>>(\"item\"),\n                        equalTo(mapOf(\"key1\" to \"a\", \"key2\" to \"ba\"))\n                    )\n                }\n            }\n        }\n        group(\"substitution operation\") {\n            on(\"doesn't contain any path variable\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\")\n                val source = map.asSource().substituted()\n                it(\"should keep it unchanged\") {\n                    assertThat(source.tree.toHierarchical(), equalTo<Any>(map))\n                }\n            }\n            on(\"contains single path variable\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${key1}\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"ba\"))\n                    )\n                }\n            }\n            on(\"contains integer path variable\") {\n                val map = mapOf(\"key1\" to 1, \"key2\" to \"b\\${key1}\", \"key3\" to \"\\${key1}\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to 1, \"key2\" to \"b1\", \"key3\" to 1))\n                    )\n                }\n            }\n            on(\"contains path variables with string list value\") {\n                val map = mapOf(\"key1\" to \"a,b,c\", \"key2\" to \"a\\${key1}\")\n                val source = Source.from.map.flat(map).substituted().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(\n                            mapOf(\n                                \"key1\" to \"a,b,c\",\n                                \"key2\" to \"aa,b,c\"\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"contains path variables in list\") {\n                val map = mapOf(\"top\" to listOf(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${top.0.key1}\")))\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"top\" to listOf(mapOf(\"key1\" to \"a\", \"key2\" to \"ba\"))))\n                    )\n                }\n            }\n            on(\"contains path variable with wrong type\") {\n                val map = mapOf(\"key1\" to 1.0, \"key2\" to \"b\\${key1}\")\n                it(\"should throw WrongTypeException\") {\n                    assertThrows<WrongTypeException> { map.asSource().substituted() }\n                }\n            }\n            on(\"contains escaped path variables\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\\$\\${key1}\")\n                val source = map.asSource().substituted()\n                it(\"should not substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${key1}\"))\n                    )\n                }\n            }\n            on(\"contains nested escaped path variables\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\\$\\$\\$\\${key1}\")\n                val source = map.asSource().substituted()\n                it(\"should escaped only once\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\$\\$\\${key1}\"))\n                    )\n                }\n            }\n            on(\"contains nested escaped path variables and substitute multiple times\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\\$\\$\\$\\${key1}\")\n                val source = map.asSource().substituted().substituted()\n                it(\"should escaped only once\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"b\\$\\$\\${key1}\"))\n                    )\n                }\n            }\n            on(\"contains undefined path variable\") {\n                val map = mapOf(\"key2\" to \"b\\${key1}\")\n                it(\"should throw UndefinedPathVariableException by default\") {\n                    assertThat(\n                        { map.asSource().substituted() },\n                        throws(has(UndefinedPathVariableException::text, equalTo(\"b\\${key1}\")))\n                    )\n                }\n                it(\"should keep unsubstituted when errorWhenUndefined is `false`\") {\n                    val source = map.asSource().substituted(errorWhenUndefined = false)\n                    assertThat(source.tree.toHierarchical(), equalTo<Any>(map))\n                }\n            }\n            on(\"contains undefined path variable in reference format\") {\n                val map = mapOf(\"key2\" to \"\\${key1}\")\n                it(\"should throw UndefinedPathVariableException by default\") {\n                    assertThat(\n                        { map.asSource().substituted() },\n                        throws(has(UndefinedPathVariableException::text, equalTo(\"\\${key1}\")))\n                    )\n                }\n                it(\"should keep unsubstituted when errorWhenUndefined is `false`\") {\n                    val source = map.asSource().substituted(errorWhenUndefined = false)\n                    assertThat(source.tree.toHierarchical(), equalTo<Any>(map))\n                }\n            }\n            on(\"contains multiple path variables\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"\\${key1}b\\${key3}\", \"key3\" to \"c\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"abc\", \"key3\" to \"c\"))\n                    )\n                }\n            }\n            on(\"contains chained path variables\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"\\${key1}b\", \"key3\" to \"\\${key2}c\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"ab\", \"key3\" to \"abc\"))\n                    )\n                }\n            }\n            on(\"contains nested path variables\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"\\${\\${key3}}b\", \"key3\" to \"key1\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"ab\", \"key3\" to \"key1\"))\n                    )\n                }\n            }\n            on(\"contains a path variable with default value\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"b\\${key3:-c}\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"bc\"))\n                    )\n                }\n            }\n            on(\"contains a path variable with key\") {\n                val map = mapOf(\"key1\" to \"a\", \"key2\" to \"\\${key1}\\${base64Decoder:SGVsbG9Xb3JsZCE=}\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"aHelloWorld!\"))\n                    )\n                }\n            }\n            on(\"contains a path variable in reference format\") {\n                val map = mapOf(\"key1\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\"), \"key2\" to \"\\${key1}\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(\n                            mapOf(\n                                \"key1\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\"),\n                                \"key2\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\")\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"contains nested path variable in reference format\") {\n                val map = mapOf(\"key1\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\"), \"key2\" to \"\\${\\${key3}}\", \"key3\" to \"key1\")\n                val source = map.asSource().substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(\n                            mapOf(\n                                \"key1\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\"),\n                                \"key2\" to mapOf(\"key3\" to \"a\", \"key4\" to \"b\"),\n                                \"key3\" to \"key1\"\n                            )\n                        )\n                    )\n                }\n            }\n            on(\"contains path variable in different sources\") {\n                val map1 = mapOf(\"key1\" to \"a\")\n                val map2 = mapOf(\"key2\" to \"b\\${key1}\")\n                val source = (map2.asSource() + map1.asSource()).substituted()\n                it(\"should substitute path variables\") {\n                    assertThat(\n                        source.tree.toHierarchical(),\n                        equalTo<Any>(mapOf(\"key1\" to \"a\", \"key2\" to \"ba\"))\n                    )\n                }\n            }\n        }\n        group(\"feature operation\") {\n            on(\"enable feature\") {\n                val source = Source().enabled(Feature.FAIL_ON_UNKNOWN_PATH)\n                it(\"should let the feature be enabled\") {\n                    assertTrue { source.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"disable feature\") {\n                val source = Source().disabled(Feature.FAIL_ON_UNKNOWN_PATH)\n                it(\"should let the feature be disabled\") {\n                    assertFalse { source.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"enable feature before transforming source\") {\n                val source = Source().enabled(Feature.FAIL_ON_UNKNOWN_PATH).withPrefix(\"prefix\")\n                it(\"should let the feature be enabled\") {\n                    assertTrue { source.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"disable feature before transforming source\") {\n                val source = Source().disabled(Feature.FAIL_ON_UNKNOWN_PATH).withPrefix(\"prefix\")\n                it(\"should let the feature be disabled\") {\n                    assertFalse { source.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH) }\n                }\n            }\n            on(\"by default\") {\n                val source = Source()\n                it(\"should use the feature's default setting\") {\n                    assertThat(\n                        source.isEnabled(Feature.FAIL_ON_UNKNOWN_PATH),\n                        equalTo(Feature.FAIL_ON_UNKNOWN_PATH.enabledByDefault)\n                    )\n                }\n            }\n        }\n        group(\"source with prefix\") {\n            val source by memoized {\n                Prefix(\"level1.level2\") + mapOf(\"key\" to \"value\").asSource()\n            }\n            on(\"prefix is empty\") {\n                it(\"should return itself\") {\n                    assertThat(source.withPrefix(\"\"), sameInstance(source))\n                }\n            }\n            on(\"find a valid path\") {\n                it(\"should contain the value\") {\n                    assertTrue(\"level1\" in source)\n                    assertTrue(\"level1.level2\" in source)\n                    assertTrue(\"level1.level2.key\" in source)\n                }\n            }\n            on(\"find an invalid path\") {\n                it(\"should not contain the value\") {\n                    assertTrue(\"level3\" !in source)\n                    assertTrue(\"level1.level3\" !in source)\n                    assertTrue(\"level1.level2.level3\" !in source)\n                    assertTrue(\"level1.level3.level2\" !in source)\n                }\n            }\n\n            on(\"get by a valid path using `getOrNull`\") {\n                it(\"should return the corresponding value\") {\n                    assertThat(\n                        (source.getOrNull(\"level1\")?.get(\"level2.key\")?.tree as ValueNode).value as String,\n                        equalTo(\"value\")\n                    )\n                    assertThat(\n                        (source.getOrNull(\"level1.level2\")?.get(\"key\")?.tree as ValueNode).value as String,\n                        equalTo(\"value\")\n                    )\n                    assertThat(\n                        (source.getOrNull(\"level1.level2.key\")?.tree as ValueNode).value as String,\n                        equalTo(\"value\")\n                    )\n                }\n            }\n            on(\"get by an invalid path using `getOrNull`\") {\n                it(\"should return null\") {\n                    assertThat(source.getOrNull(\"level3\"), absent())\n                    assertThat(source.getOrNull(\"level1.level3\"), absent())\n                    assertThat(source.getOrNull(\"level1.level2.level3\"), absent())\n                    assertThat(source.getOrNull(\"level1.level3.level2\"), absent())\n                }\n            }\n        }\n    }\n})\n\nprivate inline fun <reified T : Any> load(value: Any): Config =\n    Config().apply {\n        addSpec(\n            object : ConfigSpec() {\n                @Suppress(\"unused\")\n                val item by required<T>()\n            }\n        )\n    }.withSource(mapOf(\"item\" to value).asSource())\n\nprivate data class Person(val name: String)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/WriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.properties.toProperties\nimport com.uchuhimo.konf.tempFile\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\nimport java.nio.charset.Charset\nimport kotlin.test.assertTrue\n\nobject WriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toProperties\n    }\n\n    given(\"a writer\") {\n        val expectedString = \"key=value\" + System.lineSeparator()\n        on(\"save to string\") {\n            val string = subject.toText()\n            it(\"should return a string which contains content from config\") {\n                assertThat(string, equalTo(expectedString))\n            }\n        }\n        on(\"save to byte array\") {\n            val byteArray = subject.toBytes()\n            it(\"should return a byte array which contains content from config\") {\n                assertThat(byteArray.toString(Charset.defaultCharset()), equalTo(expectedString))\n            }\n        }\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to file\") {\n            val file = tempFile()\n            subject.toFile(file)\n            it(\"should return a file which contains content from config\") {\n                assertThat(file.readText(), equalTo(expectedString))\n            }\n            it(\"should not lock the file\") {\n                assertTrue { file.delete() }\n            }\n        }\n        on(\"save to file by path\") {\n            val file = tempFile()\n            val path = file.toString()\n            subject.toFile(path)\n            it(\"should return a file which contains content from config\") {\n                assertThat(file.readText(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/FlatSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.Source\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject FlatSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.map.flat(loadContent)\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n\nobject FlatSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.map.flat(loadContent)\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.map.flat(config.toFlatMap())\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n\nobject FlatSourceFromDefaultProvidersSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.withSource(Source.from.map.flat(loadContent))\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n\nprivate val loadContent = mapOf(\n    \"empty\" to \"null\",\n    \"literalEmpty\" to \"null\",\n    \"present\" to \"1\",\n    \"boolean\" to \"false\",\n    \"int\" to \"1\",\n    \"short\" to \"2\",\n    \"byte\" to \"3\",\n    \"bigInteger\" to \"4\",\n    \"long\" to \"4\",\n    \"double\" to \"1.5\",\n    \"float\" to \"-1.5\",\n    \"bigDecimal\" to \"1.5\",\n    \"char\" to \"a\",\n    \"string\" to \"string\",\n    \"offsetTime\" to \"10:15:30+01:00\",\n    \"offsetDateTime\" to \"2007-12-03T10:15:30+01:00\",\n    \"zonedDateTime\" to \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n    \"localDate\" to \"2007-12-03\",\n    \"localTime\" to \"10:15:30\",\n    \"localDateTime\" to \"2007-12-03T10:15:30\",\n    \"date\" to \"2007-12-03T10:15:30Z\",\n    \"year\" to \"2007\",\n    \"yearMonth\" to \"2007-12\",\n    \"instant\" to \"2007-12-03T10:15:30.00Z\",\n    \"duration\" to \"P2DT3H4M\",\n    \"simpleDuration\" to \"200millis\",\n    \"size\" to \"10k\",\n    \"enum\" to \"LABEL2\",\n    \"list\" to \"1,2,3\",\n    \"mutableList\" to \"1,2,3\",\n    \"listOfList.0\" to \"1,2\",\n    \"listOfList.1\" to \"3,4\",\n    \"set\" to \"1,2,1\",\n    \"sortedSet\" to \"2,1,1,3\",\n    \"map.a\" to \"1\",\n    \"map.b\" to \"2\",\n    \"map.c\" to \"3\",\n    \"intMap.1\" to \"a\",\n    \"intMap.2\" to \"b\",\n    \"intMap.3\" to \"c\",\n    \"sortedMap.c\" to \"3\",\n    \"sortedMap.b\" to \"2\",\n    \"sortedMap.a\" to \"1\",\n    \"nested.0.0.0.a\" to \"1\",\n    \"listOfMap.0.a\" to \"1\",\n    \"listOfMap.0.b\" to \"2\",\n    \"listOfMap.1.a\" to \"3\",\n    \"listOfMap.1.b\" to \"4\",\n    \"array.boolean\" to \"true,false\",\n    \"array.byte\" to \"1,2,3\",\n    \"array.short\" to \"1,2,3\",\n    \"array.int\" to \"1,2,3\",\n    \"array.long\" to \"4,5,6\",\n    \"array.float\" to \"-1, 0.0, 1\",\n    \"array.double\" to \"-1, 0.0, 1\",\n    \"array.char\" to \"a,b,c\",\n    \"array.object.boolean\" to \"true,false\",\n    \"array.object.int\" to \"1,2,3\",\n    \"array.object.string\" to \"one,two,three\",\n    \"array.object.enum\" to \"LABEL1,LABEL2,LABEL3\",\n    \"pair.first\" to \"1\",\n    \"pair.second\" to \"2\",\n    \"clazz.empty\" to \"null\",\n    \"clazz.literalEmpty\" to \"null\",\n    \"clazz.present\" to \"1\",\n    \"clazz.boolean\" to \"false\",\n    \"clazz.int\" to \"1\",\n    \"clazz.short\" to \"2\",\n    \"clazz.byte\" to \"3\",\n    \"clazz.bigInteger\" to \"4\",\n    \"clazz.long\" to \"4\",\n    \"clazz.double\" to \"1.5\",\n    \"clazz.float\" to \"-1.5\",\n    \"clazz.bigDecimal\" to \"1.5\",\n    \"clazz.char\" to \"a\",\n    \"clazz.string\" to \"string\",\n    \"clazz.offsetTime\" to \"10:15:30+01:00\",\n    \"clazz.offsetDateTime\" to \"2007-12-03T10:15:30+01:00\",\n    \"clazz.zonedDateTime\" to \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n    \"clazz.localDate\" to \"2007-12-03\",\n    \"clazz.localTime\" to \"10:15:30\",\n    \"clazz.localDateTime\" to \"2007-12-03T10:15:30\",\n    \"clazz.date\" to \"2007-12-03T10:15:30Z\",\n    \"clazz.year\" to \"2007\",\n    \"clazz.yearMonth\" to \"2007-12\",\n    \"clazz.instant\" to \"2007-12-03T10:15:30.00Z\",\n    \"clazz.duration\" to \"P2DT3H4M\",\n    \"clazz.simpleDuration\" to \"200millis\",\n    \"clazz.size\" to \"10k\",\n    \"clazz.enum\" to \"LABEL2\",\n    \"clazz.booleanArray\" to \"true,false\",\n    \"clazz.nested.0.0.0.a\" to \"1\",\n    \"emptyList\" to \"\",\n    \"emptySet\" to \"\",\n    \"emptyArray\" to \"\",\n    \"emptyObjectArray\" to \"\",\n    \"singleElementList\" to \"1\",\n    \"multipleElementsList\" to \"1,2\",\n    \"flatClass.stringWithComma\" to \"string,with,comma\",\n    \"flatClass.emptyList\" to \"\",\n    \"flatClass.emptySet\" to \"\",\n    \"flatClass.emptyArray\" to \"\",\n    \"flatClass.emptyObjectArray\" to \"\",\n    \"flatClass.singleElementList\" to \"1\",\n    \"flatClass.multipleElementsList\" to \"1,2\"\n).mapKeys { (key, _) -> \"level1.level2.$key\" }\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/FlatSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.InvalidPathException\nimport com.uchuhimo.konf.ListNode\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.source.ParseException\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.junit.jupiter.api.assertThrows\nimport kotlin.test.assertTrue\n\nobject FlatSourceSpec : SubjectSpek<FlatSource>({\n    given(\"a flat map source\") {\n        group(\"get operation\") {\n            val source by memoized {\n                FlatSource(map = mapOf(\"level1.level2.key\" to \"value\"))\n            }\n            on(\"get the underlying map\") {\n                it(\"should return the specified map\") {\n                    assertThat(source.map, equalTo(mapOf(\"level1.level2.key\" to \"value\")))\n                }\n            }\n            on(\"access with empty path\") {\n                it(\"should contain the path\") {\n                    assertTrue(\"\".toPath() in source)\n                }\n                it(\"should return itself in `getOrNull`\") {\n                    assertThat(source.getOrNull(\"\".toPath()), equalTo(source as Source))\n                }\n            }\n        }\n        group(\"get operation for list value\") {\n            val source by memoized {\n                FlatSource(\n                    map = mapOf(\n                        \"empty\" to \"\",\n                        \"single\" to \"a\",\n                        \"multiple\" to \"a,b\"\n                    )\n                )\n            }\n            on(\"empty string value\") {\n                it(\"should return an empty list\") {\n                    assertThat((source[\"empty\"].tree as ListNode).list, equalTo(listOf()))\n                }\n            }\n            on(\"string value without commas\") {\n                it(\"should return a list containing a single element\") {\n                    assertThat(\n                        (source[\"single\"].tree as ListNode).list.map { (it as ValueNode).value as String },\n                        equalTo(listOf(\"a\"))\n                    )\n                }\n            }\n            on(\"string value with commas\") {\n                it(\"should return a list containing multiple elements\") {\n                    assertThat(\n                        (source[\"multiple\"].tree as ListNode).list.map { (it as ValueNode).value as String },\n                        equalTo(listOf(\"a\", \"b\"))\n                    )\n                }\n            }\n        }\n        on(\"contain invalid key\") {\n            it(\"should throw InvalidPathException\") {\n                assertThrows<InvalidPathException> {\n                    FlatSource(map = mapOf(\"level1.level2.key.\" to \"value\"))\n                }\n            }\n        }\n        group(\"cast operation\") {\n            val source by memoized {\n                FlatSource(map = mapOf(\"level1.key\" to \"value\"))[\"level1.key\"]\n            }\n            on(\"value is a string\") {\n                it(\"should succeed to cast to string\") {\n                    assertThat(source.asValue<String>(), equalTo(\"value\"))\n                }\n            }\n            on(\"value is not a boolean\") {\n                it(\"should throw ParseException when casting to boolean\") {\n                    assertThat({ source.asValue<Boolean>() }, throws<ParseException>())\n                }\n            }\n            on(\"value is not a double\") {\n                it(\"should throw ParseException when casting to double\") {\n                    assertThat({ source.asValue<Double>() }, throws<ParseException>())\n                }\n            }\n            on(\"value is not an integer\") {\n                it(\"should throw ParseException when casting to integer\") {\n                    assertThat({ source.asValue<Int>() }, throws<ParseException>())\n                }\n            }\n            on(\"value is not a long\") {\n                it(\"should throw ParseException when casting to long\") {\n                    assertThat({ source.asValue<Long>() }, throws<ParseException>())\n                }\n            }\n        }\n    }\n    given(\"a config that contains list of strings with commas\") {\n        val spec = object : ConfigSpec() {\n            @Suppress(\"unused\")\n            val list by optional(listOf(\"a,b\", \"c, d\"))\n        }\n        val config = Config {\n            addSpec(spec)\n        }\n        val map = config.toFlatMap()\n        it(\"should not be joined into a string\") {\n            assertThat(map[\"list.0\"], equalTo(\"a,b\"))\n            assertThat(map[\"list.1\"], equalTo(\"c, d\"))\n        }\n    }\n    given(\"a config that contains list of strings without commas\") {\n        val spec = object : ConfigSpec() {\n            @Suppress(\"unused\")\n            val list by optional(listOf(\"a\", \"b\", \"c\", \"d\"))\n        }\n        val config = Config {\n            addSpec(spec)\n        }\n        val map = config.toFlatMap()\n        it(\"should be joined into a string with commas\") {\n            assertThat(map[\"list\"], equalTo(\"a,b,c,d\"))\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/KVSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport kotlin.test.assertFalse\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject KVSourceSpec : SubjectSpek<KVSource>({\n    subject { KVSource(map = mapOf(\"1\" to 1)) }\n\n    given(\"a KV source\") {\n        on(\"get the underlying map\") {\n            it(\"should return the specified map\") {\n                assertThat(subject.map, equalTo(mapOf(\"1\" to 1 as Any)))\n            }\n        }\n        on(\"get an existed key\") {\n            it(\"should contain the key\") {\n                assertTrue(\"1\".toPath() in subject)\n            }\n            it(\"should contain the corresponding value\") {\n                assertThat(subject.getOrNull(\"1\".toPath())?.asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"get an non-existed key\") {\n            it(\"should not contain the key\") {\n                assertFalse(\"2\".toPath() in subject)\n            }\n            it(\"should not contain the corresponding value\") {\n                assertNull(subject.getOrNull(\"2\".toPath()))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/MapSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport com.uchuhimo.konf.source.toDuration\nimport com.uchuhimo.konf.toSizeInBytes\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZonedDateTime\nimport java.util.Date\n\nobject MapSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.map.hierarchical(loadContent)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject MapSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.map.hierarchical(loadContent)\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.map.hierarchical(config.toHierarchicalMap())\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject MapSourceFromDefaultProvidersSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.withSource(Source.from.map.hierarchical(loadContent))\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nprivate val loadContent = mapOf(\n    \"level1\" to mapOf(\n        \"level2\" to\n            mapOf<String, Any>(\n                \"empty\" to \"null\",\n                \"literalEmpty\" to \"null\",\n                \"present\" to 1,\n\n                \"boolean\" to false,\n\n                \"int\" to 1,\n                \"short\" to 2.toShort(),\n                \"byte\" to 3.toByte(),\n                \"bigInteger\" to BigInteger.valueOf(4),\n                \"long\" to 4L,\n\n                \"double\" to 1.5,\n                \"float\" to -1.5f,\n                \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n                \"char\" to 'a',\n\n                \"string\" to \"string\",\n                \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n                \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n                \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n                \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n                \"localTime\" to LocalTime.parse(\"10:15:30\"),\n                \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n                \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n                \"year\" to Year.parse(\"2007\"),\n                \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n                \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n                \"duration\" to \"P2DT3H4M\".toDuration(),\n                \"simpleDuration\" to \"200millis\".toDuration(),\n                \"size\" to \"10k\".toSizeInBytes(),\n\n                \"enum\" to \"LABEL2\",\n\n                \"array\" to mapOf(\n                    \"boolean\" to listOf(true, false),\n                    \"byte\" to listOf<Byte>(1, 2, 3),\n                    \"short\" to listOf<Short>(1, 2, 3),\n                    \"int\" to listOf(1, 2, 3),\n                    \"long\" to listOf(4L, 5L, 6L),\n                    \"float\" to listOf(-1.0F, 0.0F, 1.0F),\n                    \"double\" to listOf(-1.0, 0.0, 1.0),\n                    \"char\" to listOf('a', 'b', 'c'),\n\n                    \"object\" to mapOf(\n                        \"boolean\" to listOf(true, false),\n                        \"int\" to listOf(1, 2, 3),\n                        \"string\" to listOf(\"one\", \"two\", \"three\"),\n                        \"enum\" to listOf(\"LABEL1\", \"LABEL2\", \"LABEL3\")\n                    )\n                ),\n\n                \"list\" to listOf(1, 2, 3),\n                \"mutableList\" to listOf(1, 2, 3),\n                \"listOfList\" to listOf(listOf(1, 2), listOf(3, 4)),\n                \"set\" to listOf(1, 2, 1),\n                \"sortedSet\" to listOf(2, 1, 1, 3),\n\n                \"map\" to mapOf(\n                    \"a\" to 1,\n                    \"b\" to 2,\n                    \"c\" to 3\n                ),\n                \"intMap\" to mapOf(\n                    1 to \"a\",\n                    2 to \"b\",\n                    3 to \"c\"\n                ),\n                \"sortedMap\" to mapOf(\n                    \"c\" to 3,\n                    \"b\" to 2,\n                    \"a\" to 1\n                ),\n                \"listOfMap\" to listOf(\n                    mapOf(\"a\" to 1, \"b\" to 2),\n                    mapOf(\"a\" to 3, \"b\" to 4)\n                ),\n\n                \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1)))),\n\n                \"pair\" to mapOf(\"first\" to 1, \"second\" to 2),\n\n                \"clazz\" to mapOf(\n                    \"empty\" to \"null\",\n                    \"literalEmpty\" to \"null\",\n                    \"present\" to 1,\n\n                    \"boolean\" to false,\n\n                    \"int\" to 1,\n                    \"short\" to 2.toShort(),\n                    \"byte\" to 3.toByte(),\n                    \"bigInteger\" to BigInteger.valueOf(4),\n                    \"long\" to 4L,\n\n                    \"double\" to 1.5,\n                    \"float\" to -1.5f,\n                    \"bigDecimal\" to BigDecimal.valueOf(1.5),\n\n                    \"char\" to 'a',\n\n                    \"string\" to \"string\",\n                    \"offsetTime\" to OffsetTime.parse(\"10:15:30+01:00\"),\n                    \"offsetDateTime\" to OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n                    \"zonedDateTime\" to ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n                    \"localDate\" to LocalDate.parse(\"2007-12-03\"),\n                    \"localTime\" to LocalTime.parse(\"10:15:30\"),\n                    \"localDateTime\" to LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n                    \"date\" to Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n                    \"year\" to Year.parse(\"2007\"),\n                    \"yearMonth\" to YearMonth.parse(\"2007-12\"),\n                    \"instant\" to Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n                    \"duration\" to \"P2DT3H4M\".toDuration(),\n                    \"simpleDuration\" to \"200millis\".toDuration(),\n                    \"size\" to \"10k\".toSizeInBytes(),\n\n                    \"enum\" to \"LABEL2\",\n\n                    \"booleanArray\" to listOf(true, false),\n\n                    \"nested\" to listOf(listOf(listOf(mapOf(\"a\" to 1))))\n                )\n            )\n    )\n)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/MapSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.ValueNode\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport kotlin.test.assertFalse\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject MapSourceSpec : SubjectSpek<MapSource>({\n    subject { MapSource(map = mapOf(\"1\" to 1)) }\n\n    given(\"a map source\") {\n        on(\"get the underlying map\") {\n            it(\"should return the specified map\") {\n                assertThat(subject.map, equalTo(mapOf(\"1\" to 1 as Any)))\n            }\n        }\n        on(\"cast to map\") {\n            it(\"should succeed\") {\n                val map = subject.tree.children\n                assertThat((map[\"1\"] as ValueNode).value, equalTo(1 as Any))\n            }\n        }\n        on(\"get an existed key\") {\n            it(\"should contain the key\") {\n                assertTrue(\"1\".toPath() in subject)\n            }\n            it(\"should contain the corresponding value\") {\n                assertThat(subject.getOrNull(\"1\".toPath())?.asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"get an non-existed key\") {\n            it(\"should not contain the key\") {\n                assertFalse(\"2\".toPath() in subject)\n            }\n            it(\"should not contain the corresponding value\") {\n                assertNull(subject.getOrNull(\"2\".toPath()))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/base/ValueSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.NoSuchPathException\nimport com.uchuhimo.konf.source.asSource\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject ValueSourceSpec : Spek({\n    given(\"a value source\") {\n        on(\"get with non-empty path\") {\n            it(\"should throw NoSuchPathException\") {\n                assertThat({ 1.asSource()[\"a\"] }, throws<NoSuchPathException>())\n            }\n        }\n        on(\"invoke `asSource`\") {\n            val source = 1.asSource()\n            it(\"should return itself\") {\n                assertThat(source.asSource(), sameInstance(source))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/deserializer/DurationDeserializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.ObjectMappingException\nimport com.uchuhimo.konf.source.assertCausedBy\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.time.Duration\n\nobject DurationDeserializerSpec : Spek({\n    val spec = object : ConfigSpec() {\n        val item by required<DurationWrapper>()\n    }\n    val config by memoized {\n        Config {\n            addSpec(spec)\n        }\n    }\n\n    given(\"a duration deserializer\") {\n        on(\"deserialize valid string\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"duration\" to \"P2DT3H4M\"))).apply {\n                it(\"should succeed\") {\n                    assertThat(\n                        this@apply[spec.item].duration,\n                        equalTo(Duration.parse(\"P2DT3H4M\"))\n                    )\n                }\n            }\n        }\n        on(\"deserialize empty string\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"duration\" to \"  \")))\n                }\n            }\n        }\n        on(\"deserialize value with invalid type\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"duration\" to 1)))\n                }\n            }\n        }\n        on(\"deserialize value with invalid format\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"duration\" to \"*1s\")))\n                }\n            }\n        }\n    }\n})\n\nprivate data class DurationWrapper(val duration: Duration)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/deserializer/OffsetDateTimeDeserializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.ObjectMappingException\nimport com.uchuhimo.konf.source.assertCausedBy\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.time.OffsetDateTime\n\nobject OffsetDateTimeDeserializerSpec : Spek({\n    val spec = object : ConfigSpec() {\n        val item by required<OffsetDateTimeWrapper>()\n    }\n    val config by memoized {\n        Config {\n            addSpec(spec)\n        }\n    }\n\n    given(\"an OffsetDateTime deserializer\") {\n        on(\"deserialize valid string\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"offsetDateTime\" to \"2007-12-03T10:15:30+01:00\"))).apply {\n                it(\"should succeed\") {\n                    assertThat(\n                        this@apply[spec.item].offsetDateTime,\n                        equalTo(OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"))\n                    )\n                }\n            }\n        }\n        on(\"deserialize empty string\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"offsetDateTime\" to \"  \")))\n                }\n            }\n        }\n        on(\"deserialize value with invalid type\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"offsetDateTime\" to 1)))\n                }\n            }\n        }\n        on(\"deserialize value with invalid format\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"offsetDateTime\" to \"2007-12-03T10:15:30\")))\n                }\n            }\n        }\n    }\n})\n\nprivate data class OffsetDateTimeWrapper(val offsetDateTime: OffsetDateTime)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/deserializer/StringDeserializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.ObjectMappingException\nimport com.uchuhimo.konf.source.assertCausedBy\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject StringDeserializerSpec : Spek({\n    val spec = object : ConfigSpec() {\n        val item by required<StringWrapper>()\n    }\n    val config by memoized {\n        Config {\n            addSpec(spec)\n        }\n    }\n\n    given(\"a string deserializer\") {\n        on(\"deserialize string containing commas\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to \"a,b,c\"))).apply {\n                it(\"should succeed\") {\n                    assertThat(this@apply[spec.item].string, equalTo(\"a,b,c\"))\n                }\n            }\n        }\n        on(\"deserialize string containing commas when UNWRAP_SINGLE_VALUE_ARRAYS is enable\") {\n            config.apply {\n                mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)\n            }.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to \"a,b,c\"))).apply {\n                it(\"should succeed\") {\n                    assertThat(this@apply[spec.item].string, equalTo(\"a,b,c\"))\n                }\n            }\n        }\n        on(\"deserialize string from number\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to 1))).apply {\n                it(\"should succeed\") {\n                    assertThat(this@apply[spec.item].string, equalTo(\"1\"))\n                }\n            }\n        }\n        on(\"deserialize string from list of numbers\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to listOf(1, 2)))).apply {\n                it(\"should succeed\") {\n                    assertThat(this@apply[spec.item].string, equalTo(\"1,2\"))\n                }\n            }\n        }\n        on(\"deserialize string from single value array\") {\n            config.apply {\n                mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)\n            }.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to listOf(\"a\")))).apply {\n                it(\"should succeed\") {\n                    assertThat(this@apply[spec.item].string, equalTo(\"a\"))\n                }\n            }\n        }\n        on(\"deserialize string from empty array\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.apply {\n                        mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)\n                    }.from.map.kv(mapOf(\"item\" to mapOf(\"string\" to listOf<String>())))\n                }\n            }\n        }\n    }\n})\n\nprivate data class StringWrapper(val string: String)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/deserializer/ZonedDateTimeDeserializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.deserializer\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.ObjectMappingException\nimport com.uchuhimo.konf.source.assertCausedBy\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.time.ZonedDateTime\n\nobject ZonedDateTimeDeserializerSpec : Spek({\n    val spec = object : ConfigSpec() {\n        val item by required<ZonedDateTimeWrapper>()\n    }\n    val config by memoized {\n        Config {\n            addSpec(spec)\n        }\n    }\n\n    given(\"an ZonedDateTime deserializer\") {\n        on(\"deserialize valid string\") {\n            config.from.map.kv(mapOf(\"item\" to mapOf(\"zonedDateTime\" to \"2007-12-03T10:15:30+01:00[Europe/Paris]\"))).apply {\n                it(\"should succeed\") {\n                    assertThat(\n                        this@apply[spec.item].zonedDateTime,\n                        equalTo(ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"))\n                    )\n                }\n            }\n        }\n        on(\"deserialize empty string\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"zonedDateTime\" to \"  \")))\n                }\n            }\n        }\n        on(\"deserialize value with invalid type\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"zonedDateTime\" to 1)))\n                }\n            }\n        }\n        on(\"deserialize value with invalid format\") {\n            it(\"should throw LoadException caused by ObjectMappingException\") {\n                assertCausedBy<ObjectMappingException> {\n                    config.from.map.kv(mapOf(\"item\" to mapOf(\"zonedDateTime\" to \"2007-12-03T10:15:30\")))\n                }\n            }\n        }\n    }\n})\n\nprivate data class ZonedDateTimeWrapper(val zonedDateTime: ZonedDateTime)\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/env/EnvProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.env\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport kotlin.test.assertTrue\n\nobject EnvProviderSpec : SubjectSpek<EnvProvider>({\n    subject { EnvProvider }\n\n    given(\"a source provider\") {\n        on(\"create source from system environment\") {\n            val source = subject.env()\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"system-environment\"))\n            }\n            it(\"should return a source which contains value from system environment\") {\n                val config = Config { addSpec(SourceSpec) }.withSource(source)\n                assertThat(config[SourceSpec.Test.type], equalTo(\"env\"))\n                assertTrue { config[SourceSpec.camelCase] }\n            }\n            it(\"should return a case-insensitive source\") {\n                val config = Config().withSource(source).apply { addSpec(SourceSpec) }\n                assertThat(config[SourceSpec.Test.type], equalTo(\"env\"))\n                assertTrue { config[SourceSpec.camelCase] }\n            }\n        }\n        on(\"create flatten source from system environment\") {\n            val source = subject.env(nested = false)\n            it(\"should return a source which contains value from system environment\") {\n                val config = Config { addSpec(FlattenSourceSpec) }.withSource(source)\n                assertThat(config[FlattenSourceSpec.SOURCE_TEST_TYPE], equalTo(\"env\"))\n                assertTrue { config[FlattenSourceSpec.SOURCE_CAMELCASE] }\n            }\n        }\n    }\n})\n\nobject EnvProviderInJavaSpec : SubjectSpek<EnvProvider>({\n    subject { EnvProvider.get() }\n\n    itBehavesLike(EnvProviderSpec)\n})\n\nobject SourceSpec : ConfigSpec() {\n    object Test : ConfigSpec() {\n        val type by required<String>()\n    }\n\n    val camelCase by required<Boolean>()\n}\n\nobject FlattenSourceSpec : ConfigSpec(\"\") {\n    val SOURCE_CAMELCASE by required<Boolean>()\n    val SOURCE_TEST_TYPE by required<String>()\n}\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/env/env.properties",
    "content": "#\n# Copyright 2017-2019 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nSOURCE_TEST_TYPE=env\nSOURCE_CAMELCASE=true"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/json/JsonProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject JsonProviderSpec : SubjectSpek<JsonProvider>({\n    subject { JsonProvider }\n\n    given(\"a JSON provider\") {\n        on(\"create source from reader\") {\n            //language=Json\n            val source = subject.reader(\"\"\"{ \"type\": \"reader\" }\"\"\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"JSON\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            //language=Json\n            val source = subject.inputStream(tempFileOf(\"\"\"{ \"type\": \"inputStream\" }\"\"\").inputStream())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"JSON\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject JsonProviderInJavaSpec : SubjectSpek<JsonProvider>({\n    subject { JsonProvider.get() }\n\n    itBehavesLike(JsonProviderSpec)\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/json/JsonSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject JsonSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.json.resource(\"source/source.json\")\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject JsonSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.json.resource(\"source/source.json\")\n        val json = config.toJson.toText()\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.json.string(json)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/json/JsonSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.fasterxml.jackson.databind.node.BigIntegerNode\nimport com.fasterxml.jackson.databind.node.BooleanNode\nimport com.fasterxml.jackson.databind.node.DecimalNode\nimport com.fasterxml.jackson.databind.node.DoubleNode\nimport com.fasterxml.jackson.databind.node.FloatNode\nimport com.fasterxml.jackson.databind.node.IntNode\nimport com.fasterxml.jackson.databind.node.LongNode\nimport com.fasterxml.jackson.databind.node.ShortNode\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.WrongTypeException\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject JsonSourceSpec : Spek({\n    given(\"a JSON source\") {\n        group(\"get operation\") {\n            //language=Json\n            val source by memoized { JsonProvider.string(\"\"\"{ \"key\": 1 }\"\"\") }\n            on(\"get underlying JSON node\") {\n                val intSource = JsonSource(IntNode.valueOf(1))\n                it(\"should return corresponding node\") {\n                    val node = intSource.node\n                    assertTrue(node.isInt)\n                    assertThat(node.intValue(), equalTo(1))\n                }\n            }\n            on(\"get an existed key\") {\n                it(\"should contain the key\") {\n                    assertTrue(\"key\".toPath() in source)\n                }\n                it(\"should contain the corresponding value\") {\n                    assertThat(source[\"key\".toPath()].asValue<Int>(), equalTo(1))\n                }\n            }\n            on(\"get an non-existed key\") {\n                it(\"should not contain the key\") {\n                    assertTrue(\"invalid\".toPath() !in source)\n                }\n                it(\"should not contain the corresponding value\") {\n                    assertNull(source.getOrNull(\"invalid\".toPath()))\n                }\n            }\n        }\n        group(\"cast operation\") {\n            on(\"get string from other source\") {\n                it(\"should throw WrongTypeException\") {\n                    assertThat({ JsonSource(IntNode.valueOf(1)).asValue<String>() }, throws<WrongTypeException>())\n                }\n            }\n            on(\"get boolean from other source\") {\n                it(\"should throw WrongTypeException\") {\n                    assertThat({ JsonSource(IntNode.valueOf(1)).asValue<Boolean>() }, throws<WrongTypeException>())\n                }\n            }\n            on(\"get double from other source\") {\n                it(\"should throw WrongTypeException\") {\n                    assertThat({ JsonSource(BooleanNode.valueOf(true)).asValue<Double>() }, throws<WrongTypeException>())\n                }\n            }\n            on(\"get integer from other source\") {\n                it(\"should throw WrongTypeException\") {\n                    assertThat({ JsonSource(DoubleNode.valueOf(1.0)).asValue<Int>() }, throws<WrongTypeException>())\n                }\n            }\n            on(\"get long from long source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(LongNode.valueOf(1L)).asValue<Long>(), equalTo(1L))\n                }\n            }\n            on(\"get long from integer source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(IntNode.valueOf(1)).asValue<Long>(), equalTo(1L))\n                }\n            }\n            on(\"get short from short source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(ShortNode.valueOf(1)).asValue<Short>(), equalTo(1.toShort()))\n                }\n            }\n            on(\"get short from integer source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(IntNode.valueOf(1)).asValue<Short>(), equalTo(1.toShort()))\n                }\n            }\n            on(\"get float from float source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(FloatNode.valueOf(1.0F)).asValue<Float>(), equalTo(1.0F))\n                }\n            }\n            on(\"get float from double source\") {\n                it(\"should succeed\") {\n                    assertThat(JsonSource(DoubleNode.valueOf(1.0)).asValue<Float>(), equalTo(1.0F))\n                }\n            }\n            on(\"get BigInteger from BigInteger source\") {\n                it(\"should succeed\") {\n                    assertThat(\n                        JsonSource(BigIntegerNode.valueOf(BigInteger.valueOf(1L))).asValue<BigInteger>(),\n                        equalTo(BigInteger.valueOf(1L))\n                    )\n                }\n            }\n            on(\"get BigInteger from long source\") {\n                it(\"should succeed\") {\n                    assertThat(\n                        JsonSource(LongNode.valueOf(1L)).asValue<BigInteger>(),\n                        equalTo(BigInteger.valueOf(1L))\n                    )\n                }\n            }\n            on(\"get BigDecimal from BigDecimal source\") {\n                it(\"should succeed\") {\n                    assertThat(\n                        JsonSource(DecimalNode.valueOf(BigDecimal.valueOf(1.0))).asValue<BigDecimal>(),\n                        equalTo(BigDecimal.valueOf(1.0))\n                    )\n                }\n            }\n            on(\"get BigDecimal from double source\") {\n                it(\"should succeed\") {\n                    assertThat(\n                        JsonSource(DoubleNode.valueOf(1.0)).asValue<BigDecimal>(),\n                        equalTo(BigDecimal.valueOf(1.0))\n                    )\n                }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/json/JsonWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.json\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject JsonWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toJson\n    }\n\n    given(\"a writer\") {\n        //language=Json\n        val expectedString =\n            \"\"\"\n            {\n              \"key\" : \"value\"\n            }\n            \"\"\".trimIndent().replace(\"\\n\", System.lineSeparator())\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/properties/PropertiesProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.properties\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject PropertiesProviderSpec : SubjectSpek<PropertiesProvider>({\n    subject { PropertiesProvider }\n\n    given(\"a properties provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"type = reader\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"properties\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"type = inputStream\").inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"properties\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from system properties\") {\n            System.setProperty(\"type\", \"system\")\n            val source = subject.system()\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"system-properties\"))\n            }\n            it(\"should return a source which contains value from system properties\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"system\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject PropertiesProviderInJavaSpec : SubjectSpek<PropertiesProvider>({\n    subject { PropertiesProvider.get() }\n\n    itBehavesLike(PropertiesProviderSpec)\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/properties/PropertiesSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.properties\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.base.FlatConfigForLoad\nimport com.uchuhimo.konf.source.base.FlatSourceLoadBaseSpec\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject PropertiesSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.properties.resource(\"source/source.properties\")\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n\nobject PropertiesSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.properties.resource(\"source/source.properties\")\n        val properties = config.toProperties.toText()\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.properties.string(properties)\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-core/src/test/kotlin/com/uchuhimo/konf/source/serializer/PrimitiveStdSerializerSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.serializer\n\nimport com.fasterxml.jackson.core.JsonGenerator\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.SerializerProvider\nimport com.fasterxml.jackson.databind.deser.std.StdDeserializer\nimport com.fasterxml.jackson.databind.module.SimpleModule\nimport com.fasterxml.jackson.databind.ser.std.StdSerializer\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.json.toJson\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject PrimitiveStdSerializerSpec : SubjectSpek<Config>({\n    subject {\n        Config {\n            addSpec(WrappedStringSpec)\n            mapper.registerModule(\n                SimpleModule().apply {\n                    addSerializer(WrappedString::class.java, WrappedStringStdSerializer())\n                    addDeserializer(WrappedString::class.java, WrappedStringStdDeserializer())\n                }\n            )\n        }\n    }\n\n    given(\"a config\") {\n        val json = \"\"\"\n            {\n              \"wrapped-string\" : \"1234\"\n            }\n        \"\"\".trimIndent().replace(\"\\n\", System.lineSeparator())\n        on(\"write wrapped string to json\") {\n            subject[WrappedStringSpec.wrappedString] = WrappedString(\"1234\")\n            val result = subject.toJson.toText()\n            it(\"should serialize wrapped string as string\") {\n                assertThat(result, equalTo(json))\n            }\n        }\n        on(\"read wrapped string from json\") {\n            val config = subject.from.json.string(json)\n            it(\"should deserialize wrapped string from string\") {\n                assertThat(config[WrappedStringSpec.wrappedString], equalTo(WrappedString(\"1234\")))\n            }\n        }\n    }\n})\n\nprivate object WrappedStringSpec : ConfigSpec(\"\") {\n    val wrappedString by optional(name = \"wrapped-string\", default = WrappedString(\"value\"))\n}\n\nprivate class WrappedStringStdSerializer : StdSerializer<WrappedString>(WrappedString::class.java) {\n\n    override fun serialize(value: WrappedString, gen: JsonGenerator, provider: SerializerProvider) {\n        gen.writeString(value.string)\n    }\n}\n\nprivate class WrappedStringStdDeserializer : StdDeserializer<WrappedString>(WrappedString::class.java) {\n\n    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): WrappedString {\n        return WrappedString(p.valueAsString)\n    }\n}\n\nprivate data class WrappedString(val string: String)\n"
  },
  {
    "path": "konf-core/src/test/resources/source/provider.properties",
    "content": "type=resource"
  },
  {
    "path": "konf-core/src/test/resources/source/source.json",
    "content": "{\n  \"level1\": {\n    \"level2\": {\n      \"empty\": \"null\",\n      \"literalEmpty\": null,\n      \"present\": 1,\n      \"boolean\": false,\n      \"int\": 1,\n      \"short\": 2,\n      \"byte\": 3,\n      \"bigInteger\": 4,\n      \"long\": 4,\n      \"double\": 1.5,\n      \"float\": -1.5,\n      \"bigDecimal\": 1.5,\n      \"char\": \"a\",\n      \"string\": \"string\",\n      \"offsetTime\": \"10:15:30+01:00\",\n      \"offsetDateTime\": \"2007-12-03T10:15:30+01:00\",\n      \"zonedDateTime\": \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n      \"localDate\": \"2007-12-03\",\n      \"localTime\": \"10:15:30\",\n      \"localDateTime\": \"2007-12-03T10:15:30\",\n      \"date\": \"2007-12-03T10:15:30Z\",\n      \"year\": \"2007\",\n      \"yearMonth\": \"2007-12\",\n      \"instant\": \"2007-12-03T10:15:30.00Z\",\n      \"duration\": \"P2DT3H4M\",\n      \"simpleDuration\": \"200millis\",\n      \"size\": \"10k\",\n      \"enum\": \"LABEL2\",\n      \"array\": {\n        \"boolean\": [\n          true,\n          false\n        ],\n        \"byte\": [\n          1,\n          2,\n          3\n        ],\n        \"short\": [\n          1,\n          2,\n          3\n        ],\n        \"int\": [\n          1,\n          2,\n          3\n        ],\n        \"long\": [\n          4,\n          5,\n          6\n        ],\n        \"float\": [\n          -1.0,\n          0.0,\n          1.0\n        ],\n        \"double\": [\n          -1.0,\n          0.0,\n          1.0\n        ],\n        \"char\": [\n          \"a\",\n          \"b\",\n          \"c\"\n        ],\n        \"object\": {\n          \"boolean\": [\n            true,\n            false\n          ],\n          \"int\": [\n            1,\n            2,\n            3\n          ],\n          \"string\": [\n            \"one\",\n            \"two\",\n            \"three\"\n          ],\n          \"enum\": [\n            \"LABEL1\",\n            \"LABEL2\",\n            \"LABEL3\"\n          ]\n        }\n      },\n      \"list\": [\n        1,\n        2,\n        3\n      ],\n      \"mutableList\": [\n        1,\n        2,\n        3\n      ],\n      \"listOfList\": [\n        [\n          1,\n          2\n        ],\n        [\n          3,\n          4\n        ]\n      ],\n      \"set\": [\n        1,\n        2,\n        1\n      ],\n      \"sortedSet\": [\n        2,\n        1,\n        1,\n        3\n      ],\n      \"map\": {\n        \"a\": 1,\n        \"b\": 2,\n        \"c\": 3\n      },\n      \"intMap\": {\n        \"1\": \"a\",\n        \"2\": \"b\",\n        \"3\": \"c\"\n      },\n      \"sortedMap\": {\n        \"c\": 3,\n        \"b\": 2,\n        \"a\": 1\n      },\n      \"listOfMap\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 3,\n          \"b\": 4\n        }\n      ],\n      \"nested\": [\n        [\n          [\n            {\n              \"a\": 1\n            }\n          ]\n        ]\n      ],\n      \"pair\": {\n        \"first\": 1,\n        \"second\": 2\n      },\n      \"clazz\": {\n        \"empty\": \"null\",\n        \"literalEmpty\": null,\n        \"present\": 1,\n        \"boolean\": false,\n        \"int\": 1,\n        \"short\": 2,\n        \"byte\": 3,\n        \"bigInteger\": 4,\n        \"long\": 4,\n        \"double\": 1.5,\n        \"float\": -1.5,\n        \"bigDecimal\": 1.5,\n        \"char\": \"a\",\n        \"string\": \"string\",\n        \"offsetTime\": \"10:15:30+01:00\",\n        \"offsetDateTime\": \"2007-12-03T10:15:30+01:00\",\n        \"zonedDateTime\": \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n        \"localDate\": \"2007-12-03\",\n        \"localTime\": \"10:15:30\",\n        \"localDateTime\": \"2007-12-03T10:15:30\",\n        \"date\": \"2007-12-03T10:15:30Z\",\n        \"year\": \"2007\",\n        \"yearMonth\": \"2007-12\",\n        \"instant\": \"2007-12-03T10:15:30.00Z\",\n        \"duration\": \"P2DT3H4M\",\n        \"simpleDuration\": \"200millis\",\n        \"size\": \"10k\",\n        \"enum\": \"LABEL2\",\n        \"booleanArray\": [\n          true,\n          false\n        ],\n        \"nested\": [\n          [\n            [\n              {\n                \"a\": 1\n              }\n            ]\n          ]\n        ]\n      }\n    }\n  }\n}"
  },
  {
    "path": "konf-core/src/test/resources/source/source.properties",
    "content": "level1.level2.empty=null\nlevel1.level2.literalEmpty=null\nlevel1.level2.present=1\nlevel1.level2.boolean=false\nlevel1.level2.int=1\nlevel1.level2.short=2\nlevel1.level2.byte=3\nlevel1.level2.bigInteger=4\nlevel1.level2.long=4\nlevel1.level2.double=1.5\nlevel1.level2.float=-1.5\nlevel1.level2.bigDecimal=1.5\nlevel1.level2.char=a\nlevel1.level2.string=string\nlevel1.level2.offsetTime=10:15:30+01:00\nlevel1.level2.offsetDateTime=2007-12-03T10:15:30+01:00\nlevel1.level2.zonedDateTime=2007-12-03T10:15:30+01:00[Europe/Paris]\nlevel1.level2.localDate=2007-12-03\nlevel1.level2.localTime=10:15:30\nlevel1.level2.localDateTime=2007-12-03T10:15:30\nlevel1.level2.date=2007-12-03T10:15:30Z\nlevel1.level2.year=2007\nlevel1.level2.yearMonth=2007-12\nlevel1.level2.instant=2007-12-03T10:15:30.00Z\nlevel1.level2.duration=P2DT3H4M\nlevel1.level2.simpleDuration=200millis\nlevel1.level2.size=10k\nlevel1.level2.enum=LABEL2\nlevel1.level2.list=1,2,3\nlevel1.level2.mutableList=1,2,3\nlevel1.level2.listOfList.0=1,2\nlevel1.level2.listOfList.1=3,4\nlevel1.level2.set=1,2,1\nlevel1.level2.sortedSet=2,1,1,3\nlevel1.level2.map.a=1\nlevel1.level2.map.b=2\nlevel1.level2.map.c=3\nlevel1.level2.intMap.1=a\nlevel1.level2.intMap.2=b\nlevel1.level2.intMap.3=c\nlevel1.level2.sortedMap.c=3\nlevel1.level2.sortedMap.b=2\nlevel1.level2.sortedMap.a=1\nlevel1.level2.nested.0.0.0.a=1\nlevel1.level2.listOfMap.0.a=1\nlevel1.level2.listOfMap.0.b=2\nlevel1.level2.listOfMap.1.a=3\nlevel1.level2.listOfMap.1.b=4\nlevel1.level2.array.boolean=true,false\nlevel1.level2.array.byte=1,2,3\nlevel1.level2.array.short=1,2,3\nlevel1.level2.array.int=1,2,3\nlevel1.level2.array.long=4,5,6\nlevel1.level2.array.float=-1, 0.0, 1\nlevel1.level2.array.double=-1, 0.0, 1\nlevel1.level2.array.char=a,b,c\nlevel1.level2.array.object.boolean=true,false\nlevel1.level2.array.object.int=1,2,3\nlevel1.level2.array.object.string=one,two,three\nlevel1.level2.array.object.enum=LABEL1,LABEL2,LABEL3\nlevel1.level2.pair.first=1\nlevel1.level2.pair.second=2\nlevel1.level2.clazz.empty=null\nlevel1.level2.clazz.literalEmpty=null\nlevel1.level2.clazz.present=1\nlevel1.level2.clazz.boolean=false\nlevel1.level2.clazz.int=1\nlevel1.level2.clazz.short=2\nlevel1.level2.clazz.byte=3\nlevel1.level2.clazz.bigInteger=4\nlevel1.level2.clazz.long=4\nlevel1.level2.clazz.double=1.5\nlevel1.level2.clazz.float=-1.5\nlevel1.level2.clazz.bigDecimal=1.5\nlevel1.level2.clazz.char=a\nlevel1.level2.clazz.string=string\nlevel1.level2.clazz.offsetTime=10:15:30+01:00\nlevel1.level2.clazz.offsetDateTime=2007-12-03T10:15:30+01:00\nlevel1.level2.clazz.zonedDateTime=2007-12-03T10:15:30+01:00[Europe/Paris]\nlevel1.level2.clazz.localDate=2007-12-03\nlevel1.level2.clazz.localTime=10:15:30\nlevel1.level2.clazz.localDateTime=2007-12-03T10:15:30\nlevel1.level2.clazz.date=2007-12-03T10:15:30Z\nlevel1.level2.clazz.year=2007\nlevel1.level2.clazz.yearMonth=2007-12\nlevel1.level2.clazz.instant=2007-12-03T10:15:30.00Z\nlevel1.level2.clazz.duration=P2DT3H4M\nlevel1.level2.clazz.simpleDuration=200millis\nlevel1.level2.clazz.size=10k\nlevel1.level2.clazz.enum=LABEL2\nlevel1.level2.clazz.booleanArray=true,false\nlevel1.level2.clazz.nested.0.0.0.a=1\nlevel1.level2.emptyList=\nlevel1.level2.emptySet=\nlevel1.level2.emptyArray=\nlevel1.level2.emptyObjectArray=\nlevel1.level2.singleElementList=1\nlevel1.level2.multipleElementsList=1,2\nlevel1.level2.flatClass.stringWithComma=string,with,comma\nlevel1.level2.flatClass.emptyList=\nlevel1.level2.flatClass.emptySet=\nlevel1.level2.flatClass.emptyArray=\nlevel1.level2.flatClass.emptyObjectArray=\nlevel1.level2.flatClass.singleElementList=1\nlevel1.level2.flatClass.multipleElementsList=1,2\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/TestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf\n\nimport java.io.File\n\nfun tempFileOf(content: String, prefix: String = \"tmp\", suffix: String = \".tmp\"): File {\n    return tempFile(prefix, suffix).apply {\n        writeText(content)\n    }\n}\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/ConfigForLoad.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.SizeInBytes\nimport java.io.Serializable\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Duration\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZonedDateTime\nimport java.util.Date\nimport java.util.SortedMap\nimport java.util.SortedSet\n\nobject ConfigForLoad : ConfigSpec(\"level1.level2\") {\n    val empty by required<Int?>()\n    val literalEmpty by required<Int?>()\n    val present by required<Int?>()\n\n    val boolean by required<Boolean>()\n\n    val int by required<Int>()\n    val short by required<Short>()\n    val byte by required<Byte>()\n    val bigInteger by required<BigInteger>()\n    val long by required<Long>()\n\n    val double by required<Double>()\n    val float by required<Float>()\n    val bigDecimal by required<BigDecimal>()\n\n    val char by required<Char>()\n\n    val string by required<String>()\n    val offsetTime by required<OffsetTime>()\n    val offsetDateTime by required<OffsetDateTime>()\n    val zonedDateTime by required<ZonedDateTime>()\n    val localDate by required<LocalDate>()\n    val localTime by required<LocalTime>()\n    val localDateTime by required<LocalDateTime>()\n    val date by required<Date>()\n    val year by required<Year>()\n    val yearMonth by required<YearMonth>()\n    val instant by required<Instant>()\n    val duration by required<Duration>()\n    val simpleDuration by required<Duration>()\n    val size by required<SizeInBytes>()\n\n    val enum by required<EnumForLoad>()\n\n    // array items\n    val booleanArray by required<BooleanArray>(\"array.boolean\")\n    val byteArray by required<ByteArray>(\"array.byte\")\n    val shortArray by required<ShortArray>(\"array.short\")\n    val intArray by required<IntArray>(\"array.int\")\n    val longArray by required<LongArray>(\"array.long\")\n    val floatArray by required<FloatArray>(\"array.float\")\n    val doubleArray by required<DoubleArray>(\"array.double\")\n    val charArray by required<CharArray>(\"array.char\")\n\n    // object array item\n    val booleanObjectArray by required<Array<Boolean>>(\"array.object.boolean\")\n    val intObjectArray by required<Array<Int>>(\"array.object.int\")\n    val stringArray by required<Array<String>>(\"array.object.string\")\n    val enumArray by required<Array<EnumForLoad>>(\"array.object.enum\")\n\n    val list by required<List<Int>>()\n    val mutableList by required<List<Int>>()\n    val listOfList by required<List<List<Int>>>()\n    val set by required<Set<Int>>()\n    val sortedSet by required<SortedSet<Int>>()\n\n    val map by required<Map<String, Int>>()\n    val intMap by required<Map<Int, String>>()\n    val sortedMap by required<SortedMap<String, Int>>()\n    val listOfMap by required<List<Map<String, Int>>>()\n\n    val nested by required<Array<List<Set<Map<String, Int>>>>>()\n\n    val pair by required<Pair<Int, Int>>()\n\n    val clazz by required<ClassForLoad>()\n}\n\nenum class EnumForLoad {\n    LABEL1, LABEL2, LABEL3\n}\n\ndata class ClassForLoad(\n    val empty: Int?,\n    val literalEmpty: Int?,\n    val present: Int?,\n    val boolean: Boolean,\n    val int: Int,\n    val short: Short,\n    val byte: Byte,\n    val bigInteger: BigInteger,\n    val long: Long,\n    val double: Double,\n    val float: Float,\n    val bigDecimal: BigDecimal,\n    val char: Char,\n    val string: String,\n    val offsetTime: OffsetTime,\n    val offsetDateTime: OffsetDateTime,\n    val zonedDateTime: ZonedDateTime,\n    val localDate: LocalDate,\n    val localTime: LocalTime,\n    val localDateTime: LocalDateTime,\n    val date: Date,\n    val year: Year,\n    val yearMonth: YearMonth,\n    val instant: Instant,\n    val duration: Duration,\n    val simpleDuration: Duration,\n    val size: SizeInBytes,\n    val enum: EnumForLoad,\n    val booleanArray: BooleanArray,\n    val nested: Array<List<Set<Map<String, Int>>>>\n) : Serializable\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/SingleThreadDispatcher.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport java.util.concurrent.Executors\n\nfun newSequentialDispatcher() = Executors.newSingleThreadExecutor().asCoroutineDispatcher()\n\nprivate val dispatcher = newSequentialDispatcher()\n\nval Dispatchers.Sequential: CoroutineDispatcher get() = dispatcher\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/SourceLoadBaseSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.SizeInBytes\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.math.BigDecimal\nimport java.math.BigInteger\nimport java.time.Duration\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.LocalTime\nimport java.time.OffsetDateTime\nimport java.time.OffsetTime\nimport java.time.Year\nimport java.time.YearMonth\nimport java.time.ZonedDateTime\nimport java.util.Arrays\nimport java.util.Date\nimport java.util.SortedSet\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject SourceLoadBaseSpec : SubjectSpek<Config>({\n    given(\"a source\") {\n        on(\"load the source into config\") {\n            it(\"should contain every value specified in the source\") {\n                assertNull(subject[ConfigForLoad.empty])\n                assertNull(subject[ConfigForLoad.literalEmpty])\n                assertThat(subject[ConfigForLoad.present], equalTo(1))\n                assertThat(subject[ConfigForLoad.boolean], equalTo(false))\n\n                assertThat(subject[ConfigForLoad.int], equalTo(1))\n                assertThat(subject[ConfigForLoad.short], equalTo(2.toShort()))\n                assertThat(subject[ConfigForLoad.byte], equalTo(3.toByte()))\n                assertThat(subject[ConfigForLoad.bigInteger], equalTo(BigInteger.valueOf(4)))\n                assertThat(subject[ConfigForLoad.long], equalTo(4L))\n\n                assertThat(subject[ConfigForLoad.double], equalTo(1.5))\n                assertThat(subject[ConfigForLoad.float], equalTo(-1.5f))\n                assertThat(subject[ConfigForLoad.bigDecimal], equalTo(BigDecimal.valueOf(1.5)))\n\n                assertThat(subject[ConfigForLoad.char], equalTo('a'))\n\n                assertThat(subject[ConfigForLoad.string], equalTo(\"string\"))\n                assertThat(\n                    subject[ConfigForLoad.offsetTime],\n                    equalTo(OffsetTime.parse(\"10:15:30+01:00\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.offsetDateTime],\n                    equalTo(OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.zonedDateTime],\n                    equalTo(ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.localDate],\n                    equalTo(LocalDate.parse(\"2007-12-03\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.localTime],\n                    equalTo(LocalTime.parse(\"10:15:30\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.localDateTime],\n                    equalTo(LocalDateTime.parse(\"2007-12-03T10:15:30\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.date],\n                    equalTo(Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")))\n                )\n                assertThat(\n                    subject[ConfigForLoad.year],\n                    equalTo(Year.parse(\"2007\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.yearMonth],\n                    equalTo(YearMonth.parse(\"2007-12\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.instant],\n                    equalTo(Instant.parse(\"2007-12-03T10:15:30.00Z\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.duration],\n                    equalTo(Duration.parse(\"P2DT3H4M\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.simpleDuration],\n                    equalTo(Duration.ofMillis(200))\n                )\n                assertThat(subject[ConfigForLoad.size].bytes, equalTo(10240L))\n\n                assertThat(subject[ConfigForLoad.enum], equalTo(EnumForLoad.LABEL2))\n\n                // array items\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.booleanArray],\n                        booleanArrayOf(true, false)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.byteArray],\n                        byteArrayOf(1, 2, 3)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.shortArray],\n                        shortArrayOf(1, 2, 3)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.intArray],\n                        intArrayOf(1, 2, 3)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.longArray],\n                        longArrayOf(4, 5, 6)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.floatArray],\n                        floatArrayOf(-1.0F, 0.0F, 1.0F)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.doubleArray],\n                        doubleArrayOf(-1.0, 0.0, 1.0)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.charArray],\n                        charArrayOf('a', 'b', 'c')\n                    )\n                )\n\n                // object array items\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.booleanObjectArray],\n                        arrayOf(true, false)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.intObjectArray],\n                        arrayOf(1, 2, 3)\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.stringArray],\n                        arrayOf(\"one\", \"two\", \"three\")\n                    )\n                )\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.enumArray],\n                        arrayOf(EnumForLoad.LABEL1, EnumForLoad.LABEL2, EnumForLoad.LABEL3)\n                    )\n                )\n\n                assertThat(subject[ConfigForLoad.list], equalTo(listOf(1, 2, 3)))\n\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.mutableList].toTypedArray(),\n                        arrayOf(1, 2, 3)\n                    )\n                )\n\n                assertThat(\n                    subject[ConfigForLoad.listOfList],\n                    equalTo(listOf(listOf(1, 2), listOf(3, 4)))\n                )\n\n                assertThat(subject[ConfigForLoad.set], equalTo(setOf(1, 2)))\n\n                assertThat(\n                    subject[ConfigForLoad.sortedSet],\n                    equalTo<SortedSet<Int>>(sortedSetOf(1, 2, 3))\n                )\n\n                assertThat(\n                    subject[ConfigForLoad.map],\n                    equalTo(mapOf(\"a\" to 1, \"b\" to 2, \"c\" to 3))\n                )\n                assertThat(\n                    subject[ConfigForLoad.intMap],\n                    equalTo(mapOf(1 to \"a\", 2 to \"b\", 3 to \"c\"))\n                )\n                assertThat(\n                    subject[ConfigForLoad.sortedMap],\n                    equalTo(sortedMapOf(\"a\" to 1, \"b\" to 2, \"c\" to 3))\n                )\n                assertThat(subject[ConfigForLoad.sortedMap].firstKey(), equalTo(\"a\"))\n                assertThat(subject[ConfigForLoad.sortedMap].lastKey(), equalTo(\"c\"))\n                assertThat(\n                    subject[ConfigForLoad.listOfMap],\n                    equalTo(listOf(mapOf(\"a\" to 1, \"b\" to 2), mapOf(\"a\" to 3, \"b\" to 4)))\n                )\n\n                assertTrue(\n                    Arrays.equals(\n                        subject[ConfigForLoad.nested],\n                        arrayOf(listOf(setOf(mapOf(\"a\" to 1))))\n                    )\n                )\n\n                assertThat(subject[ConfigForLoad.pair], equalTo(1 to 2))\n\n                val classForLoad = ClassForLoad(\n                    empty = null,\n                    literalEmpty = null,\n                    present = 1,\n                    boolean = false,\n                    int = 1,\n                    short = 2.toShort(),\n                    byte = 3.toByte(),\n                    bigInteger = BigInteger.valueOf(4),\n                    long = 4L,\n                    double = 1.5,\n                    float = -1.5f,\n                    bigDecimal = BigDecimal.valueOf(1.5),\n                    char = 'a',\n                    string = \"string\",\n                    offsetTime = OffsetTime.parse(\"10:15:30+01:00\"),\n                    offsetDateTime = OffsetDateTime.parse(\"2007-12-03T10:15:30+01:00\"),\n                    zonedDateTime = ZonedDateTime.parse(\"2007-12-03T10:15:30+01:00[Europe/Paris]\"),\n                    localDate = LocalDate.parse(\"2007-12-03\"),\n                    localTime = LocalTime.parse(\"10:15:30\"),\n                    localDateTime = LocalDateTime.parse(\"2007-12-03T10:15:30\"),\n                    date = Date.from(Instant.parse(\"2007-12-03T10:15:30Z\")),\n                    year = Year.parse(\"2007\"),\n                    yearMonth = YearMonth.parse(\"2007-12\"),\n                    instant = Instant.parse(\"2007-12-03T10:15:30.00Z\"),\n                    duration = \"P2DT3H4M\".toDuration(),\n                    simpleDuration = Duration.ofMillis(200),\n                    size = SizeInBytes.parse(\"10k\"),\n                    enum = EnumForLoad.LABEL2,\n                    booleanArray = booleanArrayOf(true, false),\n                    nested = arrayOf(listOf(setOf(mapOf(\"a\" to 1))))\n                )\n                assertThat(subject[ConfigForLoad.clazz].empty, equalTo(classForLoad.empty))\n                assertThat(subject[ConfigForLoad.clazz].literalEmpty, equalTo(classForLoad.literalEmpty))\n                assertThat(subject[ConfigForLoad.clazz].present, equalTo(classForLoad.present))\n                assertThat(subject[ConfigForLoad.clazz].boolean, equalTo(classForLoad.boolean))\n                assertThat(subject[ConfigForLoad.clazz].int, equalTo(classForLoad.int))\n                assertThat(subject[ConfigForLoad.clazz].short, equalTo(classForLoad.short))\n                assertThat(subject[ConfigForLoad.clazz].byte, equalTo(classForLoad.byte))\n                assertThat(subject[ConfigForLoad.clazz].bigInteger, equalTo(classForLoad.bigInteger))\n                assertThat(subject[ConfigForLoad.clazz].long, equalTo(classForLoad.long))\n                assertThat(subject[ConfigForLoad.clazz].double, equalTo(classForLoad.double))\n                assertThat(subject[ConfigForLoad.clazz].float, equalTo(classForLoad.float))\n                assertThat(subject[ConfigForLoad.clazz].bigDecimal, equalTo(classForLoad.bigDecimal))\n                assertThat(subject[ConfigForLoad.clazz].char, equalTo(classForLoad.char))\n                assertThat(subject[ConfigForLoad.clazz].string, equalTo(classForLoad.string))\n                assertThat(subject[ConfigForLoad.clazz].offsetTime, equalTo(classForLoad.offsetTime))\n                assertThat(subject[ConfigForLoad.clazz].offsetDateTime, equalTo(classForLoad.offsetDateTime))\n                assertThat(subject[ConfigForLoad.clazz].zonedDateTime, equalTo(classForLoad.zonedDateTime))\n                assertThat(subject[ConfigForLoad.clazz].localDate, equalTo(classForLoad.localDate))\n                assertThat(subject[ConfigForLoad.clazz].localTime, equalTo(classForLoad.localTime))\n                assertThat(subject[ConfigForLoad.clazz].localDateTime, equalTo(classForLoad.localDateTime))\n                assertThat(subject[ConfigForLoad.clazz].date, equalTo(classForLoad.date))\n                assertThat(subject[ConfigForLoad.clazz].year, equalTo(classForLoad.year))\n                assertThat(subject[ConfigForLoad.clazz].yearMonth, equalTo(classForLoad.yearMonth))\n                assertThat(subject[ConfigForLoad.clazz].instant, equalTo(classForLoad.instant))\n                assertThat(subject[ConfigForLoad.clazz].duration, equalTo(classForLoad.duration))\n                assertThat(subject[ConfigForLoad.clazz].simpleDuration, equalTo(classForLoad.simpleDuration))\n                assertThat(subject[ConfigForLoad.clazz].size, equalTo(classForLoad.size))\n                assertThat(subject[ConfigForLoad.clazz].enum, equalTo(classForLoad.enum))\n                assertTrue(Arrays.equals(subject[ConfigForLoad.clazz].booleanArray, classForLoad.booleanArray))\n                assertTrue(Arrays.equals(subject[ConfigForLoad.clazz].nested, classForLoad.nested))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/TestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.Matcher\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.has\nimport com.natpryce.hamkrest.isA\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\n\nobject DefaultLoadersConfig : ConfigSpec(\"source.test\") {\n    val type by required<String>()\n}\n\nfun Source.toConfig(): Config = Config {\n    addSpec(DefaultLoadersConfig)\n}.withSource(this)\n\ninline fun <reified T : Any> assertCausedBy(noinline block: () -> Unit) {\n    @Suppress(\"UNCHECKED_CAST\")\n    assertThat(\n        block,\n        throws(\n            has(\n                LoadException::cause,\n                isA<T>() as Matcher<Throwable?>\n            )\n        )\n    )\n}\n\nconst val propertiesContent = \"source.test.type = properties\"\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/base/FlatConfigForLoad.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.uchuhimo.konf.ConfigSpec\nimport java.io.Serializable\n\nobject FlatConfigForLoad : ConfigSpec(\"level1.level2\") {\n    val emptyList by required<List<Int>>()\n    val emptySet by required<Set<Int>>()\n    val emptyArray by required<IntArray>()\n    val emptyObjectArray by required<Array<Int>>()\n    val singleElementList by required<List<Int>>()\n    val multipleElementsList by required<List<Int>>()\n    val flatClass by required<ClassForLoad>()\n}\n\ndata class ClassForLoad(\n    val stringWithComma: String,\n    val emptyList: List<Int>,\n    val emptySet: Set<Int>,\n    val emptyArray: IntArray,\n    val emptyObjectArray: Array<Int>,\n    val singleElementList: List<Int>,\n    val multipleElementsList: List<Int>\n) : Serializable\n"
  },
  {
    "path": "konf-core/src/testFixtures/kotlin/com/uchuhimo/konf/source/base/FlatSourceLoadBaseSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.base\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport java.util.Arrays\nimport kotlin.test.assertTrue\n\nobject FlatSourceLoadBaseSpec : SubjectSpek<Config>({\n    itBehavesLike(SourceLoadBaseSpec)\n\n    given(\"a flat source\") {\n        on(\"load the source into config\") {\n            it(\"should contain every value specified in the source\") {\n                val classForLoad = ClassForLoad(\n                    stringWithComma = \"string,with,comma\",\n                    emptyList = listOf(),\n                    emptySet = setOf(),\n                    emptyArray = intArrayOf(),\n                    emptyObjectArray = arrayOf(),\n                    singleElementList = listOf(1),\n                    multipleElementsList = listOf(1, 2)\n                )\n                assertThat(subject[FlatConfigForLoad.emptyList], equalTo(listOf()))\n                assertThat(subject[FlatConfigForLoad.emptySet], equalTo(setOf()))\n                assertTrue(Arrays.equals(subject[FlatConfigForLoad.emptyArray], intArrayOf()))\n                assertTrue(Arrays.equals(subject[FlatConfigForLoad.emptyObjectArray], arrayOf()))\n                assertThat(subject[FlatConfigForLoad.singleElementList], equalTo(listOf(1)))\n                assertThat(subject[FlatConfigForLoad.multipleElementsList], equalTo(listOf(1, 2)))\n                assertThat(\n                    subject[FlatConfigForLoad.flatClass].stringWithComma,\n                    equalTo(classForLoad.stringWithComma)\n                )\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-git/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    api(\"org.eclipse.jgit\", \"org.eclipse.jgit\", Versions.jgit)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-git/src/main/kotlin/com/uchuhimo/konf/source/DefaultGitLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport kotlinx.coroutines.Dispatchers\nimport org.eclipse.jgit.lib.Constants\nimport java.io.File\nimport java.util.concurrent.TimeUnit\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Returns a child config containing values from a specified git repository.\n *\n * Format of the url is auto-detected from the url extension.\n * Supported url formats and the corresponding extensions:\n * - HOCON: conf\n * - JSON: json\n * - Properties: properties\n * - TOML: toml\n * - XML: xml\n * - YAML: yml, yaml\n *\n * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param optional whether the source is optional\n * @param action additional action when cloning/pulling\n * @return a child config containing values from a specified git repository\n * @throws UnsupportedExtensionException\n */\nfun DefaultLoaders.git(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    optional: Boolean = this.optional\n): Config = dispatchExtension(File(file).extension, \"{repo: $repo, file: $file}\")\n    .git(repo, file, dir, branch, optional)\n\n/**\n * Returns a child config containing values from a specified git repository,\n * and reloads values periodically.\n *\n * Format of the url is auto-detected from the url extension.\n * Supported url formats and the corresponding extensions:\n * - HOCON: conf\n * - JSON: json\n * - Properties: properties\n * - TOML: toml\n * - XML: xml\n * - YAML: yml, yaml\n *\n * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param period reload period. The default value is 1.\n * @param unit time unit of reload period. The default value is [TimeUnit.MINUTES].\n * @param context context of the coroutine. The default value is [Dispatchers.Default].\n * @param optional whether the source is optional\n * @param onLoad function invoked after the updated git file is loaded\n * @return a child config containing values from a specified git repository\n * @throws UnsupportedExtensionException\n */\nfun DefaultLoaders.watchGit(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    period: Long = 1,\n    unit: TimeUnit = TimeUnit.MINUTES,\n    context: CoroutineContext = Dispatchers.Default,\n    optional: Boolean = this.optional,\n    onLoad: ((config: Config, source: Source) -> Unit)? = null\n): Config = dispatchExtension(File(file).extension, \"{repo: $repo, file: $file}\")\n    .watchGit(repo, file, dir, branch, period, unit, context, optional, onLoad)\n"
  },
  {
    "path": "konf-git/src/main/kotlin/com/uchuhimo/konf/source/DefaultGitProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport org.eclipse.jgit.lib.Constants\nimport java.io.File\n\n/**\n * Returns a source from a specified git repository.\n *\n * Format of the url is auto-detected from the url extension.\n * Supported url formats and the corresponding extensions:\n * - HOCON: conf\n * - JSON: json\n * - Properties: properties\n * - TOML: toml\n * - XML: xml\n * - YAML: yml, yaml\n *\n * Throws [UnsupportedExtensionException] if the url extension is unsupported.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param optional whether the source is optional\n * @return a source from a specified git repository\n * @throws UnsupportedExtensionException\n */\nfun DefaultProviders.git(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    optional: Boolean = false\n): Source = dispatchExtension(File(file).extension, \"{repo: $repo, file: $file}\")\n    .git(repo, file, dir, branch, optional)\n"
  },
  {
    "path": "konf-git/src/main/kotlin/com/uchuhimo/konf/source/GitLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempDirectory\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport org.eclipse.jgit.lib.Constants\nimport java.util.concurrent.TimeUnit\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Returns a child config containing values from a specified git repository.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param optional whether the source is optional\n * @return a child config containing values from a specified git repository\n */\nfun Loader.git(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    optional: Boolean = this.optional\n): Config =\n    config.withSource(provider.git(repo, file, dir, branch, optional))\n\n/**\n * Returns a child config containing values from a specified git repository,\n * and reloads values periodically.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param period reload period. The default value is 1.\n * @param unit time unit of reload period. The default value is [TimeUnit.MINUTES].\n * @param context context of the coroutine. The default value is [Dispatchers.Default].\n * @param optional whether the source is optional\n * @param onLoad function invoked after the updated git file is loaded\n * @return a child config containing values from a specified git repository\n */\nfun Loader.watchGit(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    period: Long = 1,\n    unit: TimeUnit = TimeUnit.MINUTES,\n    context: CoroutineContext = Dispatchers.Default,\n    optional: Boolean = this.optional,\n    onLoad: ((config: Config, source: Source) -> Unit)? = null\n): Config {\n    return (dir ?: tempDirectory(prefix = \"local_git_repo\").path).let { directory ->\n        provider.git(repo, file, directory, branch, optional).let { source ->\n            config.withLoadTrigger(\"watch ${source.description}\") { newConfig, load ->\n                newConfig.lock {\n                    load(source)\n                }\n                onLoad?.invoke(newConfig, source)\n                GlobalScope.launch(context) {\n                    while (true) {\n                        delay(unit.toMillis(period))\n                        val newSource = provider.git(repo, file, directory, branch, optional)\n                        newConfig.lock {\n                            newConfig.clear()\n                            load(newSource)\n                        }\n                        onLoad?.invoke(newConfig, newSource)\n                    }\n                }\n            }.withLayer()\n        }\n    }\n}\n"
  },
  {
    "path": "konf-git/src/main/kotlin/com/uchuhimo/konf/source/GitProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.base.EmptyMapSource\nimport com.uchuhimo.konf.tempDirectory\nimport org.eclipse.jgit.api.Git\nimport org.eclipse.jgit.api.errors.GitAPIException\nimport org.eclipse.jgit.lib.Constants\nimport org.eclipse.jgit.transport.URIish\nimport java.io.File\nimport java.io.IOException\nimport java.nio.file.Paths\n\n/**\n * Returns a new source from a specified git repository.\n *\n * @param repo git repository\n * @param file file in the git repository\n * @param dir local directory of the git repository\n * @param branch the initial branch\n * @param optional whether this source is optional\n * @return a new source from a specified git repository\n */\nfun Provider.git(\n    repo: String,\n    file: String,\n    dir: String? = null,\n    branch: String = Constants.HEAD,\n    optional: Boolean = false\n): Source {\n    return (dir?.let(::File) ?: tempDirectory(prefix = \"local_git_repo\")).let { directory ->\n        val extendContext: Source.() -> Unit = {\n            info[\"repo\"] = repo\n            info[\"file\"] = file\n            info[\"dir\"] = directory.path\n            info[\"branch\"] = branch\n        }\n        try {\n            if ((directory.list { _, name -> name == \".git\" } ?: emptyArray()).isEmpty()) {\n                Git.cloneRepository().apply {\n                    setURI(repo)\n                    setDirectory(directory)\n                    setBranch(branch)\n                }.call().close()\n            } else {\n                Git.open(directory).use { git ->\n                    val uri = URIish(repo)\n                    val remoteName = git.remoteList().call().firstOrNull { it.urIs.contains(uri) }?.name\n                        ?: throw InvalidRemoteRepoException(repo, directory.path)\n                    git.pull().apply {\n                        remote = remoteName\n                        remoteBranchName = branch\n                    }.call()\n                }\n            }\n        } catch (ex: Exception) {\n            when (ex) {\n                is GitAPIException, is IOException, is SourceException -> {\n                    if (optional) {\n                        return EmptyMapSource().apply(extendContext)\n                    } else {\n                        throw ex\n                    }\n                }\n                else -> throw ex\n            }\n        }\n        file(Paths.get(directory.path, file).toFile(), optional).apply(extendContext)\n    }\n}\n"
  },
  {
    "path": "konf-git/src/test/kotlin/com/uchuhimo/konf/source/DefaultGitLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempDirectory\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.eclipse.jgit.api.Git\nimport org.eclipse.jgit.lib.Constants\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.nio.file.Paths\nimport java.util.concurrent.TimeUnit\n\nobject DefaultGitLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from git repository\") {\n            tempDirectory().let { dir ->\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    Paths.get(dir.path, \"source.properties\").toFile().writeText(propertiesContent)\n                    git.add().apply {\n                        addFilepattern(\"source.properties\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                val config = subject.git(repo.toString(), \"source.properties\")\n                it(\"should load as auto-detected file format\") {\n                    assertThat(config[item], equalTo(\"properties\"))\n                }\n            }\n        }\n        mapOf(\n            \"load from watched git repository\" to { loader: DefaultLoaders, repo: String ->\n                loader.watchGit(\n                    repo,\n                    \"source.properties\",\n                    period = 1,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential\n                )\n            },\n            \"load from watched git repository to the given directory\" to { loader: DefaultLoaders, repo: String ->\n                loader.watchGit(\n                    repo,\n                    \"source.properties\",\n                    dir = tempDirectory(prefix = \"local_git_repo\").path,\n                    branch = Constants.HEAD,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential,\n                    optional = false\n                )\n            }\n        ).forEach { (description, func) ->\n            on(description) {\n                tempDirectory(prefix = \"remote_git_repo\", suffix = \".git\").let { dir ->\n                    val file = Paths.get(dir.path, \"source.properties\").toFile()\n                    Git.init().apply {\n                        setDirectory(dir)\n                    }.call().use { git ->\n                        file.writeText(propertiesContent)\n                        git.add().apply {\n                            addFilepattern(\"source.properties\")\n                        }.call()\n                        git.commit().apply {\n                            message = \"init commit\"\n                        }.call()\n                    }\n                    val repo = dir.toURI()\n                    val config = func(subject, repo.toString())\n                    val originalValue = config[item]\n                    file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n                    Git.open(dir).use { git ->\n                        git.add().apply {\n                            addFilepattern(\"source.properties\")\n                        }.call()\n                        git.commit().apply {\n                            message = \"update value\"\n                        }.call()\n                    }\n                    runBlocking(Dispatchers.Sequential) {\n                        delay(TimeUnit.SECONDS.toMillis(1))\n                    }\n                    val newValue = config[item]\n                    it(\"should load as auto-detected file format\") {\n                        assertThat(originalValue, equalTo(\"properties\"))\n                    }\n                    it(\"should load new value after file content in git repository has been changed\") {\n                        assertThat(newValue, equalTo(\"newValue\"))\n                    }\n                }\n            }\n        }\n        on(\"load from watched git repository with listener\") {\n            tempDirectory(prefix = \"remote_git_repo\", suffix = \".git\").let { dir ->\n                val file = Paths.get(dir.path, \"source.properties\").toFile()\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    file.writeText(propertiesContent)\n                    git.add().apply {\n                        addFilepattern(\"source.properties\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                var newValue = \"\"\n                val config = subject.watchGit(\n                    repo.toString(),\n                    \"source.properties\",\n                    period = 1,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential\n                ) { config, _ ->\n                    newValue = config[item]\n                }\n                val originalValue = config[item]\n                file.writeText(propertiesContent.replace(\"properties\", \"newValue\"))\n                Git.open(dir).use { git ->\n                    git.add().apply {\n                        addFilepattern(\"source.properties\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"update value\"\n                    }.call()\n                }\n                runBlocking(Dispatchers.Sequential) {\n                    delay(TimeUnit.SECONDS.toMillis(1))\n                }\n                it(\"should load as auto-detected file format\") {\n                    assertThat(originalValue, equalTo(\"properties\"))\n                }\n                it(\"should load new value after file content in git repository has been changed\") {\n                    assertThat(newValue, equalTo(\"newValue\"))\n                }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-git/src/test/kotlin/com/uchuhimo/konf/source/DefaultGitProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempDirectory\nimport org.eclipse.jgit.api.Git\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.nio.file.Paths\n\nobject DefaultGitProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provider source from git repository\") {\n            tempDirectory().let { dir ->\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    Paths.get(dir.path, \"source.properties\").toFile().writeText(propertiesContent)\n                    git.add().apply {\n                        addFilepattern(\"source.properties\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                val config = subject.git(repo.toString(), \"source.properties\").toConfig()\n                it(\"should provide as auto-detected file format\") {\n                    assertThat(config[item], equalTo(\"properties\"))\n                }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-git/src/test/kotlin/com/uchuhimo/konf/source/GitLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport com.uchuhimo.konf.tempDirectory\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.eclipse.jgit.api.Git\nimport org.eclipse.jgit.lib.Constants\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.nio.file.Paths\nimport java.util.concurrent.TimeUnit\n\nobject GitLoaderSpec : SubjectSpek<Loader>({\n    val parentConfig = Config {\n        addSpec(SourceType)\n    }\n    subject {\n        Loader(parentConfig, PropertiesProvider)\n    }\n\n    given(\"a loader\") {\n        on(\"load from git repository\") {\n            tempDirectory().let { dir ->\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    Paths.get(dir.path, \"test\").toFile().writeText(\"type = git\")\n                    git.add().apply {\n                        addFilepattern(\"test\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                val config = subject.git(repo.toString(), \"test\")\n                it(\"should return a config which contains value in git repository\") {\n                    assertThat(config[SourceType.type], equalTo(\"git\"))\n                }\n            }\n        }\n        mapOf(\n            \"load from watched git repository\" to { loader: Loader, repo: String ->\n                loader.watchGit(\n                    repo,\n                    \"test\",\n                    period = 1,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential\n                )\n            },\n            \"load from watched git repository to the given directory\" to { loader: Loader, repo: String ->\n                loader.watchGit(\n                    repo,\n                    \"test\",\n                    dir = tempDirectory(prefix = \"local_git_repo\").path,\n                    branch = Constants.HEAD,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential,\n                    optional = false\n                )\n            }\n        ).forEach { (description, func) ->\n            on(description) {\n                tempDirectory(prefix = \"remote_git_repo\", suffix = \".git\").let { dir ->\n                    val file = Paths.get(dir.path, \"test\").toFile()\n                    Git.init().apply {\n                        setDirectory(dir)\n                    }.call().use { git ->\n                        file.writeText(\"type = originalValue\")\n                        git.add().apply {\n                            addFilepattern(\"test\")\n                        }.call()\n                        git.commit().apply {\n                            message = \"init commit\"\n                        }.call()\n                    }\n                    val repo = dir.toURI()\n                    val config = func(subject, repo.toString())\n                    val originalValue = config[SourceType.type]\n                    file.writeText(\"type = newValue\")\n                    Git.open(dir).use { git ->\n                        git.add().apply {\n                            addFilepattern(\"test\")\n                        }.call()\n                        git.commit().apply {\n                            message = \"update value\"\n                        }.call()\n                    }\n                    runBlocking(Dispatchers.Sequential) {\n                        delay(TimeUnit.SECONDS.toMillis(1))\n                    }\n                    val newValue = config[SourceType.type]\n                    it(\"should return a config which contains value in git repository\") {\n                        assertThat(originalValue, equalTo(\"originalValue\"))\n                    }\n                    it(\"should load new value when content of git repository has been changed\") {\n                        assertThat(newValue, equalTo(\"newValue\"))\n                    }\n                }\n            }\n        }\n        on(\"load from watched git repository with listener\") {\n            tempDirectory(prefix = \"remote_git_repo\", suffix = \".git\").let { dir ->\n                val file = Paths.get(dir.path, \"test\").toFile()\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    file.writeText(\"type = originalValue\")\n                    git.add().apply {\n                        addFilepattern(\"test\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                var newValue = \"\"\n                val config = subject.watchGit(\n                    repo.toString(),\n                    \"test\",\n                    period = 1,\n                    unit = TimeUnit.SECONDS,\n                    context = Dispatchers.Sequential\n                ) { config, _ ->\n                    newValue = config[SourceType.type]\n                }\n                val originalValue = config[SourceType.type]\n                file.writeText(\"type = newValue\")\n                Git.open(dir).use { git ->\n                    git.add().apply {\n                        addFilepattern(\"test\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"update value\"\n                    }.call()\n                }\n                runBlocking(Dispatchers.Sequential) {\n                    delay(TimeUnit.SECONDS.toMillis(1))\n                }\n                it(\"should return a config which contains value in git repository\") {\n                    assertThat(originalValue, equalTo(\"originalValue\"))\n                }\n                it(\"should load new value when content of git repository has been changed\") {\n                    assertThat(newValue, equalTo(\"newValue\"))\n                }\n            }\n        }\n    }\n})\n\nprivate object SourceType : ConfigSpec(\"\") {\n    val type by required<String>()\n}\n"
  },
  {
    "path": "konf-git/src/test/kotlin/com/uchuhimo/konf/source/GitProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.properties.PropertiesProvider\nimport com.uchuhimo.konf.tempDirectory\nimport org.eclipse.jgit.api.Git\nimport org.eclipse.jgit.lib.Constants\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.nio.file.Paths\nimport kotlin.test.assertTrue\n\nobject GitProviderSpec : SubjectSpek<Provider>({\n    subject { PropertiesProvider }\n\n    given(\"a provider\") {\n        on(\"create source from git repository\") {\n            tempDirectory().let { dir ->\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    Paths.get(dir.path, \"test\").toFile().writeText(\"type = git\")\n                    git.add().apply {\n                        addFilepattern(\"test\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                val repo = dir.toURI()\n                val source = subject.git(repo.toString(), \"test\")\n                it(\"should create from the specified git repository\") {\n                    assertThat(source.info[\"repo\"], equalTo(repo.toString()))\n                    assertThat(source.info[\"file\"], equalTo(\"test\"))\n                    assertThat(source.info[\"branch\"], equalTo(Constants.HEAD))\n                }\n                it(\"should return a source which contains value in git repository\") {\n                    assertThat(source[\"type\"].asValue<String>(), equalTo(\"git\"))\n                }\n            }\n        }\n        on(\"create source from invalid git repository\") {\n            tempDirectory().let { dir ->\n                Git.init().apply {\n                    setDirectory(dir)\n                }.call().use { git ->\n                    Paths.get(dir.path, \"test\").toFile().writeText(\"type = git\")\n                    git.add().apply {\n                        addFilepattern(\"test\")\n                    }.call()\n                    git.commit().apply {\n                        message = \"init commit\"\n                    }.call()\n                }\n                it(\"should throw InvalidRemoteRepoException\") {\n                    assertThat(\n                        { subject.git(tempDirectory().path, \"test\", dir = dir.path) },\n                        throws<InvalidRemoteRepoException>()\n                    )\n                }\n                it(\"should return an empty source if optional\") {\n                    assertTrue {\n                        subject.git(tempDirectory().path, \"test\", dir = dir.path, optional = true).tree.children.isEmpty()\n                    }\n                }\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-hocon/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    implementation(\"com.typesafe\", \"config\", Versions.hocon)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/DefaultHoconLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.hocon.HoconProvider\n\n/**\n * Loader for HOCON source.\n */\nval DefaultLoaders.hocon get() = Loader(config, HoconProvider.orMapped())\n"
  },
  {
    "path": "konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/DefaultHoconProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.hocon.HoconProvider\n\n/**\n * Provider for HOCON source.\n */\nval DefaultProviders.hocon get() = HoconProvider\n"
  },
  {
    "path": "konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/hocon/HoconProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.typesafe.config.ConfigFactory\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.RegisterExtension\nimport com.uchuhimo.konf.source.Source\nimport java.io.InputStream\nimport java.io.Reader\n\n/**\n * Provider for HOCON source.\n */\n@RegisterExtension([\"conf\"])\nobject HoconProvider : Provider {\n    override fun reader(reader: Reader): Source =\n        HoconSource(ConfigFactory.parseReader(reader).resolve())\n\n    override fun inputStream(inputStream: InputStream): Source {\n        inputStream.reader().use {\n            return reader(it)\n        }\n    }\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/hocon/HoconSource.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.typesafe.config.Config\nimport com.typesafe.config.ConfigList\nimport com.typesafe.config.ConfigObject\nimport com.typesafe.config.ConfigValue\nimport com.typesafe.config.ConfigValueType\nimport com.uchuhimo.konf.ContainerNode\nimport com.uchuhimo.konf.TreeNode\nimport com.uchuhimo.konf.source.ListSourceNode\nimport com.uchuhimo.konf.source.NullSourceNode\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.SourceInfo\nimport com.uchuhimo.konf.source.ValueSourceNode\n\nprivate fun ConfigValue.toTree(): TreeNode {\n    return when (valueType()!!) {\n        ConfigValueType.NULL -> NullSourceNode\n        ConfigValueType.BOOLEAN, ConfigValueType.NUMBER, ConfigValueType.STRING -> ValueSourceNode(unwrapped())\n        ConfigValueType.LIST -> ListSourceNode(\n            mutableListOf<TreeNode>().apply {\n                for (value in (this@toTree as ConfigList)) {\n                    add(value.toTree())\n                }\n            }\n        )\n        ConfigValueType.OBJECT -> ContainerNode(\n            mutableMapOf<String, TreeNode>().apply {\n                for ((key, value) in (this@toTree as ConfigObject)) {\n                    put(key, value.toTree())\n                }\n            }\n        )\n    }\n}\n\n/**\n * Source from a HOCON value.\n */\nclass HoconSource(\n    val value: Config\n) : Source {\n    override val info: SourceInfo = SourceInfo(\"type\" to \"HOCON\")\n\n    override val tree: TreeNode = value.root().toTree()\n}\n"
  },
  {
    "path": "konf-hocon/src/main/kotlin/com/uchuhimo/konf/source/hocon/HoconWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.typesafe.config.ConfigRenderOptions\nimport com.typesafe.config.ConfigValueFactory\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport java.io.OutputStream\n\n/**\n * Writer for HOCON source.\n */\nclass HoconWriter(val config: Config) : Writer {\n    private val renderOpts = ConfigRenderOptions.defaults()\n        .setOriginComments(false)\n        .setComments(false)\n        .setJson(false)\n\n    override fun toWriter(writer: java.io.Writer) {\n        writer.write(toText())\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        outputStream.writer().use {\n            toWriter(it)\n        }\n    }\n\n    override fun toText(): String {\n        return ConfigValueFactory.fromMap(config.toHierarchicalMap()).render(renderOpts)\n            .replace(\"\\n\", System.lineSeparator())\n    }\n}\n\n/**\n * Returns writer for HOCON source.\n */\nval Config.toHocon: Writer get() = HoconWriter(this)\n"
  },
  {
    "path": "konf-hocon/src/test/java/com/uchuhimo/konf/LoaderJavaApiTest.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\n\nimport com.uchuhimo.konf.source.hocon.HoconProvider;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\n\n@DisplayName(\"test Java API of loader\")\nclass LoaderJavaApiTest {\n  private Config config;\n\n  @BeforeEach\n  void initConfig() {\n    config = Configs.create();\n    config.addSpec(NetworkBufferInJava.spec);\n  }\n\n  @Test\n  @DisplayName(\"test fluent API to load from default loader\")\n  void loadFromDefaultLoader() {\n    final Config newConfig =\n        config\n            .from()\n            .source(HoconProvider.get())\n            .string(config.nameOf(NetworkBufferInJava.size) + \" = 1024\");\n    assertThat(newConfig.get(NetworkBufferInJava.size), equalTo(1024));\n  }\n}\n"
  },
  {
    "path": "konf-hocon/src/test/java/com/uchuhimo/konf/NetworkBufferInJava.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf;\n\npublic class NetworkBufferInJava {\n  public static final ConfigSpec spec = new ConfigSpec(\"network.buffer\");\n\n  public static final RequiredItem<Integer> size =\n      new RequiredItem<Integer>(spec, \"size\", \"size of buffer in KB\") {};\n}\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/DefaultHoconLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultHoconLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from HOCON file\") {\n            val config = subject.file(tempFileOf(hoconContent, suffix = \".conf\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"conf\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/DefaultHoconProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultHoconProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provider source from HOCON file\") {\n            val config = subject.file(tempFileOf(hoconContent, suffix = \".conf\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"conf\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/hocon/HoconProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject HoconProviderSpec : SubjectSpek<HoconProvider>({\n    subject { HoconProvider }\n\n    given(\"a HOCON provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"type = reader\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"HOCON\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"type = inputStream\").inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"HOCON\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject HoconProviderInJavaSpec : SubjectSpek<HoconProvider>({\n    subject { HoconProvider.get() }\n\n    itBehavesLike(HoconProviderSpec)\n})\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/hocon/HoconSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport com.uchuhimo.konf.source.hocon\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject HoconSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.hocon.resource(\"source/source.conf\")\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject HoconSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.hocon.resource(\"source/source.conf\")\n        val hocon = config.toHocon.toText()\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.hocon.string(hocon)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/hocon/HoconSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asSource\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.toPath\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport kotlin.test.assertNull\nimport kotlin.test.assertTrue\n\nobject HoconSourceSpec : SubjectSpek<HoconSource>({\n    subject { HoconProvider.string(\"key = 1\") as HoconSource }\n\n    given(\"a HOCON source\") {\n        on(\"get underlying config\") {\n            it(\"should return corresponding config\") {\n                val config = subject.value\n                assertThat(config.getInt(\"key\"), equalTo(1))\n            }\n        }\n        on(\"get an existed key\") {\n            it(\"should contain the key\") {\n                assertTrue(\"key\".toPath() in subject)\n            }\n            it(\"should contain the corresponding value\") {\n                assertThat(subject[\"key\".toPath()].asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"get an non-existed key\") {\n            it(\"should not contain the key\") {\n                assertTrue(\"invalid\".toPath() !in subject)\n            }\n            it(\"should not contain the corresponding value\") {\n                assertNull(subject.getOrNull(\"invalid\".toPath()))\n            }\n        }\n        on(\"use substitutions in source\") {\n            val source = HoconProvider.string(\n                \"\"\"\n                key1 = 1\n                key2 = ${'$'}{key1}\n                \"\"\".trimIndent()\n            )\n            it(\"should resolve the key\") {\n                assertThat(source[\"key2\"].asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"use substitutions in source when variables are in other sources\") {\n            val source = (\n                HoconProvider.string(\n                    \"\"\"\n                key1 = \"1\"\n                key2 = ${'$'}{key1}\n                key3 = \"${'$'}{key4}\"\n                key5 = \"${'$'}{key1}+${'$'}{key4}\"\n                key6 = \"${\"$$\"}{key1}\"\n                    \"\"\".trimIndent()\n                ) +\n                    mapOf(\"key4\" to \"4\", \"key1\" to \"2\").asSource()\n                ).substituted().substituted()\n            it(\"should resolve the key\") {\n                assertThat(source[\"key2\"].asValue<Int>(), equalTo(1))\n                assertThat(source[\"key3\"].asValue<Int>(), equalTo(4))\n                assertThat(source[\"key5\"].asValue<String>(), equalTo(\"2+4\"))\n                assertThat(source[\"key6\"].asValue<String>(), equalTo(\"\\${key1}\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/hocon/HoconValueSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.NoSuchPathException\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.asValue\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport kotlin.test.assertTrue\n\nobject HoconValueSourceSpec : Spek({\n    given(\"a HOCON value source\") {\n        on(\"treat object value source as HOCON source\") {\n            val source = \"{key = 1}\".toHoconValueSource()\n            it(\"should contain specified value\") {\n                assertTrue(\"key\" in source)\n                assertThat(source[\"key\"].asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"treat number value source as HOCON source\") {\n            val source = \"1\".toHoconValueSource()\n            it(\"should throw NoSuchPathException\") {\n                assertThat({ source[\"key\"] }, throws<NoSuchPathException>())\n            }\n        }\n        on(\"get integer from integer value source\") {\n            it(\"should succeed\") {\n                assertThat(\"1\".toHoconValueSource().asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"get long from long value source\") {\n            val source = \"123456789000\".toHoconValueSource()\n            it(\"should succeed\") {\n                assertThat(source.asValue<Long>(), equalTo(123_456_789_000L))\n            }\n        }\n        on(\"get long from integer value source\") {\n            val source = \"1\".toHoconValueSource()\n            it(\"should succeed\") {\n                assertThat(source.asValue<Long>(), equalTo(1L))\n            }\n        }\n        on(\"get double from double value source\") {\n            val source = \"1.5\".toHoconValueSource()\n            it(\"should succeed\") {\n                assertThat(source.asValue<Double>(), equalTo(1.5))\n            }\n        }\n        on(\"get double from int value source\") {\n            val source = \"1\".toHoconValueSource()\n            it(\"should succeed\") {\n                assertThat(source.asValue<Double>(), equalTo(1.0))\n            }\n        }\n        on(\"get double from long value source\") {\n            val source = \"123456789000\".toHoconValueSource()\n            it(\"should succeed\") {\n                assertThat(source.asValue<Double>(), equalTo(123456789000.0))\n            }\n        }\n    }\n})\n\nprivate fun String.toHoconValueSource(): Source {\n    return HoconProvider.string(\"key = $this\")[\"key\"]\n}\n"
  },
  {
    "path": "konf-hocon/src/test/kotlin/com/uchuhimo/konf/source/hocon/HoconWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.hocon\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject HoconWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toHocon\n    }\n\n    given(\"a writer\") {\n        val expectedString = \"key=value\" + System.lineSeparator()\n        on(\"save to string\") {\n            val string = subject.toText()\n            it(\"should return a string which contains content from config\") {\n                assertThat(string, equalTo(expectedString))\n            }\n        }\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-hocon/src/test/resources/source/source.conf",
    "content": "level1 {\n  level2 {\n    empty = \"null\"\n    literalEmpty = null\n    present = 1\n\n    boolean = false\n\n    int = 1\n    short = 2\n    byte = 3\n    bigInteger = 4\n    long = 4\n\n    double = 1.5\n    float = -1.5\n    bigDecimal = 1.5\n\n    char = \"a\"\n\n    string = string\n    offsetTime = \"10:15:30+01:00\"\n    offsetDateTime = \"2007-12-03T10:15:30+01:00\"\n    zonedDateTime = \"2007-12-03T10:15:30+01:00[Europe/Paris]\"\n    localDate = 2007-12-03\n    localTime = \"10:15:30\"\n    localDateTime = \"2007-12-03T10:15:30\"\n    date = \"2007-12-03T10:15:30Z\"\n    year = \"2007\"\n    yearMonth = 2007-12\n    instant = \"2007-12-03T10:15:30.00Z\"\n    duration = P2DT3H4M\n    simpleDuration = 200millis\n    size = 10k\n\n    enum = LABEL2\n\n    array {\n      boolean = [true, false]\n      byte = [1, 2, 3]\n      short = [1, 2, 3]\n      int = [1, 2, 3]\n      long = [4, 5, 6]\n      float = [-1, 0.0, 1]\n      double = [-1, 0.0, 1]\n      char = [a, b, c]\n\n      object {\n        boolean = [true, false]\n        int = [1, 2, 3]\n        string = [one, two, three]\n        enum = [LABEL1, LABEL2, LABEL3]\n      }\n    }\n\n    list = [1, 2, 3]\n    mutableList = [1, 2, 3]\n    listOfList = [[1, 2], [3, 4]]\n    set = [1, 2, 1]\n    sortedSet = [2, 1, 1, 3]\n\n    map = {a = 1, b = 2, c = 3}\n    intMap = {1 = a, 2 = b, 3 = c}\n    sortedMap = {c = 3, b = 2, a = 1}\n    listOfMap = [\n      {a = 1, b = 2}\n      {a = 3, b = 4}\n    ]\n\n    nested = [[[{a = 1}]]]\n\n    pair = {first = 1, second = 2}\n\n    clazz = {\n      empty = \"null\"\n      literalEmpty = null\n      present = 1\n      boolean = false\n      int = 1\n      short = 2\n      byte = 3\n      bigInteger = 4\n      long = 4\n      double = 1.5\n      float = -1.5\n      bigDecimal = 1.5\n      char = \"a\"\n      string = string\n      offsetTime = \"10:15:30+01:00\"\n      offsetDateTime = \"2007-12-03T10:15:30+01:00\"\n      zonedDateTime = \"2007-12-03T10:15:30+01:00[Europe/Paris]\"\n      localDate = 2007-12-03\n      localTime = \"10:15:30\"\n      localDateTime = \"2007-12-03T10:15:30\"\n      date = \"2007-12-03T10:15:30Z\"\n      year = \"2007\"\n      yearMonth = 2007-12\n      instant = \"2007-12-03T10:15:30.00Z\"\n      duration = P2DT3H4M\n      simpleDuration = 200millis\n      size = 10k\n      enum = LABEL2\n      booleanArray = [true, false]\n      nested = [[[{a = 1}]]]\n    }\n  }\n}\n"
  },
  {
    "path": "konf-hocon/src/testFixtures/kotlin/com/uchuhimo/konf/source/HoconTestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nconst val hoconContent = \"source.test.type = conf\"\n"
  },
  {
    "path": "konf-js/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    implementation(\"org.graalvm.sdk\", \"graal-sdk\", Versions.graal)\n    implementation(\"org.graalvm.js\", \"js\", Versions.graal)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-js/src/main/kotlin/com/uchuhimo/konf/source/DefaultJsLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.js.JsProvider\n\n/**\n * Loader for JavaScript source.\n */\nval DefaultLoaders.js get() = Loader(config, JsProvider.orMapped())\n"
  },
  {
    "path": "konf-js/src/main/kotlin/com/uchuhimo/konf/source/DefaultJsProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.js.JsProvider\n\n/**\n * Provider for JavaScript source.\n */\nval DefaultProviders.js get() = JsProvider\n"
  },
  {
    "path": "konf-js/src/main/kotlin/com/uchuhimo/konf/source/js/JsProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.js\n\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.RegisterExtension\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.json.JsonProvider\nimport org.graalvm.polyglot.Context\nimport java.io.InputStream\nimport java.io.Reader\nimport java.util.stream.Collectors\n\n/**\n * Provider for JavaScript source.\n */\n@RegisterExtension([\"js\"])\nobject JsProvider : Provider {\n    override fun reader(reader: Reader): Source {\n        val sourceString = reader.buffered().lines().collect(Collectors.joining(\"\\n\"))\n        Context.create().use { context ->\n            val value = context.eval(\"js\", sourceString)\n            context.getBindings(\"js\").putMember(\"source\", value)\n            val jsonString = context.eval(\"js\", \"JSON.stringify(source)\").asString()\n            return JsonProvider.string(jsonString).apply {\n                this.info[\"type\"] = \"JavaScript\"\n            }\n        }\n    }\n\n    override fun inputStream(inputStream: InputStream): Source {\n        inputStream.reader().use {\n            return reader(it)\n        }\n    }\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-js/src/main/kotlin/com/uchuhimo/konf/source/js/JsWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.js\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport java.io.OutputStream\nimport java.util.regex.Pattern\n\n/**\n * Writer for JavaScript source.\n */\nclass JsWriter(val config: Config) : Writer {\n    override fun toWriter(writer: java.io.Writer) {\n        val jsonOutput = config.mapper\n            .writerWithDefaultPrettyPrinter()\n            .writeValueAsString(config.toHierarchicalMap())\n        val pattern = Pattern.compile(\"(\\\")(.*)(\\\"\\\\s*):\")\n        val jsOutput = pattern.matcher(jsonOutput).replaceAll(\"$2:\")\n        writer.write(\"($jsOutput)\")\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        outputStream.writer().use {\n            toWriter(it)\n        }\n    }\n}\n\n/**\n * Returns writer for JavaScript source.\n */\nval Config.toJs: Writer get() = JsWriter(this)\n"
  },
  {
    "path": "konf-js/src/test/kotlin/com/uchuhimo/konf/source/DefaultJsLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultJsLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from JavaScript file\") {\n            val config = subject.file(tempFileOf(jsContent, suffix = \".js\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"js\"))\n            }\n        }\n    }\n})\n\n//language=JavaScript\nconst val jsContent =\n    \"\"\"\n({\n  source: {\n    test: {\n      type: \"js\"\n    }\n  }\n})\n\"\"\"\n"
  },
  {
    "path": "konf-js/src/test/kotlin/com/uchuhimo/konf/source/DefaultJsProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultJsProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provider source from JavaScript file\") {\n            val config = subject.file(tempFileOf(jsContent, suffix = \".js\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"js\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-js/src/test/kotlin/com/uchuhimo/konf/source/js/JsProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.js\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject JsProviderSpec : SubjectSpek<JsProvider>({\n    subject { JsProvider }\n\n    given(\"a JavaScript provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"({type: 'reader'})\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"JavaScript\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"({type: 'inputStream'})\").inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"JavaScript\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"({})\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject JsProviderInJavaSpec : SubjectSpek<JsProvider>({\n    subject { JsProvider.get() }\n\n    itBehavesLike(JsProviderSpec)\n})\n"
  },
  {
    "path": "konf-js/src/test/kotlin/com/uchuhimo/konf/source/js/JsSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.js\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport com.uchuhimo.konf.source.js\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject JsSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.js.resource(\"source/source.js\")\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject JsSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.js.resource(\"source/source.js\")\n        val js = config.toJs.toText()\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.js.string(js)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-js/src/test/kotlin/com/uchuhimo/konf/source/js/JsWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.js\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject JsWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toJs\n    }\n\n    given(\"a writer\") {\n        val expectedString =\n            \"\"\"({\n            |  key: \"value\"\n            |})\"\"\".trimMargin().replace(\"\\n\", System.lineSeparator())\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-js/src/test/resources/source/source.js",
    "content": "({\n    level1: {\n        level2: {\n            empty: null,\n            literalEmpty: null,\n            present: 1,\n            boolean: false,\n            int: 1,\n            short: 2,\n            byte: 3,\n            bigInteger: 4,\n            long: 4,\n            double: 1.5,\n            float: -1.5,\n            bigDecimal: 1.5,\n            char: \"a\",\n            string: \"string\",\n            offsetTime: \"10:15:30+01:00\",\n            offsetDateTime: \"2007-12-03T10:15:30+01:00\",\n            zonedDateTime: \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n            localDate: \"2007-12-03\",\n            localTime: \"10:15:30\",\n            localDateTime: \"2007-12-03T10:15:30\",\n            date: \"2007-12-03T10:15:30Z\",\n            year: \"2007\",\n            yearMonth: \"2007-12\",\n            instant: \"2007-12-03T10:15:30.00Z\",\n            duration: \"P2DT3H4M\",\n            simpleDuration: \"200millis\",\n            size: \"10k\",\n            enum: \"LABEL2\",\n            array: {\n                boolean: [\n                    true,\n                    false\n                ],\n                byte: [\n                    1,\n                    2,\n                    3\n                ],\n                short: [\n                    1,\n                    2,\n                    3\n                ],\n                int: [\n                    1,\n                    2,\n                    3\n                ],\n                long: [\n                    4,\n                    5,\n                    6\n                ],\n                float: [\n                    -1.0,\n                    0.0,\n                    1.0\n                ],\n                double: [\n                    -1.0,\n                    0.0,\n                    1.0\n                ],\n                char: [\n                    \"a\",\n                    \"b\",\n                    \"c\"\n                ],\n                object: {\n                    boolean: [\n                        true,\n                        false\n                    ],\n                    int: [\n                        1,\n                        2,\n                        3\n                    ],\n                    string: [\n                        \"one\",\n                        \"two\",\n                        \"three\"\n                    ],\n                    enum: [\n                        \"LABEL1\",\n                        \"LABEL2\",\n                        \"LABEL3\"\n                    ]\n                }\n            },\n            list: [\n                1,\n                2,\n                3\n            ],\n            mutableList: [\n                1,\n                2,\n                3\n            ],\n            listOfList: [\n                [\n                    1,\n                    2\n                ],\n                [\n                    3,\n                    4\n                ]\n            ],\n            set: [\n                1,\n                2,\n                1\n            ],\n            sortedSet: [\n                2,\n                1,\n                1,\n                3\n            ],\n            map: {\n                a: 1,\n                b: 2,\n                c: 3\n            },\n            intMap: {\n                1: \"a\",\n                2: \"b\",\n                3: \"c\"\n            },\n            sortedMap: {\n                c: 3,\n                b: 2,\n                a: 1\n            },\n            listOfMap: [\n                {\n                    a: 1,\n                    b: 2\n                },\n                {\n                    a: 3,\n                    b: 4\n                }\n            ],\n            nested: [\n                [\n                    [\n                        {\n                            a: 1\n                        }\n                    ]\n                ]\n            ],\n            pair: {\n                first: 1,\n                second: 2\n            },\n            clazz: {\n                empty: null,\n                literalEmpty: null,\n                present: 1,\n                boolean: false,\n                int: 1,\n                short: 2,\n                byte: 3,\n                bigInteger: 4,\n                long: 4,\n                double: 1.5,\n                float: -1.5,\n                bigDecimal: 1.5,\n                char: \"a\",\n                string: \"string\",\n                offsetTime: \"10:15:30+01:00\",\n                offsetDateTime: \"2007-12-03T10:15:30+01:00\",\n                zonedDateTime: \"2007-12-03T10:15:30+01:00[Europe/Paris]\",\n                localDate: \"2007-12-03\",\n                localTime: \"10:15:30\",\n                localDateTime: \"2007-12-03T10:15:30\",\n                date: \"2007-12-03T10:15:30Z\",\n                year: \"2007\",\n                yearMonth: \"2007-12\",\n                instant: \"2007-12-03T10:15:30.00Z\",\n                duration: \"P2DT3H4M\",\n                simpleDuration: \"200millis\",\n                size: \"10k\",\n                enum: \"LABEL2\",\n                booleanArray: [\n                    true,\n                    false\n                ],\n                nested: [\n                    [\n                        [\n                            {\n                                a: 1\n                            }\n                        ]\n                    ]\n                ]\n            }\n        }\n    }\n})"
  },
  {
    "path": "konf-toml/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    implementation(\"com.moandjiezana.toml\", \"toml4j\", Versions.toml4j)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-toml/src/main/kotlin/com/moandjiezana/toml/Toml4jWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.moandjiezana.toml\n\nimport com.moandjiezana.toml.BooleanValueReaderWriter.BOOLEAN_VALUE_READER_WRITER\nimport com.moandjiezana.toml.DateValueReaderWriter.DATE_VALUE_READER_WRITER\nimport com.moandjiezana.toml.NumberValueReaderWriter.NUMBER_VALUE_READER_WRITER\nimport com.moandjiezana.toml.StringValueReaderWriter.STRING_VALUE_READER_WRITER\nimport java.io.IOException\nimport java.io.StringWriter\nimport java.io.Writer\nimport java.util.TimeZone\nimport java.util.regex.Pattern\n\n/**\n * <p>Converts Objects to TOML</p>\n *\n * <p>An input Object can comprise arbitrarily nested combinations of Java primitive types,\n * other {@link Object}s, {@link Map}s, {@link List}s, and Arrays. {@link Object}s and {@link Map}s\n * are output to TOML tables, and {@link List}s and Array to TOML arrays.</p>\n *\n * <p>Example usage:</p>\n * <pre><code>\n * class AClass {\n *   int anInt = 1;\n *   int[] anArray = { 2, 3 };\n * }\n *\n * String tomlString = new TomlWriter().write(new AClass());\n * </code></pre>\n */\nclass Toml4jWriter {\n    /**\n     * Write an Object into TOML String.\n     *\n     * @param from the object to be written\n     * @return a string containing the TOML representation of the given Object\n     */\n    fun write(from: Any): String {\n        try {\n            val output = StringWriter()\n            write(from, output)\n            return output.toString()\n        } catch (e: IOException) {\n            throw RuntimeException(e)\n        }\n    }\n\n    /**\n     * Write an Object in TOML to a [Writer]. You MUST ensure that the [Writer]s's encoding is set to UTF-8 for the TOML to be valid.\n     *\n     * @param from the object to be written. Can be a Map or a custom type. Must not be null.\n     * @param target the Writer to which TOML will be written. The Writer is not closed.\n     * @throws IOException if target.write() fails\n     * @throws IllegalArgumentException if from is of an invalid type\n     */\n    @Throws(IOException::class)\n    fun write(from: Any, target: Writer) {\n        val valueWriter = Toml4jValueWriters.findWriterFor(from)\n        if (valueWriter === NewMapValueWriter) {\n            val context = WriterContext(\n                IndentationPolicy(0, 0, 0),\n                DatePolicy(TimeZone.getTimeZone(\"UTC\"), false),\n                target\n            )\n            valueWriter.write(from, context)\n        } else {\n            throw IllegalArgumentException(\"An object of class \" + from.javaClass.simpleName + \" cannot produce valid TOML. Please pass in a Map or a custom type.\")\n        }\n    }\n}\n\ninternal object Toml4jValueWriters {\n\n    fun findWriterFor(value: Any): ValueWriter {\n        for (valueWriter in VALUE_WRITERS) {\n            if (valueWriter.canWrite(value)) {\n                return valueWriter\n            }\n        }\n        return NewMapValueWriter\n    }\n\n    private val VALUE_WRITERS = arrayOf<ValueWriter>(\n        STRING_VALUE_READER_WRITER,\n        NUMBER_VALUE_READER_WRITER,\n        BOOLEAN_VALUE_READER_WRITER,\n        DATE_VALUE_READER_WRITER,\n        NewMapValueWriter,\n        NewArrayValueWriter\n    )\n}\n\ninternal object NewArrayValueWriter : ArrayValueWriter() {\n    override fun canWrite(value: Any?): Boolean = isArrayish(value)\n\n    override fun write(o: Any, context: WriterContext) {\n        val values = normalize(o)\n\n        context.write('[')\n        context.writeArrayDelimiterPadding()\n\n        var first = true\n        var firstWriter: ValueWriter? = null\n\n        for (value in values) {\n            if (first) {\n                firstWriter = Toml4jValueWriters.findWriterFor(value!!)\n                first = false\n            } else {\n                val writer = Toml4jValueWriters.findWriterFor(value!!)\n                if (writer !== firstWriter) {\n                    throw IllegalStateException(\n                        context.contextPath +\n                            \": cannot write a heterogeneous array; first element was of type \" + firstWriter +\n                            \" but found \" + writer\n                    )\n                }\n                context.write(\", \")\n            }\n\n            val writer = Toml4jValueWriters.findWriterFor(value)\n            val isNestedOldValue = NewMapValueWriter.isNested\n            if (writer == NewMapValueWriter) {\n                NewMapValueWriter.isNested = true\n            }\n            writer.write(value, context)\n            if (writer == NewMapValueWriter) {\n                NewMapValueWriter.isNested = isNestedOldValue\n            }\n        }\n\n        context.writeArrayDelimiterPadding()\n        context.write(']')\n    }\n}\n\ninternal object NewMapValueWriter : ValueWriter {\n\n    override fun canWrite(value: Any): Boolean {\n        return value is Map<*, *>\n    }\n\n    var isNested: Boolean = false\n\n    override fun write(value: Any, context: WriterContext) {\n        val from = value as Map<*, *>\n\n        if (hasPrimitiveValues(from)) {\n            if (isNested) {\n                context.indent()\n                context.write(\"{\\n\")\n            } else {\n                context.writeKey()\n            }\n        }\n\n        // Render primitive types and arrays of primitive first so they are\n        // grouped under the same table (if there is one)\n        for ((key, value1) in from) {\n            val fromValue = value1 ?: continue\n\n            val valueWriter = Toml4jValueWriters.findWriterFor(fromValue)\n            if (valueWriter.isPrimitiveType()) {\n                context.indent()\n                context.write(quoteKey(key!!)).write(\" = \")\n                valueWriter.write(fromValue, context)\n                if (isNested) {\n                    context.write(',')\n                }\n                context.write('\\n')\n            } else if (valueWriter === NewArrayValueWriter) {\n                context.setArrayKey(key.toString())\n                context.write(quoteKey(key!!)).write(\" = \")\n                valueWriter.write(fromValue, context)\n                if (isNested) {\n                    context.write(',')\n                }\n                context.write('\\n')\n            }\n        }\n\n        // Now render (sub)tables and arrays of tables\n        for (key in from.keys) {\n            val fromValue = from[key] ?: continue\n\n            val valueWriter = Toml4jValueWriters.findWriterFor(fromValue)\n            if (valueWriter === this) {\n                valueWriter.write(fromValue, context.pushTable(quoteKey(key!!)))\n            }\n        }\n        if (isNested) {\n            context.indent()\n            context.write(\"}\\n\")\n        }\n    }\n\n    override fun isPrimitiveType(): Boolean {\n        return false\n    }\n\n    private val REQUIRED_QUOTING_PATTERN = Pattern.compile(\"^.*[^A-Za-z\\\\d_-].*$\")\n\n    private fun quoteKey(key: Any): String {\n        var stringKey = key.toString()\n        val matcher = REQUIRED_QUOTING_PATTERN.matcher(stringKey)\n        if (matcher.matches()) {\n            stringKey = \"\\\"\" + stringKey + \"\\\"\"\n        }\n\n        return stringKey\n    }\n\n    private fun hasPrimitiveValues(values: Map<*, *>): Boolean {\n        for (key in values.keys) {\n            val fromValue = values[key] ?: continue\n\n            val valueWriter = Toml4jValueWriters.findWriterFor(fromValue)\n            if (valueWriter.isPrimitiveType() || valueWriter === NewArrayValueWriter) {\n                return true\n            }\n        }\n\n        return false\n    }\n}\n"
  },
  {
    "path": "konf-toml/src/main/kotlin/com/uchuhimo/konf/source/DefaultTomlLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.toml.TomlProvider\n\n/**\n * Loader for TOML source.\n */\nval DefaultLoaders.toml get() = Loader(config, TomlProvider.orMapped())\n"
  },
  {
    "path": "konf-toml/src/main/kotlin/com/uchuhimo/konf/source/DefaultTomlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.toml.TomlProvider\n\n/**\n * Provider for TOML source.\n */\nval DefaultProviders.toml get() = TomlProvider\n"
  },
  {
    "path": "konf-toml/src/main/kotlin/com/uchuhimo/konf/source/toml/TomlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.moandjiezana.toml.Toml\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.RegisterExtension\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.asSource\nimport java.io.InputStream\nimport java.io.Reader\n\n/**\n * Provider for TOML source.\n */\n@RegisterExtension([\"toml\"])\nobject TomlProvider : Provider {\n    override fun reader(reader: Reader): Source =\n        Toml().read(reader).toMap().asSource(type = \"TOML\")\n\n    override fun inputStream(inputStream: InputStream): Source =\n        Toml().read(inputStream).toMap().asSource(type = \"TOML\")\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-toml/src/main/kotlin/com/uchuhimo/konf/source/toml/TomlWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.moandjiezana.toml.Toml4jWriter\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport java.io.OutputStream\n\n/**\n * Writer for TOML source.\n */\nclass TomlWriter(val config: Config) : Writer {\n    private val toml4jWriter = Toml4jWriter()\n\n    override fun toWriter(writer: java.io.Writer) {\n        writer.write(toText())\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        outputStream.writer().use {\n            toWriter(it)\n        }\n    }\n\n    override fun toText(): String {\n        return toml4jWriter.write(config.toHierarchicalMap()).replace(\"\\n\", System.lineSeparator())\n    }\n}\n\n/**\n * Returns writer for TOML source.\n */\nval Config.toToml: Writer get() = TomlWriter(this)\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/DefaultTomlLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultTomlLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from TOML file\") {\n            val config = subject.file(tempFileOf(tomlContent, suffix = \".toml\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"toml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/DefaultTomlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultTomlProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provide source from TOML file\") {\n            val config = subject.file(tempFileOf(tomlContent, suffix = \".toml\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"toml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/toml/TomlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject TomlProviderSpec : SubjectSpek<TomlProvider>({\n    subject { TomlProvider }\n\n    given(\"a TOML provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"type = \\\"reader\\\"\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"TOML\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"type = \\\"inputStream\\\"\").inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"TOML\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject TomlProviderInJavaSpec : SubjectSpek<TomlProvider>({\n    subject { TomlProvider.get() }\n\n    itBehavesLike(TomlProviderSpec)\n})\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/toml/TomlSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport com.uchuhimo.konf.source.toml\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject TomlSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.toml.resource(\"source/source.toml\")\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n\nobject TomlSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.toml.resource(\"source/source.toml\")\n        val toml = config.toToml.toText()\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.toml.string(toml)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/toml/TomlValueSourceSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.natpryce.hamkrest.sameInstance\nimport com.natpryce.hamkrest.throws\nimport com.uchuhimo.konf.source.ParseException\nimport com.uchuhimo.konf.source.asSource\nimport com.uchuhimo.konf.source.asValue\nimport org.jetbrains.spek.api.Spek\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\n\nobject TomlValueSourceSpec : Spek({\n    given(\"a TOML source\") {\n        on(\"get integer from long source\") {\n            it(\"should succeed\") {\n                assertThat(1L.asSource().asValue<Int>(), equalTo(1))\n            }\n        }\n        on(\"get integer from long source whose value is out of range of integer\") {\n            it(\"should throw ParseException\") {\n                assertThat({ Long.MAX_VALUE.asSource().asValue<Int>() }, throws<ParseException>())\n                assertThat({ Long.MIN_VALUE.asSource().asValue<Int>() }, throws<ParseException>())\n            }\n        }\n        on(\"invoke `asTomlSource`\") {\n            val source = 1.asSource()\n            it(\"should return itself\") {\n                assertThat(source.asSource(), sameInstance(source))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-toml/src/test/kotlin/com/uchuhimo/konf/source/toml/TomlWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.toml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject TomlWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toToml\n    }\n\n    given(\"a writer\") {\n        val expectedString =\n            \"\"\"key = \"value\"\n            |\"\"\".trimMargin().replace(\"\\n\", System.lineSeparator())\n        on(\"save to string\") {\n            val string = subject.toText()\n            it(\"should return a string which contains content from config\") {\n                assertThat(string, equalTo(expectedString))\n            }\n        }\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-toml/src/test/resources/source/source.toml",
    "content": "[level1.level2]\nempty = \"null\"\nliteralEmpty = \"null\"\npresent = 1\n\nboolean = false\n\nint = 1\nshort = 2\nbyte = 3\nbigInteger = 4\nlong = 4\n\ndouble = 1.5\nfloat = -1.5\nbigDecimal = 1.5\n\nchar = \"a\"\n\nstring = \"string\"\noffsetTime = \"10:15:30+01:00\"\noffsetDateTime = \"2007-12-03T10:15:30+01:00\"\nzonedDateTime = \"2007-12-03T10:15:30+01:00[Europe/Paris]\"\nlocalDate = \"2007-12-03\"\nlocalTime = \"10:15:30\"\nlocalDateTime = \"2007-12-03T10:15:30\"\ndate = 2007-12-03T10:15:30Z\nyear = \"2007\"\nyearMonth = \"2007-12\"\ninstant = 2007-12-03T10:15:30.00Z\nduration = \"P2DT3H4M\"\nsimpleDuration = \"200millis\"\nsize = \"10k\"\n\nenum = \"LABEL2\"\n\nlist = [1, 2, 3]\nmutableList = [1, 2, 3]\nlistOfList = [[1, 2], [3, 4]]\nset = [1, 2, 1]\nsortedSet = [2, 1, 1, 3]\n\nmap = { a = 1, b = 2, c = 3 }\nintMap = { 1 = \"a\", 2 = \"b\", 3 = \"c\" }\nsortedMap = { c = 3, b = 2, a = 1 }\n\nnested = [[[{ a = 1 }]]]\n\n    [[level1.level2.listOfMap]]\n    a = 1\n    b = 2\n\n    [[level1.level2.listOfMap]]\n    a = 3\n    b = 4\n\n    [level1.level2.array]\n    boolean = [true, false]\n    byte = [1, 2, 3]\n    short = [1, 2, 3]\n    int = [1, 2, 3]\n    long = [4, 5, 6]\n    float = [-1.0, 0.0, 1.0]\n    double = [-1.0, 0.0, 1.0]\n    char = [\"a\", \"b\", \"c\"]\n\n        [level1.level2.array.object]\n        boolean = [true, false]\n        int = [1, 2, 3]\n        string = [\"one\", \"two\", \"three\"]\n        enum = [\"LABEL1\", \"LABEL2\", \"LABEL3\"]\n\n    [level1.level2.pair]\n    first = 1\n    second = 2\n\n    [level1.level2.clazz]\n    empty = \"null\"\n    literalEmpty = \"null\"\n    present = 1\n\n    boolean = false\n\n    int = 1\n    short = 2\n    byte = 3\n    bigInteger = 4\n    long = 4\n\n    double = 1.5\n    float = -1.5\n    bigDecimal = 1.5\n\n    char = \"a\"\n\n    string = \"string\"\n    offsetTime = \"10:15:30+01:00\"\n    offsetDateTime = \"2007-12-03T10:15:30+01:00\"\n    zonedDateTime = \"2007-12-03T10:15:30+01:00[Europe/Paris]\"\n    localDate = \"2007-12-03\"\n    localTime = \"10:15:30\"\n    localDateTime = \"2007-12-03T10:15:30\"\n    date = 2007-12-03T10:15:30Z\n    year = \"2007\"\n    yearMonth = \"2007-12\"\n    instant = 2007-12-03T10:15:30.00Z\n    duration = \"P2DT3H4M\"\n    simpleDuration = \"200millis\"\n    size = \"10k\"\n\n    enum = \"LABEL2\"\n\n    booleanArray = [true, false]\n\n    nested = [[[{ a = 1 }]]]\n"
  },
  {
    "path": "konf-toml/src/testFixtures/kotlin/com/uchuhimo/konf/source/TomlTestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\n//language=TOML\nconst val tomlContent =\n    \"\"\"\n[source.test]\ntype = \"toml\"\n\"\"\"\n"
  },
  {
    "path": "konf-xml/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    implementation(\"org.dom4j\", \"dom4j\", Versions.dom4j)\n    implementation(\"jaxen\", \"jaxen\", Versions.jaxen)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-xml/src/main/kotlin/com/uchuhimo/konf/source/DefaultXmlLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.xml.XmlProvider\n\n/**\n * Loader for XML source.\n */\nval DefaultLoaders.xml get() = Loader(config, XmlProvider.orMapped())\n"
  },
  {
    "path": "konf-xml/src/main/kotlin/com/uchuhimo/konf/source/DefaultXmlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.xml.XmlProvider\n\n/**\n * Provider for XML source.\n */\nval DefaultProviders.xml get() = XmlProvider\n"
  },
  {
    "path": "konf-xml/src/main/kotlin/com/uchuhimo/konf/source/xml/XmlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.xml\n\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.RegisterExtension\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.base.FlatSource\nimport org.dom4j.Document\nimport org.dom4j.io.SAXReader\nimport java.io.InputStream\nimport java.io.Reader\n\n/**\n * Provider for XML source.\n */\n@RegisterExtension([\"xml\"])\nobject XmlProvider : Provider {\n    private fun Document.toMap(): Map<String, String> {\n        val rootElement = this.rootElement\n        val propertyNodes = rootElement.selectNodes(\"/configuration/property\")\n        return mutableMapOf<String, String>().apply {\n            for (property in propertyNodes) {\n                put(property.selectSingleNode(\"name\").text, property.selectSingleNode(\"value\").text)\n            }\n        }\n    }\n\n    override fun reader(reader: Reader): Source {\n        return FlatSource(SAXReader().read(reader).toMap(), type = \"XML\")\n    }\n\n    override fun inputStream(inputStream: InputStream): Source {\n        return FlatSource(SAXReader().read(inputStream).toMap(), type = \"XML\")\n    }\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n"
  },
  {
    "path": "konf-xml/src/main/kotlin/com/uchuhimo/konf/source/xml/XmlWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.xml\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toFlatMap\nimport org.dom4j.Document\nimport org.dom4j.DocumentHelper\nimport org.dom4j.io.OutputFormat\nimport org.dom4j.io.XMLWriter\nimport java.io.OutputStream\n\n/**\n * Writer for XML source.\n */\nclass XmlWriter(val config: Config) : Writer {\n    private fun Map<String, String>.toDocument(): Document {\n        val document = DocumentHelper.createDocument()\n        val rootElement = document.addElement(\"configuration\")\n        for ((key, value) in this) {\n            val propertyElement = rootElement.addElement(\"property\")\n            propertyElement.addElement(\"name\").text = key\n            propertyElement.addElement(\"value\").text = value\n        }\n        return document\n    }\n\n    private val outputFormat = OutputFormat.createPrettyPrint().apply {\n        lineSeparator = System.lineSeparator()\n    }\n\n    override fun toWriter(writer: java.io.Writer) {\n        val xmlWriter = XMLWriter(writer, outputFormat)\n        xmlWriter.write(config.toFlatMap().toDocument())\n        xmlWriter.close()\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        val xmlWriter = XMLWriter(outputStream, outputFormat)\n        xmlWriter.write(config.toFlatMap().toDocument())\n        xmlWriter.close()\n    }\n}\n\n/**\n * Returns writer for XML source.\n */\nval Config.toXml: Writer get() = XmlWriter(this)\n"
  },
  {
    "path": "konf-xml/src/test/kotlin/com/uchuhimo/konf/source/DefaultXmlLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultXmlLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from XML file\") {\n            val config = subject.file(tempFileOf(xmlContent, suffix = \".xml\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"xml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-xml/src/test/kotlin/com/uchuhimo/konf/source/DefaultXmlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultXmlProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provide source from XML file\") {\n            val config = subject.file(tempFileOf(xmlContent, suffix = \".xml\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"xml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-xml/src/test/kotlin/com/uchuhimo/konf/source/xml/XmlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.xml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject XmlProviderSpec : SubjectSpek<XmlProvider>({\n    subject { XmlProvider }\n\n    //language=XML\n    fun xmlDoc(name: String, value: String) =\n        \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <configuration>\n            <property>\n                <name>$name</name>\n                <value>$value</value>\n            </property>\n        </configuration>\n        \"\"\".trimIndent()\n\n    given(\"a XML provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(xmlDoc(\"type\", \"reader\").reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"XML\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(xmlDoc(\"type\", \"inputStream\")).inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"XML\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><configuration></configuration>\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject XmlProviderInJavaSpec : SubjectSpek<XmlProvider>({\n    subject { XmlProvider.get() }\n\n    itBehavesLike(XmlProviderSpec)\n})\n"
  },
  {
    "path": "konf-xml/src/test/kotlin/com/uchuhimo/konf/source/xml/XmlSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.xml\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.base.FlatConfigForLoad\nimport com.uchuhimo.konf.source.base.FlatSourceLoadBaseSpec\nimport com.uchuhimo.konf.source.xml\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject XmlSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.xml.resource(\"source/source.xml\")\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n\nobject XmlSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.xml.resource(\"source/source.xml\")\n        val xml = config.toXml.toText()\n        Config {\n            addSpec(ConfigForLoad)\n            addSpec(FlatConfigForLoad)\n        }.from.xml.string(xml)\n    }\n\n    itBehavesLike(FlatSourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-xml/src/test/kotlin/com/uchuhimo/konf/source/xml/XmlWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.xml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject XmlWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toXml\n    }\n\n    given(\"a writer\") {\n        val expectedString =\n            \"\"\"\n            |<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n            |\n            |<configuration>\n            |  <property>\n            |    <name>key</name>\n            |    <value>value</value>\n            |  </property>\n            |</configuration>\n            |\"\"\".trimMargin().replace(\"\\n\", System.lineSeparator())\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-xml/src/test/resources/source/source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n    <property>\n        <name>level1.level2.empty</name>\n        <value>null</value>\n    </property>\n    <property>\n        <name>level1.level2.literalEmpty</name>\n        <value>null</value>\n    </property>\n    <property>\n        <name>level1.level2.present</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.boolean</name>\n        <value>false</value>\n    </property>\n    <property>\n        <name>level1.level2.int</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.short</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.byte</name>\n        <value>3</value>\n    </property>\n    <property>\n        <name>level1.level2.bigInteger</name>\n        <value>4</value>\n    </property>\n    <property>\n        <name>level1.level2.long</name>\n        <value>4</value>\n    </property>\n    <property>\n        <name>level1.level2.double</name>\n        <value>1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.float</name>\n        <value>-1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.bigDecimal</name>\n        <value>1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.char</name>\n        <value>a</value>\n    </property>\n    <property>\n        <name>level1.level2.string</name>\n        <value>string</value>\n    </property>\n    <property>\n        <name>level1.level2.offsetTime</name>\n        <value>10:15:30+01:00</value>\n    </property>\n    <property>\n        <name>level1.level2.offsetDateTime</name>\n        <value>2007-12-03T10:15:30+01:00</value>\n    </property>\n    <property>\n        <name>level1.level2.zonedDateTime</name>\n        <value>2007-12-03T10:15:30+01:00[Europe/Paris]</value>\n    </property>\n    <property>\n        <name>level1.level2.localDate</name>\n        <value>2007-12-03</value>\n    </property>\n    <property>\n        <name>level1.level2.localTime</name>\n        <value>10:15:30</value>\n    </property>\n    <property>\n        <name>level1.level2.localDateTime</name>\n        <value>2007-12-03T10:15:30</value>\n    </property>\n    <property>\n        <name>level1.level2.date</name>\n        <value>2007-12-03T10:15:30Z</value>\n    </property>\n    <property>\n        <name>level1.level2.year</name>\n        <value>2007</value>\n    </property>\n    <property>\n        <name>level1.level2.yearMonth</name>\n        <value>2007-12</value>\n    </property>\n    <property>\n        <name>level1.level2.instant</name>\n        <value>2007-12-03T10:15:30.00Z</value>\n    </property>\n    <property>\n        <name>level1.level2.duration</name>\n        <value>P2DT3H4M</value>\n    </property>\n    <property>\n        <name>level1.level2.simpleDuration</name>\n        <value>200millis</value>\n    </property>\n    <property>\n        <name>level1.level2.size</name>\n        <value>10k</value>\n    </property>\n    <property>\n        <name>level1.level2.enum</name>\n        <value>LABEL2</value>\n    </property>\n    <property>\n        <name>level1.level2.list</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.mutableList</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfList.0</name>\n        <value>1,2</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfList.1</name>\n        <value>3,4</value>\n    </property>\n    <property>\n        <name>level1.level2.set</name>\n        <value>1,2,1</value>\n    </property>\n    <property>\n        <name>level1.level2.sortedSet</name>\n        <value>2,1,1,3</value>\n    </property>\n    <property>\n        <name>level1.level2.map.a</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.map.b</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.map.c</name>\n        <value>3</value>\n    </property>\n    <property>\n        <name>level1.level2.intMap.1</name>\n        <value>a</value>\n    </property>\n    <property>\n        <name>level1.level2.intMap.2</name>\n        <value>b</value>\n    </property>\n    <property>\n        <name>level1.level2.intMap.3</name>\n        <value>c</value>\n    </property>\n    <property>\n        <name>level1.level2.sortedMap.c</name>\n        <value>3</value>\n    </property>\n    <property>\n        <name>level1.level2.sortedMap.b</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.sortedMap.a</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.nested.0.0.0.a</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfMap.0.a</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfMap.0.b</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfMap.1.a</name>\n        <value>3</value>\n    </property>\n    <property>\n        <name>level1.level2.listOfMap.1.b</name>\n        <value>4</value>\n    </property>\n    <property>\n        <name>level1.level2.array.boolean</name>\n        <value>true,false</value>\n    </property>\n    <property>\n        <name>level1.level2.array.byte</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.array.short</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.array.int</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.array.long</name>\n        <value>4,5,6</value>\n    </property>\n    <property>\n        <name>level1.level2.array.float</name>\n        <value>-1, 0.0, 1</value>\n    </property>\n    <property>\n        <name>level1.level2.array.double</name>\n        <value>-1, 0.0, 1</value>\n    </property>\n    <property>\n        <name>level1.level2.array.char</name>\n        <value>a,b,c</value>\n    </property>\n    <property>\n        <name>level1.level2.array.object.boolean</name>\n        <value>true,false</value>\n    </property>\n    <property>\n        <name>level1.level2.array.object.int</name>\n        <value>1,2,3</value>\n    </property>\n    <property>\n        <name>level1.level2.array.object.string</name>\n        <value>one,two,three</value>\n    </property>\n    <property>\n        <name>level1.level2.array.object.enum</name>\n        <value>LABEL1,LABEL2,LABEL3</value>\n    </property>\n    <property>\n        <name>level1.level2.pair.first</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.pair.second</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.empty</name>\n        <value>null</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.literalEmpty</name>\n        <value>null</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.present</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.boolean</name>\n        <value>false</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.int</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.short</name>\n        <value>2</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.byte</name>\n        <value>3</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.bigInteger</name>\n        <value>4</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.long</name>\n        <value>4</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.double</name>\n        <value>1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.float</name>\n        <value>-1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.bigDecimal</name>\n        <value>1.5</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.char</name>\n        <value>a</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.string</name>\n        <value>string</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.offsetTime</name>\n        <value>10:15:30+01:00</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.offsetDateTime</name>\n        <value>2007-12-03T10:15:30+01:00</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.zonedDateTime</name>\n        <value>2007-12-03T10:15:30+01:00[Europe/Paris]</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.localDate</name>\n        <value>2007-12-03</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.localTime</name>\n        <value>10:15:30</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.localDateTime</name>\n        <value>2007-12-03T10:15:30</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.date</name>\n        <value>2007-12-03T10:15:30Z</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.year</name>\n        <value>2007</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.yearMonth</name>\n        <value>2007-12</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.instant</name>\n        <value>2007-12-03T10:15:30.00Z</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.duration</name>\n        <value>P2DT3H4M</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.simpleDuration</name>\n        <value>200millis</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.size</name>\n        <value>10k</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.enum</name>\n        <value>LABEL2</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.booleanArray</name>\n        <value>true,false</value>\n    </property>\n    <property>\n        <name>level1.level2.clazz.nested.0.0.0.a</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.stringWithComma</name>\n        <value>string,with,comma</value>\n    </property>\n    <property>\n        <name>level1.level2.emptyList</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.emptySet</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.emptyArray</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.emptyObjectArray</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.singleElementList</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.multipleElementsList</name>\n        <value>1,2</value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.stringWithComma</name>\n        <value>string,with,comma</value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.emptyList</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.emptySet</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.emptyArray</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.emptyObjectArray</name>\n        <value></value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.singleElementList</name>\n        <value>1</value>\n    </property>\n    <property>\n        <name>level1.level2.flatClass.multipleElementsList</name>\n        <value>1,2</value>\n    </property>\n</configuration>"
  },
  {
    "path": "konf-xml/src/testFixtures/kotlin/com/uchuhimo/konf/source/XmlTestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\n//language=XML\nval xmlContent =\n    \"\"\"\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n    <property>\n        <name>source.test.type</name>\n        <value>xml</value>\n    </property>\n</configuration>\n\"\"\".trim()\n"
  },
  {
    "path": "konf-yaml/build.gradle.kts",
    "content": "dependencies {\n    api(project(\":konf-core\"))\n    implementation(\"org.yaml\", \"snakeyaml\", Versions.yaml)\n\n    testImplementation(testFixtures(project(\":konf-core\")))\n}\n"
  },
  {
    "path": "konf-yaml/src/main/kotlin/com/uchuhimo/konf/source/DefaultYamlLoader.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.yaml.YamlProvider\n\n/**\n * Loader for YAML source.\n */\nval DefaultLoaders.yaml get() = Loader(config, YamlProvider.orMapped())\n"
  },
  {
    "path": "konf-yaml/src/main/kotlin/com/uchuhimo/konf/source/DefaultYamlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.uchuhimo.konf.source.yaml.YamlProvider\n\n/**\n * Provider for YAML source.\n */\nval DefaultProviders.yaml get() = YamlProvider\n"
  },
  {
    "path": "konf-yaml/src/main/kotlin/com/uchuhimo/konf/source/yaml/YamlProvider.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.yaml\n\nimport com.uchuhimo.konf.annotation.JavaApi\nimport com.uchuhimo.konf.source.Provider\nimport com.uchuhimo.konf.source.RegisterExtension\nimport com.uchuhimo.konf.source.Source\nimport com.uchuhimo.konf.source.asSource\nimport org.yaml.snakeyaml.Yaml\nimport org.yaml.snakeyaml.constructor.AbstractConstruct\nimport org.yaml.snakeyaml.constructor.SafeConstructor\nimport org.yaml.snakeyaml.nodes.Node\nimport org.yaml.snakeyaml.nodes.ScalarNode\nimport org.yaml.snakeyaml.nodes.Tag\nimport java.io.InputStream\nimport java.io.Reader\n\n/**\n * Provider for YAML source.\n */\n@RegisterExtension([\"yml\", \"yaml\"])\nobject YamlProvider : Provider {\n    override fun reader(reader: Reader): Source {\n        val yaml = Yaml(YamlConstructor())\n        val value = yaml.load<Any>(reader)\n        if (value == \"null\") {\n            return mapOf<String, Any>().asSource(\"YAML\")\n        } else {\n            return value.asSource(\"YAML\")\n        }\n    }\n\n    override fun inputStream(inputStream: InputStream): Source {\n        val yaml = Yaml(YamlConstructor())\n        val value = yaml.load<Any>(inputStream)\n        if (value == \"null\") {\n            return mapOf<String, Any>().asSource(\"YAML\")\n        } else {\n            return value.asSource(\"YAML\")\n        }\n    }\n\n    @JavaApi\n    @JvmStatic\n    fun get() = this\n}\n\nprivate class YamlConstructor : SafeConstructor() {\n    init {\n        yamlConstructors[Tag.NULL] = object : AbstractConstruct() {\n            override fun construct(node: Node?): Any? {\n                if (node != null) {\n                    constructScalar(node as ScalarNode)\n                }\n                return \"null\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "konf-yaml/src/main/kotlin/com/uchuhimo/konf/source/yaml/YamlWriter.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.yaml\n\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.source.Writer\nimport com.uchuhimo.konf.source.base.toHierarchicalMap\nimport org.yaml.snakeyaml.DumperOptions\nimport org.yaml.snakeyaml.Yaml\nimport org.yaml.snakeyaml.constructor.SafeConstructor\nimport org.yaml.snakeyaml.representer.Representer\nimport java.io.OutputStream\n\n/**\n * Writer for YAML source.\n */\nclass YamlWriter(val config: Config) : Writer {\n    private val yaml = Yaml(\n        SafeConstructor(),\n        Representer(),\n        DumperOptions().apply {\n            defaultFlowStyle = DumperOptions.FlowStyle.BLOCK\n            lineBreak = DumperOptions.LineBreak.getPlatformLineBreak()\n        }\n    )\n\n    override fun toWriter(writer: java.io.Writer) {\n        yaml.dump(config.toHierarchicalMap(), writer)\n    }\n\n    override fun toOutputStream(outputStream: OutputStream) {\n        outputStream.writer().use {\n            toWriter(it)\n        }\n    }\n}\n\n/**\n * Returns writer for YAML source.\n */\nval Config.toYaml: Writer get() = YamlWriter(this)\n"
  },
  {
    "path": "konf-yaml/src/test/kotlin/com/uchuhimo/konf/source/DefaultYamlLoaderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultYamlLoaderSpec : SubjectSpek<DefaultLoaders>({\n    subject {\n        Config {\n            addSpec(DefaultLoadersConfig)\n        }.from\n    }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a loader\") {\n        on(\"load from YAML file\") {\n            val config = subject.file(tempFileOf(yamlContent, suffix = \".yaml\"))\n            it(\"should load as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"yaml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-yaml/src/test/kotlin/com/uchuhimo/konf/source/DefaultYamlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\n\nobject DefaultYamlProviderSpec : SubjectSpek<DefaultProviders>({\n    subject { Source.from }\n\n    val item = DefaultLoadersConfig.type\n\n    given(\"a provider\") {\n        on(\"provide source from YAML file\") {\n            val config = subject.file(tempFileOf(yamlContent, suffix = \".yaml\")).toConfig()\n            it(\"should provide as auto-detected file format\") {\n                assertThat(config[item], equalTo(\"yaml\"))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-yaml/src/test/kotlin/com/uchuhimo/konf/source/yaml/YamlProviderSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.yaml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.source.asValue\nimport com.uchuhimo.konf.tempFileOf\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\n\nobject YamlProviderSpec : SubjectSpek<YamlProvider>({\n    subject { YamlProvider }\n\n    given(\"a YAML provider\") {\n        on(\"create source from reader\") {\n            val source = subject.reader(\"type: reader\".reader())\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"YAML\"))\n            }\n            it(\"should return a source which contains value from reader\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"reader\"))\n            }\n        }\n        on(\"create source from input stream\") {\n            val source = subject.inputStream(\n                tempFileOf(\"type: inputStream\").inputStream()\n            )\n            it(\"should have correct type\") {\n                assertThat(source.info[\"type\"], equalTo(\"YAML\"))\n            }\n            it(\"should return a source which contains value from input stream\") {\n                assertThat(source[\"type\"].asValue<String>(), equalTo(\"inputStream\"))\n            }\n        }\n        on(\"create source from an empty file\") {\n            val file = tempFileOf(\"\")\n            it(\"should return an empty source\") {\n                assertThat(\n                    subject.file(file).tree.children,\n                    equalTo(mutableMapOf())\n                )\n            }\n        }\n    }\n})\n\nobject YamlProviderInJavaSpec : SubjectSpek<YamlProvider>({\n    subject { YamlProvider.get() }\n\n    itBehavesLike(YamlProviderSpec)\n})\n"
  },
  {
    "path": "konf-yaml/src/test/kotlin/com/uchuhimo/konf/source/yaml/YamlSourceLoadSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.yaml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.Feature\nimport com.uchuhimo.konf.source.ConfigForLoad\nimport com.uchuhimo.konf.source.SourceLoadBaseSpec\nimport com.uchuhimo.konf.source.yaml\nimport com.uchuhimo.konf.toValue\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport org.jetbrains.spek.subject.itBehavesLike\nimport kotlin.test.assertTrue\n\nobject YamlSourceLoadSpec : SubjectSpek<Config>({\n\n    subject {\n        Config {\n            addSpec(ConfigForLoad)\n            enable(Feature.FAIL_ON_UNKNOWN_PATH)\n        }.from.yaml.resource(\"source/source.yaml\")\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n\n    given(\"a config\") {\n        on(\"load a YAML with an int key\") {\n            val config = Config().from.yaml.string(\n                \"\"\"\n                tree:\n                  1:\n                    myVal: true\n                \"\"\".trimIndent()\n            )\n            it(\"should treat it as a string key\") {\n                assertTrue { config.at(\"tree.1.myVal\").toValue() }\n            }\n        }\n        on(\"load a YAML with a long key\") {\n            val config = Config().from.yaml.string(\n                \"\"\"\n                tree:\n                  2147483648:\n                    myVal: true\n                \"\"\".trimIndent()\n            )\n            it(\"should treat it as a string key\") {\n                assertTrue { config.at(\"tree.2147483648.myVal\").toValue() }\n            }\n        }\n        on(\"load a YAML with a BigInteger key\") {\n            val config = Config().from.yaml.string(\n                \"\"\"\n                tree:\n                  9223372036854775808:\n                    myVal: true\n                \"\"\".trimIndent()\n            )\n            it(\"should treat it as a string key\") {\n                assertTrue { config.at(\"tree.9223372036854775808.myVal\").toValue() }\n            }\n        }\n        on(\"load a YAML with a top-level list\") {\n            val config = Config().from.yaml.string(\n                \"\"\"\n                - a\n                - b\n                \"\"\".trimIndent()\n            )\n            it(\"should treat it as a list\") {\n                assertThat(config.toValue(), equalTo(listOf(\"a\", \"b\")))\n            }\n        }\n    }\n})\n\nobject YamlSourceReloadSpec : SubjectSpek<Config>({\n\n    subject {\n        val config = Config {\n            addSpec(ConfigForLoad)\n        }.from.yaml.resource(\"source/source.yaml\")\n        val yaml = config.toYaml.toText()\n        Config {\n            addSpec(ConfigForLoad)\n        }.from.yaml.string(yaml)\n    }\n\n    itBehavesLike(SourceLoadBaseSpec)\n})\n"
  },
  {
    "path": "konf-yaml/src/test/kotlin/com/uchuhimo/konf/source/yaml/YamlWriterSpec.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source.yaml\n\nimport com.natpryce.hamkrest.assertion.assertThat\nimport com.natpryce.hamkrest.equalTo\nimport com.uchuhimo.konf.Config\nimport com.uchuhimo.konf.ConfigSpec\nimport com.uchuhimo.konf.source.Writer\nimport org.jetbrains.spek.api.dsl.given\nimport org.jetbrains.spek.api.dsl.it\nimport org.jetbrains.spek.api.dsl.on\nimport org.jetbrains.spek.subject.SubjectSpek\nimport java.io.ByteArrayOutputStream\nimport java.io.StringWriter\n\nobject YamlWriterSpec : SubjectSpek<Writer>({\n    subject {\n        val config = Config {\n            addSpec(\n                object : ConfigSpec() {\n                    val key by optional(\"value\")\n                }\n            )\n        }\n        config.toYaml\n    }\n\n    given(\"a writer\") {\n        val expectedString = \"key: value\" + System.lineSeparator()\n        on(\"save to writer\") {\n            val writer = StringWriter()\n            subject.toWriter(writer)\n            it(\"should return a writer which contains content from config\") {\n                assertThat(writer.toString(), equalTo(expectedString))\n            }\n        }\n        on(\"save to output stream\") {\n            val outputStream = ByteArrayOutputStream()\n            subject.toOutputStream(outputStream)\n            it(\"should return an output stream which contains content from config\") {\n                assertThat(outputStream.toString(), equalTo(expectedString))\n            }\n        }\n    }\n})\n"
  },
  {
    "path": "konf-yaml/src/test/resources/source/source.yaml",
    "content": "level1:\n  level2:\n    empty: \"null\"\n    literalEmpty: null\n    present: 1\n\n    boolean: false\n\n    int: 1\n    short: 2\n    byte: 3\n    bigInteger: 4\n    long: 4\n\n    double: 1.5\n    float: -1.5\n    bigDecimal: 1.5\n\n    char: \"a\"\n\n    string: string\n    offsetTime: 10:15:30+01:00\n    offsetDateTime: \"2007-12-03T10:15:30+01:00\"\n    zonedDateTime: 2007-12-03T10:15:30+01:00[Europe/Paris]\n    localDate: 2007-12-03\n    localTime: \"10:15:30\"\n    localDateTime: 2007-12-03T10:15:30\n    date: 2007-12-03T10:15:30Z\n    year: \"2007\"\n    yearMonth: 2007-12\n    instant: 2007-12-03T10:15:30.00Z\n    duration: P2DT3H4M\n    simpleDuration: 200millis\n    size: 10k\n\n    enum: LABEL2\n\n    array:\n      boolean:\n        - true\n        - false\n      byte: [1, 2, 3]\n      short: [1, 2, 3]\n      int: [1, 2, 3]\n      long: [4, 5, 6]\n      float: [-1, 0.0, 1]\n      double: [-1, 0.0, 1]\n      char: [a, b, c]\n\n      object:\n        boolean: [true, false]\n        int: [1, 2, 3]\n        string: [one, two, three]\n        enum: [LABEL1, LABEL2, LABEL3]\n\n    list: [1, 2, 3]\n    mutableList: [1, 2, 3]\n    listOfList:\n      - - 1\n        - 2\n      - [3, 4]\n    set: [1, 2, 1]\n    sortedSet: [2, 1, 1, 3]\n\n    map:\n      a: 1\n      b: 2\n      c: 3\n    intMap:\n      1: a\n      2: b\n      3: c\n    sortedMap: { c: 3, b: 2, a: 1 }\n    listOfMap:\n      - a: 1\n        b: 2\n      - a: 3\n        b: 4\n\n    nested: [[[{ a: 1 }]]]\n\n    pair:\n      first: 1\n      second: 2\n\n    clazz:\n      empty: \"null\"\n      literalEmpty: null\n      present: 1\n\n      boolean: false\n\n      int: 1\n      short: 2\n      byte: 3\n      bigInteger: 4\n      long: 4\n\n      double: 1.5\n      float: -1.5\n      bigDecimal: 1.5\n\n      char: \"a\"\n\n      string: string\n      offsetTime: 10:15:30+01:00\n      offsetDateTime: \"2007-12-03T10:15:30+01:00\"\n      zonedDateTime: 2007-12-03T10:15:30+01:00[Europe/Paris]\n      localDate: 2007-12-03\n      localTime: \"10:15:30\"\n      localDateTime: 2007-12-03T10:15:30\n      date: 2007-12-03T10:15:30Z\n      year: \"2007\"\n      yearMonth: 2007-12\n      instant: 2007-12-03T10:15:30.00Z\n      duration: P2DT3H4M\n      simpleDuration: 200millis\n      size: 10k\n\n      enum: LABEL2\n\n      booleanArray:\n        - true\n        - false\n\n      nested: [[[{ a: 1 }]]]"
  },
  {
    "path": "konf-yaml/src/testFixtures/kotlin/com/uchuhimo/konf/source/YamlTestUtils.kt",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.uchuhimo.konf.source\n\n//language=YAML\nconst val yamlContent =\n    \"\"\"\nsource:\n    test:\n        type: yaml\n\"\"\"\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\nrootProject.name = \"konf\"\n\ninclude(\n    \"konf-core\",\n    \"konf-git\",\n    \"konf-hocon\",\n    \"konf-js\",\n    \"konf-toml\",\n    \"konf-xml\",\n    \"konf-yaml\",\n    \"konf-all\"\n)\n\nplugins {\n    id(\"com.gradle.enterprise\") version \"3.0\"\n}\n\ngradleEnterprise {\n    buildScan {\n        termsOfServiceUrl = \"https://gradle.com/terms-of-service\"\n        termsOfServiceAgree = \"yes\"\n    }\n}\n"
  }
]