commit 3d0927aeebacde9944846587dc00963ba2711cef Author: CarmJos Date: Sun Apr 17 00:32:33 2022 +0800 初始版本完成 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b5667d3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [ CarmJos ] +custom: [ 'https://donate.carm.cc' ] diff --git a/.github/ISSUE_TEMPLATE/bugs_report.md b/.github/ISSUE_TEMPLATE/bugs_report.md new file mode 100644 index 0000000..0efcb43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bugs_report.md @@ -0,0 +1,37 @@ +--- +name: 问题提交 +about: 描述问题并提交,帮助我们对其进行检查与修复。 +title: '' +labels: bug +assignees: '' + +--- + +### **问题简述** + +用简短的话语描述一下大概问题。 + +### **问题来源** + +描述一下通过哪些操作才发现的问题,如: + +1. 使用了 '...' +2. 输入了 '....' +3. 出现了报错 '....' + +### **预期结果** (可选) + +如果问题不发生,应该是什么情况 + +### **问题截图/问题报错** + +如果有报错或输出,请提供截图。 + +### **操作环境** + +- 系统环境: `Windows 10` / `Ubuntu` / `...` +- Java版本: `JDK11` / `OPENJDK8` / `JRE8` / `...` + +### **其他补充** + +如有其他补充,可以在这里描述。 diff --git a/.github/ISSUE_TEMPLATE/feature_issues.md b/.github/ISSUE_TEMPLATE/feature_issues.md new file mode 100644 index 0000000..779ae79 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_issues.md @@ -0,0 +1,23 @@ +--- +name: 功能需求 +about: 希望我们提供更多的功能。 +title: '' +labels: enhancement +assignees: '' +--- + +### **功能简述** + +简单的描述一下你想要的功能 + +### **需求来源** + +简单的描述一下为什么需要这个功能。 + +### **功能参考**(可选) + +如果有相关功能的参考,如文本、截图,请提供给我们。 + +### **附加内容** + +如果有什么小细节需要重点注意,请在这里告诉我们。 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..76e22be --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/gpg_public.key b/.github/gpg_public.key new file mode 100644 index 0000000..653bac3 --- /dev/null +++ b/.github/gpg_public.key @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGHwDt0BDAC+2u7hHXIp+C3tvUc5w7Ga5gDVNN3xTQEurGXgYSnGnNPb89h/ +tk6MBQ2AHdsj61yK/mH65RbDZe725+0zBvumxfrPbgqYBy9veE1Cjpl3wJwsGYa+ +gidq3tU2WBpUpaFOcyfxzvoDjKv6BClX+m7RijRM4tTSxmzrUTfwrClSdSV2HlBu +AuKvY5W+cDwlKtuXEBtgCpdlOGsp8YZsqe4QD9xMI6GOOnXnHisYnmsMzn2RU8mW +GUS3ob1J1vAfIinixwB8tHlxB/G3jaOXtQEwFmI2dfYOdkbxOiIgcSfbRI8PGiHA +KiluZpn+Ww05GwUch2HdX8dw1hsbWM4G/X8Aqy3HdJB28p73dE4I9FRrJ1uxsmMe +iON8QevhSBC0qwSxb+16vKt58ErQnqXrJI6+HzPldn22OQIF7bMZGwYkZiOjS5LU +xAoRT4Jomks0ccOZGe7wMIUp2Ch22vmv4O78Pd2GEzAcTUvM8mrS+zJBMogjx27C +r86HOWEjmi2R32EAEQEAAbQeQ2FybSBKb3MgPEthcm11bkpAb3V0bG9vay5jb20+ +iQHUBBMBCAA+FiEEL6NL2WG27xbAlAIkh337tzeYbfcFAmHwDt0CGwMFCQPCZwAF +CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQh337tzeYbffNvQwAscXykUimCOli +lRK52P6+w5n/arl7UxCh7TZiRjf9feiCp3OivETKCeqnbtNTgv67aNbxjO9asCTK +dU6J6Zh6wO8CqDhg+EA8qn+Nu4ESPGvgyWyeck9otMy16To5/I9eQRYTOos1crOA +DRUH1MWLeIkZabM6wSPad/CcRAzFNf5+8JNuQqCgQ3Rngst1Z6Gyb1hixWnjxc4P +7dFquwbR0D0ojwj0Etqd0c5p0iwyRl2I2QQ1bS3aGqdW0LzM9ixh25HAReg2QH7G +FBQ5PLLXr4UqYQygzwhUtxl2jra0+3ia+D7OBwlgm3QPnlo82Z7nExQUYmemD7jV +3Gc1ELXKSRHKbVjSoGiHWpnSiw4ptLo+tnzhRCHlV+pTS3IbQoPdb/glBOVIkA/j +ksCfbrmC8aXpk1YycAXY2my7BpXsImWAOwPHVsvcB2IpEA2s3VfsZ/IB9z+yih3n +z8mL0BFjKWUV23IOoeRqmt7l8nB7u55Nbjasu0LdTcl2R6swE3fTuQGNBGHwDt0B +DAChLPfZ1njctL8BijLO//Hgvw9E6STJGYgqglNetfdoir+YAwCPQ32K4MsaQKl8 +xQelmcOU+5jO2C8wEyNAjmvyKGB2J/IjLEtAlbOn1UltKQ/GhxgMjg0EheY81ZMa +7FDq1TDwYRCN5SMKhl5GF0JJ4OWfg1i7HbpEfkw4mW1pl0/eNdeQaC6qV6EWTsqz +WRbi8DeH1WarSgq/00Za6zxNntLNLoq7jsTbDwTc6pgOp1Z8EcGfI/mcn3moqTxc +o/PLYg+6impCKXVeRUlgGBpJ5YVvR5ACTLS9Tztwho9MpKJ9obXAfwXKyoToHCII ++pTnuzweOfOsrjLsFySnXq8WO2PY9JbNWjveKfk35fGfsrbwU0Vg+m67UahXqA4i +KNvZeA8bG8AXrxUirKLWIj/8AuW8NAKu7ui4YmexldraYUgaoBrqhXZCVe8dNQv+ +erzNbmJUCPDauNddnDsCqOoZ8fWyBenDs3NS0TWuvua4/ND+AyVxPeatI4qfS2TD +gnUAEQEAAYkBvAQYAQgAJhYhBC+jS9lhtu8WwJQCJId9+7c3mG33BQJh8A7dAhsM +BQkDwmcAAAoJEId9+7c3mG33znkL/01lWSQOzFd+omzrz0RPqFUksxqQS+CUty0m +/4n9H/K3BLcut+nUNbosNuqPqISoiaV7BGigv0bT+Pu+EQQtyjYOSeibeBadB48w +cYp8k3YJbfinuKApw1Zp9IfAd3eXXWi30OY4FmlsKy6LGnusZ6KS+FzTjU94yN/0 +LK05fmBtLN/MQJQyqYIkquzk//diwpsxnv34+10igYaQBAEpPIsmsYwWg+ecCtyx +lJGvmQggBrKvo5EdOGhO9DJAu1WQcFqnUCj5qvL+YKIsMyIwujQH8554P8xfCLFU +a351qs30yWXX4HGMn3o7RuVQAACs1buxlMen/JEdQOLOaUtFcu2iYzCFhuzDsetc +geNinFyo0bV9dXiahG95oTL45OA0w+E9Y0B5VXc9Yf08Yyj8ayMChASfVG5lZU6l +KhiaKHV9t4xmwP43lRjs8HTC5rtXc31kPtOAT61HG9vPA49ZdXybUqoHru15PFmc +OK7d0W/LdJ3iFeselROADHgPQn14sg== +=rRA5 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml new file mode 100644 index 0000000..3c023b9 --- /dev/null +++ b/.github/workflows/codacy-analysis.yml @@ -0,0 +1,54 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: "Codacy Security Scan" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '27 16 * * 5' + +jobs: + codacy-security-scan: + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v2 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: results.sarif diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..d6f8837 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Analysis" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '45 12 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0e37a12 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,108 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: "Project Deploy" + +on: + # 支持手动触发构建 + workflow_dispatch: + release: + # 创建release的时候触发 + types: [ published ] + +jobs: + gh-deploy: + name: "Publish Project (GitHub)" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: "Set up JDK" + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + server-id: github + server-username: MAVEN_USERNAME + server-password: MAVEN_TOKEN + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase + + - name: "Maven Deploy With Javadoc" + run: mvn -B -Pgithub deploy --file pom.xml -DskipTests + env: + MAVEN_USERNAME: ${{ github.repository_owner }} + MAVEN_TOKEN: ${{secrets.GITHUB_TOKEN}} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: "Copy Javadoc to Location" + run: | + rm -rf docs + mkdir -vp docs + cp -vrf api/target/apidocs/* docs/ + cp -vrf .documentation/JAVADOC-README.md docs/README.md + + - name: "Generate the Javadoc sitemap" + id: sitemap + uses: cicirello/generate-sitemap@v1 + with: + base-url-path: https://carmjos.github.io/EasySQL + path-to-root: docs + + - name: "Output stats" + run: | + echo "sitemap-path = ${{ steps.sitemap.outputs.sitemap-path }}" + echo "url-count = ${{ steps.sitemap.outputs.url-count }}" + echo "excluded-count = ${{ steps.sitemap.outputs.excluded-count }}" + + - name: "Configure Git" + env: + DEPLOY_PRI: ${{secrets.DEPLOY_PRI}} + run: | + sudo timedatectl set-timezone "Asia/Shanghai" + mkdir -p ~/.ssh/ + echo "$DEPLOY_PRI" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com >> ~/.ssh/known_hosts + git config --global user.name 'CarmJos' + git config --global user.email 'carm@carm.cc' + + - name: "Commit documentations" + run: | + cd docs + git init + git remote add origin git@github.com:CarmJos/EasySQL.git + git checkout -b gh-pages + git add -A + git commit -m "API Document generated." + + - name: "Push javadocs" + run: | + cd docs + git push origin HEAD:gh-pages --force + + central-deploy: + name: "Deploy Project (Central Repository)" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: "Set up JDK" + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase + + - name: "Central Deploy" + run: mvn -B -Possrh deploy --file pom.xml -DskipTests + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USER }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_PASS }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..9011a58 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,32 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Project Build & Tests + +on: + # 支持手动触发构建 + workflow_dispatch: + pull_request: + push: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: "Set up JDK" + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: "Package" + run: mvn -B package --file pom.xml -Dgpg.skip + - name: "Target Stage" + run: mkdir staging && cp */target/*.jar staging + - name: "Upload artifact" + uses: actions/upload-artifact@v2 + with: + name: Artifact + path: staging diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7f0d54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/ +**/target/ +**.iml \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4cd0986 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Carm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c28e111 --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +```text + ____ _____ ____ __ _ + / __/__ ____ __ __ / ___/__ ___ / _(_)__ ___ _________ _/ /_(_)__ ___ + / _// _ `(_- +远程库配置 + +```xml + + + + + + + maven + Maven Central + https://repo1.maven.org/maven2 + + + + + EasyConfiguration + GitHub Packages + https://maven.pkg.github.com/CarmJos/EasyConfiguration + + + + + carm-repo + Carm's Repo + https://repo.carm.cc/repository/maven-public/ + + + + +``` + + + +
+通用原生依赖 + +```xml + + + + + + cc.carm.lib + easyconfiguration-core + [LATEST RELEASE] + compile + + + + + cc.carm.lib + easyconfiguration-yaml + [LATEST RELEASE] + compile + + + + +``` + +
+ +
+平台依赖版本 + +
+基于Spigot实现的版本 + +```xml + + + + + cc.carm.lib + easyconfiguration-spigot + [LATEST RELEASE] + compile + + + +``` + +
+
+基于Bungee实现的版本 (不支持自动注释) + +```xml + + + + + cc.carm.lib + easyconfiguration-bungee + [LATEST RELEASE] + compile + + + +``` + +
+ +
+ +#### Gradle 依赖 + +
+远程库配置 + +```groovy +repositories { + + // 采用Maven中心库,安全稳定,但版本更新需要等待同步 + mavenCentral() + + // 采用github依赖库,实时更新,但需要配置 (推荐) + maven { url 'https://maven.pkg.github.com/CarmJos/EasyConfiguration' } + + // 采用我的私人依赖库,简单方便,但可能因为变故而无法使用 + maven { url 'https://repo.carm.cc/repository/maven-public/' } +} +``` + +
+ +
+原生依赖 + +```groovy + +dependencies { + + //基础实现部分,需要自行实现“Provider”与“Wrapper”。 + api "cc.carm.lib:easyconfiguration-core:[LATEST RELEASE]" + + //基于YAML文件的实现版本,可用于全部Java环境。 + api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" + +} +``` + +
+ +
+含连接池版本 + +```groovy + +dependencies { + + // 用于Spigot服务端的版本 + api "cc.carm.lib:easyconfiguration-spigot:[LATEST RELEASE]" + + // 用于BungeeCord服务端的版本,不支持注解。 + // 如需注解,可选择使用 `easyconfiguration-yaml` 并打包。 + api "cc.carm.lib:easyconfiguration-bungee:[LATEST RELEASE]" + +} +``` + +
+ +## 支持与捐赠 + +若您觉得本插件做的不错,您可以通过捐赠支持我! + +感谢您对开源项目的支持! + + + +## 开源协议 + +本项目源码采用 [The MIT License](https://opensource.org/licenses/MIT) 开源协议。 + +
+关于 MIT 协议 + +> MIT 协议可能是几大开源协议中最宽松的一个,核心条款是: +> +> 该软件及其相关文档对所有人免费,可以任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。唯一的限制是,软件中必须包含上述版 权和许可提示。 +> +> 这意味着: +> +> - 你可以自由使用,复制,修改,可以用于自己的项目。 +> - 可以免费分发或用来盈利。 +> - 唯一的限制是必须包含许可声明。 +> +> MIT 协议是所有开源许可中最宽松的一个,除了必须包含许可声明外,再无任何限制。 +> +> *以上文字来自 [五种开源协议GPL,LGPL,BSD,MIT,Apache](https://www.oschina.net/question/54100_9455) 。* + +
diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..ca8a234 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,19 @@ + + + + easyconfiguration-parent + cc.carm.lib + 1.0.0 + + 4.0.0 + + ${java.version} + ${java.version} + + + easyconfiguration-core + jar + + \ No newline at end of file diff --git a/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java b/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java new file mode 100644 index 0000000..444601a --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java @@ -0,0 +1,100 @@ +package cc.carm.lib.configuration.core; + +import cc.carm.lib.configuration.core.annotation.ConfigComment; +import cc.carm.lib.configuration.core.annotation.ConfigPath; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.value.ConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Optional; + +public class ConfigInitializer { + + public static void initialize(ConfigurationProvider source, Class rootClazz) { + initialize(source, rootClazz, true); + } + + public static void initialize(ConfigurationProvider provider, Class rootClazz, boolean saveDefault) { + ConfigPath sectionAnnotation = rootClazz.getAnnotation(ConfigPath.class); + + String rootSection = null; + if (sectionAnnotation != null && sectionAnnotation.value().length() > 1) { + rootSection = sectionAnnotation.value(); + } + + for (Class innerClass : rootClazz.getDeclaredClasses()) { + initSection(provider, rootSection, innerClass, saveDefault); + } + + for (Field field : rootClazz.getFields()) { + initValue(provider, rootSection, rootClazz, field, saveDefault); + } + + if (saveDefault) { + try { + provider.save(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + private static void initSection(ConfigurationProvider provider, String parentSection, Class clazz, boolean saveDefault) { + if (!Modifier.isStatic(clazz.getModifiers()) || !Modifier.isPublic(clazz.getModifiers())) return; + + String section = getSectionPath(clazz.getSimpleName(), parentSection, clazz.getAnnotation(ConfigPath.class)); + ConfigComment comments = clazz.getAnnotation(ConfigComment.class); + if (comments != null && comments.value().length > 0) { + provider.setComments(parentSection, comments.value()); + } + + for (Field field : clazz.getFields()) initValue(provider, section, clazz, field, saveDefault); + for (Class innerClass : clazz.getDeclaredClasses()) initSection(provider, section, innerClass, saveDefault); + + } + + private static void initValue(ConfigurationProvider provider, String parentSection, Class clazz, Field field, boolean saveDefault) { + try { + Object object = field.get(clazz); + if (object instanceof ConfigValue) { + initializeValue( + provider, (ConfigValue) object, + getSectionPath(field.getName(), parentSection, field.getAnnotation(ConfigPath.class)), + Optional.ofNullable(field.getAnnotation(ConfigComment.class)) + .map(ConfigComment::value).orElse(new String[0]), + saveDefault); + } + } catch (IllegalAccessException ignored) { + } + } + + + public static void initializeValue(@NotNull ConfigurationProvider provider, @NotNull ConfigValue value, + @NotNull String path, @NotNull String[] comments, boolean saveDefault) { + value.initialize(provider, path, comments); + if (saveDefault && value.getDefaultValue() != null && !provider.getConfiguration().contains(path)) { + value.setDefault(); + } + } + + public static String getSectionPath(@NotNull String name, + @Nullable String parentSection, + @Nullable ConfigPath pathAnnotation) { + String parent = parentSection != null ? parentSection + "." : ""; + if (pathAnnotation != null && pathAnnotation.value().length() > 0) { + return parent + pathAnnotation.value(); + } else { + return parent + getSectionName(name); + } + } + + public static String getSectionName(String codeName) { + return codeName.toLowerCase().replace("_", "-"); + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/ConfigurationRoot.java b/core/src/main/java/cc/carm/lib/configuration/core/ConfigurationRoot.java new file mode 100644 index 0000000..3c8a908 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/ConfigurationRoot.java @@ -0,0 +1,4 @@ +package cc.carm.lib.configuration.core; + +public abstract class ConfigurationRoot { +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigComment.java b/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigComment.java new file mode 100644 index 0000000..b24e874 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigComment.java @@ -0,0 +1,17 @@ +package cc.carm.lib.configuration.core.annotation; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigComment { + + @NotNull + String[] value() default ""; + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigPath.java b/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigPath.java new file mode 100644 index 0000000..a4dc331 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/annotation/ConfigPath.java @@ -0,0 +1,14 @@ +package cc.carm.lib.configuration.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigPath { + + String value(); + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/AbstractConfigBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/AbstractConfigBuilder.java new file mode 100644 index 0000000..f1f355c --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/AbstractConfigBuilder.java @@ -0,0 +1,42 @@ +package cc.carm.lib.configuration.core.builder; + +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.value.ConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractConfigBuilder, T> { + + + protected @Nullable ConfigurationProvider provider; + protected @Nullable String path; + + protected @NotNull String[] comments = new String[0]; + protected @Nullable T defaultValue; + + protected abstract @NotNull B getThis(); + + public abstract @NotNull ConfigValue build(); + + public @NotNull B from(@Nullable ConfigurationProvider provider) { + this.provider = provider; + return getThis(); + } + + public @NotNull B path(@Nullable String path) { + this.path = path; + return getThis(); + } + + public @NotNull B comments(@NotNull String... comments) { + this.comments = comments; + return getThis(); + } + + public @NotNull B defaults(@Nullable T defaultValue) { + this.defaultValue = defaultValue; + return getThis(); + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/ConfigBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/ConfigBuilder.java new file mode 100644 index 0000000..e47891d --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/ConfigBuilder.java @@ -0,0 +1,42 @@ +package cc.carm.lib.configuration.core.builder; + +import cc.carm.lib.configuration.core.builder.list.ConfigListBuilder; +import cc.carm.lib.configuration.core.builder.map.ConfigMapBuilder; +import cc.carm.lib.configuration.core.builder.value.ConfigValueBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.TreeMap; + +public class ConfigBuilder { + + public static @NotNull ConfigValueBuilder asValue(@NotNull Class valueClass) { + return new ConfigValueBuilder<>(valueClass); + } + + public static @NotNull ConfigListBuilder asList(@NotNull Class valueClass) { + return new ConfigListBuilder<>(valueClass); + } + + public static @NotNull ConfigMapBuilder, K, V> asMap(@NotNull Class keyClass, + @NotNull Class valueClass) { + return new ConfigMapBuilder<>(LinkedHashMap::new, keyClass, valueClass); + } + + public static @NotNull ConfigMapBuilder, K, V> asHashMap(@NotNull Class keyClass, + @NotNull Class valueClass) { + return asMap(keyClass, valueClass).supplier(HashMap::new); + } + + public static @NotNull ConfigMapBuilder, K, V> asLinkedMap(@NotNull Class keyClass, + @NotNull Class valueClass) { + return asMap(keyClass, valueClass); + } + + public static , V> @NotNull ConfigMapBuilder, K, V> asTreeMap(@NotNull Class keyClass, + @NotNull Class valueClass) { + return asMap(keyClass, valueClass).supplier(TreeMap::new); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/list/ConfigListBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/list/ConfigListBuilder.java new file mode 100644 index 0000000..cf19269 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/list/ConfigListBuilder.java @@ -0,0 +1,46 @@ +package cc.carm.lib.configuration.core.builder.list; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import org.jetbrains.annotations.NotNull; + +public class ConfigListBuilder { + + protected final @NotNull Class valueClass; + + public ConfigListBuilder(@NotNull Class valueClass) { + this.valueClass = valueClass; + } + + public @NotNull SourceListBuilder from(@NotNull Class sourceClass, + @NotNull ConfigDataFunction sourceParser, + @NotNull ConfigDataFunction valueParser, + @NotNull ConfigDataFunction valueSerializer, + @NotNull ConfigDataFunction sourceSerializer) { + return new SourceListBuilder<>(sourceClass, sourceParser, this.valueClass, valueParser, valueSerializer, sourceSerializer); + } + + + public @NotNull SourceListBuilder from(Class sourceClass) { + return from(sourceClass, + ConfigDataFunction.required(), ConfigDataFunction.required(), + ConfigDataFunction.required(), ConfigDataFunction.required() + ); + } + + public @NotNull SourceListBuilder fromObject() { + return from( + Object.class, ConfigDataFunction.identity(), + ConfigDataFunction.castObject(valueClass), + ConfigDataFunction.identity(), ConfigDataFunction.toObject() + ); + } + + public @NotNull SourceListBuilder fromString() { + return from( + String.class, ConfigDataFunction.castToString(), + ConfigDataFunction.castFromString(this.valueClass), + ConfigDataFunction.castToString(), ConfigDataFunction.toObject() + ); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/list/SourceListBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/list/SourceListBuilder.java new file mode 100644 index 0000000..6d597f0 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/list/SourceListBuilder.java @@ -0,0 +1,70 @@ +package cc.carm.lib.configuration.core.builder.list; + +import cc.carm.lib.configuration.core.builder.AbstractConfigBuilder; +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.value.type.ConfiguredList; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SourceListBuilder + extends AbstractConfigBuilder, List> { + + protected final @NotNull Class sourceClass; + protected @NotNull ConfigDataFunction sourceParser; + + protected final @NotNull Class valueClass; + protected @NotNull ConfigDataFunction valueParser; + + protected @NotNull ConfigDataFunction valueSerializer; + protected @NotNull ConfigDataFunction sourceSerializer; + + public SourceListBuilder(@NotNull Class sourceClass, @NotNull ConfigDataFunction sourceParser, + @NotNull Class valueClass, @NotNull ConfigDataFunction valueParser, + @NotNull ConfigDataFunction valueSerializer, + @NotNull ConfigDataFunction sourceSerializer) { + this.sourceClass = sourceClass; + this.sourceParser = sourceParser; + this.sourceSerializer = sourceSerializer; + this.valueClass = valueClass; + this.valueParser = valueParser; + this.valueSerializer = valueSerializer; + } + + public @NotNull SourceListBuilder parseSource(ConfigDataFunction sourceParser) { + this.sourceParser = sourceParser; + return this; + } + + public @NotNull SourceListBuilder parseValue(ConfigDataFunction valueParser) { + this.valueParser = valueParser; + return this; + } + + public @NotNull SourceListBuilder serializeValue(ConfigDataFunction serializer) { + this.valueSerializer = serializer; + return this; + } + + public @NotNull SourceListBuilder serializeSource(ConfigDataFunction serializer) { + this.sourceSerializer = serializer; + return this; + } + + @Override + protected @NotNull SourceListBuilder getThis() { + return this; + } + + @Override + public @NotNull ConfiguredList build() { + return new ConfiguredList<>( + this.provider, this.path, this.comments, + this.valueClass, this.defaultValue, + this.sourceParser.andThen(this.valueParser), + this.valueSerializer.andThen(sourceSerializer) + ); + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/map/ConfigMapBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/map/ConfigMapBuilder.java new file mode 100644 index 0000000..93f522c --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/map/ConfigMapBuilder.java @@ -0,0 +1,60 @@ +package cc.carm.lib.configuration.core.builder.map; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.function.Supplier; + +public class ConfigMapBuilder, K, V> { + + protected final @NotNull Supplier<@NotNull M> supplier; + + protected final @NotNull Class keyClass; + protected final @NotNull Class valueClass; + + public ConfigMapBuilder(@NotNull Supplier<@NotNull M> supplier, @NotNull Class keyClass, @NotNull Class valueClass) { + this.supplier = supplier; + this.keyClass = keyClass; + this.valueClass = valueClass; + } + + public > ConfigMapBuilder supplier(@NotNull Supplier supplier) { + return new ConfigMapBuilder<>(supplier, keyClass, valueClass); + } + + public SourceMapBuilder from(@NotNull Class sourceClass, + @NotNull ConfigDataFunction valueParser, + @NotNull ConfigDataFunction valueSerializer) { + return new SourceMapBuilder<>(supplier, + keyClass, ConfigDataFunction.castFromString(this.keyClass), // #String -> key + sourceClass, ConfigDataFunction.castObject(sourceClass), // #Object -> source + valueClass, valueParser, // source -> value + ConfigDataFunction.castToString(), // key -> #String + valueSerializer/*value -> source*/, + ConfigDataFunction.toObject()/* source -> #Object */ + ); + } + + public SourceMapBuilder from(@NotNull Class sourceClass) { + return from(sourceClass, ConfigDataFunction.required(), ConfigDataFunction.required()); + } + + public SourceMapBuilder fromString(@NotNull ConfigDataFunction valueParser) { + return from(String.class, valueParser, ConfigDataFunction.castToString()); + } + + public SourceMapBuilder fromString() { + return fromString(ConfigDataFunction.castFromString(this.valueClass)); + } + + public SourceMapBuilder fromObject(@NotNull ConfigDataFunction valueParser) { + return from(Object.class, valueParser, ConfigDataFunction.toObject()); + } + + public SourceMapBuilder fromObject() { + return fromObject(ConfigDataFunction.required()); + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/map/SourceMapBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/map/SourceMapBuilder.java new file mode 100644 index 0000000..ad20f4c --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/map/SourceMapBuilder.java @@ -0,0 +1,101 @@ +package cc.carm.lib.configuration.core.builder.map; + +import cc.carm.lib.configuration.core.builder.AbstractConfigBuilder; +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.value.type.ConfiguredMap; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.function.Supplier; + +public class SourceMapBuilder, S, K, V> + extends AbstractConfigBuilder, M> { + + protected final @NotNull Supplier<@NotNull M> supplier; + + protected final @NotNull Class keyClass; + protected @NotNull ConfigDataFunction keyParser; + + protected final @NotNull Class sourceClass; + protected @NotNull ConfigDataFunction sourceParser; + + protected final @NotNull Class valueClass; + protected @NotNull ConfigDataFunction valueParser; + + protected @NotNull ConfigDataFunction keySerializer; + protected @NotNull ConfigDataFunction valueSerializer; + protected @NotNull ConfigDataFunction sourceSerializer; + + public SourceMapBuilder(@NotNull Supplier<@NotNull M> supplier, + @NotNull Class keyClass, @NotNull ConfigDataFunction keyParser, + @NotNull Class sourceClass, @NotNull ConfigDataFunction sourceParser, + @NotNull Class valueClass, @NotNull ConfigDataFunction valueParser, + @NotNull ConfigDataFunction keySerializer, + @NotNull ConfigDataFunction valueSerializer, + @NotNull ConfigDataFunction sourceSerializer) { + this.supplier = supplier; + this.keyClass = keyClass; + this.keyParser = keyParser; + this.valueClass = valueClass; + this.valueParser = valueParser; + this.sourceClass = sourceClass; + this.sourceParser = sourceParser; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + this.sourceSerializer = sourceSerializer; + } + + public > SourceMapBuilder supplier(@NotNull Supplier supplier) { + return new SourceMapBuilder<>(supplier, + keyClass, keyParser, sourceClass, sourceParser, valueClass, valueParser, + keySerializer, valueSerializer, sourceSerializer + ); + } + + public @NotNull SourceMapBuilder parseKey(@NotNull ConfigDataFunction parser) { + this.keyParser = parser; + return this; + } + + public @NotNull SourceMapBuilder parseSource(@NotNull ConfigDataFunction parser) { + this.sourceParser = parser; + return this; + } + + public @NotNull SourceMapBuilder parseValue(@NotNull ConfigDataFunction parser) { + this.valueParser = parser; + return this; + } + + public @NotNull SourceMapBuilder serializeKey(@NotNull ConfigDataFunction serializer) { + this.keySerializer = serializer; + return this; + } + + public @NotNull SourceMapBuilder serializeValue(@NotNull ConfigDataFunction serializer) { + this.valueSerializer = serializer; + return this; + } + + public @NotNull SourceMapBuilder serializeSource(@NotNull ConfigDataFunction serializer) { + this.sourceSerializer = serializer; + return this; + } + + @Override + protected @NotNull SourceMapBuilder getThis() { + return this; + } + + @Override + public @NotNull ConfiguredMap build() { + return new ConfiguredMap<>( + this.provider, this.path, this.comments, + this.defaultValue, this.supplier, + this.keyClass, this.keyParser, + this.valueClass, this.sourceParser.andThen(this.valueParser), + this.keySerializer, this.valueSerializer.andThen(this.sourceSerializer) + ); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/value/ConfigValueBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/ConfigValueBuilder.java new file mode 100644 index 0000000..d0b30b6 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/ConfigValueBuilder.java @@ -0,0 +1,61 @@ +package cc.carm.lib.configuration.core.builder.value; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class ConfigValueBuilder { + + protected final @NotNull Class valueClass; + + public ConfigValueBuilder(@NotNull Class valueClass) { + this.valueClass = valueClass; + } + + public @NotNull SectionValueBuilder fromSection() { + return fromSection(ConfigValueParser.required(), ConfigDataFunction.required()); + } + + public @NotNull SectionValueBuilder fromSection(@NotNull ConfigValueParser valueParser, + @NotNull ConfigDataFunction> valueSerializer) { + return new SectionValueBuilder<>(this.valueClass, valueParser, valueSerializer); + } + + public @NotNull SourceValueBuilder from(Class sourceClass) { + return from( + sourceClass, ConfigDataFunction.required(), ConfigValueParser.required(), + ConfigDataFunction.required(), ConfigDataFunction.required() + ); + } + + public @NotNull SourceValueBuilder from(@NotNull Class sourceClass, + @NotNull ConfigDataFunction sourceParser, + @NotNull ConfigValueParser valueParser, + @NotNull ConfigDataFunction valueSerializer, + @NotNull ConfigDataFunction sourceSerializer) { + return new SourceValueBuilder<>( + sourceClass, sourceParser, this.valueClass, valueParser, + valueSerializer, sourceSerializer + ); + } + + public @NotNull SourceValueBuilder fromObject() { + return from( + Object.class, ConfigDataFunction.identity(), + ConfigValueParser.castObject(valueClass), + ConfigDataFunction.identity(), ConfigDataFunction.toObject() + ); + } + + public @NotNull SourceValueBuilder fromString() { + return from( + String.class, ConfigDataFunction.castToString(), + ConfigValueParser.parseString(this.valueClass), + ConfigDataFunction.castToString(), ConfigDataFunction.toObject() + ); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SectionValueBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SectionValueBuilder.java new file mode 100644 index 0000000..e17b6a3 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SectionValueBuilder.java @@ -0,0 +1,54 @@ +package cc.carm.lib.configuration.core.builder.value; + +import cc.carm.lib.configuration.core.builder.AbstractConfigBuilder; +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.value.type.ConfiguredSection; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class SectionValueBuilder + extends AbstractConfigBuilder, V> { + + + protected final @NotNull Class valueClass; + + protected @NotNull ConfigValueParser parser; + protected @NotNull ConfigDataFunction> serializer; + + public SectionValueBuilder(@NotNull Class valueClass, + @NotNull ConfigValueParser parser, + @NotNull ConfigDataFunction> serializer) { + this.valueClass = valueClass; + this.parser = parser; + this.serializer = serializer; + } + + + @Override + protected @NotNull SectionValueBuilder getThis() { + return this; + } + + public @NotNull SectionValueBuilder parseValue(ConfigValueParser valueParser) { + this.parser = valueParser; + return this; + } + + public @NotNull SectionValueBuilder serializeValue(ConfigDataFunction> serializer) { + this.serializer = serializer; + return this; + } + + @Override + public @NotNull ConfiguredSection build() { + return new ConfiguredSection<>( + this.provider, this.path, this.comments, + this.valueClass, this.defaultValue, + this.parser, this.serializer + ); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SourceValueBuilder.java b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SourceValueBuilder.java new file mode 100644 index 0000000..2bf90e2 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/builder/value/SourceValueBuilder.java @@ -0,0 +1,67 @@ +package cc.carm.lib.configuration.core.builder.value; + +import cc.carm.lib.configuration.core.builder.AbstractConfigBuilder; +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import cc.carm.lib.configuration.core.value.type.ConfiguredValue; +import org.jetbrains.annotations.NotNull; + +public class SourceValueBuilder extends AbstractConfigBuilder, V> { + + protected final @NotNull Class sourceClass; + protected @NotNull ConfigDataFunction sourceParser; + + protected final @NotNull Class valueClass; + protected @NotNull ConfigValueParser valueParser; + + protected @NotNull ConfigDataFunction sourceSerializer; + protected @NotNull ConfigDataFunction valueSerializer; + + public SourceValueBuilder(@NotNull Class sourceClass, @NotNull ConfigDataFunction sourceParser, + @NotNull Class valueClass, @NotNull ConfigValueParser valueParser, + @NotNull ConfigDataFunction valueSerializer, + @NotNull ConfigDataFunction sourceSerializer) { + this.sourceClass = sourceClass; + this.sourceParser = sourceParser; + this.valueClass = valueClass; + this.valueParser = valueParser; + this.sourceSerializer = sourceSerializer; + this.valueSerializer = valueSerializer; + } + + @Override + protected @NotNull SourceValueBuilder getThis() { + return this; + } + + public @NotNull SourceValueBuilder parseSource(@NotNull ConfigDataFunction sourceParser) { + this.sourceParser = sourceParser; + return this; + } + + public @NotNull SourceValueBuilder parseValue(@NotNull ConfigValueParser valueParser) { + this.valueParser = valueParser; + return this; + } + + public @NotNull SourceValueBuilder serializeValue(@NotNull ConfigDataFunction serializer) { + this.valueSerializer = serializer; + return this; + } + + public @NotNull SourceValueBuilder serializeSource(@NotNull ConfigDataFunction serializer) { + this.sourceSerializer = serializer; + return this; + } + + @Override + public @NotNull ConfiguredValue build() { + return new ConfiguredValue<>( + this.provider, this.path, this.comments, + this.valueClass, this.defaultValue, + this.valueParser.compose(this.sourceParser), + this.valueSerializer.andThen(sourceSerializer) + ); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigDataFunction.java b/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigDataFunction.java new file mode 100644 index 0000000..780dde3 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigDataFunction.java @@ -0,0 +1,64 @@ +package cc.carm.lib.configuration.core.function; + + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +@FunctionalInterface +public interface ConfigDataFunction { + + @NotNull R parse(@NotNull T data) throws Exception; + + default @NotNull ConfigDataFunction andThen(@NotNull ConfigDataFunction after) { + Objects.requireNonNull(after); + return ((data) -> after.parse(parse(data))); + } + + @Contract(pure = true) + static @NotNull ConfigDataFunction identity() { + return (input) -> input; + } + + @Contract(pure = true) + static @NotNull ConfigDataFunction required() { + return (input) -> { + throw new IllegalArgumentException("Please specify the value parser."); + }; + } + + @Contract(pure = true) + static @NotNull ConfigDataFunction toObject() { + return (input) -> input; + } + + + @Contract(pure = true) + static @NotNull ConfigDataFunction castObject(Class valueClass) { + return (input) -> { + if (valueClass.isInstance(input)) return valueClass.cast(input); + else throw new IllegalArgumentException("Cannot cast value to " + valueClass.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigDataFunction castFromString(Class valueClass) { + return (input) -> { + if (valueClass.isInstance(input)) return valueClass.cast(input); + else throw new IllegalArgumentException("Cannot cast string to " + valueClass.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigDataFunction castToString() { + return (input) -> { + if (input instanceof String) return (String) input; + else return input.toString(); + }; + } + + +} + + diff --git a/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigValueParser.java b/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigValueParser.java new file mode 100644 index 0000000..4f3b894 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/function/ConfigValueParser.java @@ -0,0 +1,164 @@ +package cc.carm.lib.configuration.core.function; + + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +@FunctionalInterface +public interface ConfigValueParser { + + @Nullable R parse(@NotNull T data, @Nullable R defaultValue) throws Exception; + + default ConfigValueParser andThen(@NotNull ConfigValueParser after) { + Objects.requireNonNull(after); + return ((data, defaultValue) -> { + R result = parse(data, null); + if (result == null) return defaultValue; + else return after.parse(result, defaultValue); + }); + } + + default ConfigValueParser compose(@NotNull ConfigDataFunction before) { + Objects.requireNonNull(before); + return ((data, defaultValue) -> { + T result = before.parse(data); + return parse(result, defaultValue); + }); + } + + + @Contract(pure = true) + static @NotNull ConfigValueParser identity() { + return (input, defaultValue) -> input; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser toObject() { + return (input, defaultValue) -> input; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser required() { + return (input, defaultValue) -> { + throw new IllegalArgumentException("Please specify the value parser."); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser castObject(Class valueClass) { + return (input, defaultValue) -> { + if (valueClass.isInstance(input)) return valueClass.cast(input); + else throw new IllegalArgumentException("Cannot cast value to " + valueClass.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser parseString(Class valueClass) { + return (input, defaultValue) -> { + if (valueClass.isInstance(input)) return valueClass.cast(input); + else throw new IllegalArgumentException("Cannot cast string to " + valueClass.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser castToString() { + return (input, defaultValue) -> { + if (input instanceof String) return (String) input; + else return input.toString(); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser castToString(Class clazz) { + return (input, defaultValue) -> { + if (input instanceof String) return (String) input; + else return input.toString(); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser intValue() { + return (input, defaultValue) -> { + if (input instanceof Integer) { + return (Integer) input; + } else if (input instanceof Number) { + return ((Number) input).intValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Integer.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser shortValue() { + return (input, defaultValue) -> { + if (input instanceof Short) { + return (Short) input; + } else if (input instanceof Number) { + return ((Number) input).shortValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Short.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser doubleValue() { + return (input, defaultValue) -> { + if (input instanceof Double) { + return (Double) input; + } else if (input instanceof Number) { + return ((Number) input).doubleValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Double.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser byteValue() { + return (input, defaultValue) -> { + if (input instanceof Byte) { + return (Byte) input; + } else if (input instanceof Number) { + return ((Number) input).byteValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Byte.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser floatValue() { + return (input, defaultValue) -> { + if (input instanceof Float) { + return (Float) input; + } else if (input instanceof Number) { + return ((Number) input).floatValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Float.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser longValue() { + return (input, defaultValue) -> { + if (input instanceof Long) { + return (Long) input; + } else if (input instanceof Number) { + return ((Number) input).longValue(); + } else throw new IllegalArgumentException("Cannot cast value to " + Long.class.getName()); + }; + } + + @Contract(pure = true) + static @NotNull ConfigValueParser booleanValue() { + return (input, defaultValue) -> { + if (input instanceof Boolean) { + return (Boolean) input; + } else if (input instanceof String) { + String s = (String) input; + return Boolean.parseBoolean(s) || "yes".equalsIgnoreCase(s); + } else if (input instanceof Integer) { + return ((Integer) input) == 1; + } else throw new IllegalArgumentException("Cannot cast value to " + Boolean.class.getName()); + }; + } + +} + + diff --git a/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java new file mode 100644 index 0000000..e483fcb --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java @@ -0,0 +1,38 @@ +package cc.carm.lib.configuration.core.source; + +import cc.carm.lib.configuration.core.ConfigInitializer; +import cc.carm.lib.configuration.core.ConfigurationRoot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class ConfigurationProvider { + + protected long updateTime; + + public ConfigurationProvider() { + this.updateTime = System.currentTimeMillis(); + } + + public long getUpdateTime() { + return updateTime; + } + + public boolean isExpired(long time) { + return this.updateTime > time; + } + + public abstract @NotNull ConfigurationWrapper getConfiguration(); + + public abstract void reload() throws Exception; + + public abstract void save() throws Exception; + + public abstract void setComments(@NotNull String path, @NotNull String... comments); + + public abstract @Nullable String[] getComments(@NotNull String path); + + public void initialize(Class configClazz) { + ConfigInitializer.initialize(this, configClazz, true); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationReader.java b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationReader.java new file mode 100644 index 0000000..2985e33 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationReader.java @@ -0,0 +1,144 @@ +package cc.carm.lib.configuration.core.source; + +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +interface ConfigurationReader { + + ConfigurationWrapper getWrapper(); + + default boolean isBoolean(@NotNull String path) { + return getWrapper().isType(path, Boolean.class); + } + + default boolean getBoolean(@NotNull String path) { + return getBoolean(path, false); + } + + @Contract("_, !null -> !null") + default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) { + return getWrapper().get(path, def, ConfigValueParser.booleanValue()); + } + + default @Nullable Boolean isByte(@NotNull String path) { + return getWrapper().isType(path, Byte.class); + } + + + default @Nullable Byte getByte(@NotNull String path) { + return getByte(path, (byte) 0); + } + + @Contract("_, !null -> !null") + default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) { + return getWrapper().get(path, def, ConfigValueParser.byteValue()); + } + + + default boolean isShort(@NotNull String path) { + return getWrapper().isType(path, Short.class); + } + + + default @Nullable Short getShort(@NotNull String path) { + return getShort(path, (short) 0); + } + + @Contract("_, !null -> !null") + default @Nullable Short getShort(@NotNull String path, @Nullable Short def) { + return getWrapper().get(path, def, ConfigValueParser.shortValue()); + } + + + default boolean isInt(@NotNull String path) { + return getWrapper().isType(path, Integer.class); + } + + + default @Nullable Integer getInt(@NotNull String path) { + return getInt(path, 0); + } + + @Contract("_, !null -> !null") + default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) { + return getWrapper().get(path, def, ConfigValueParser.intValue()); + } + + + default boolean isLong(@NotNull String path) { + return getWrapper().isType(path, Long.class); + } + + + default @Nullable Long getLong(@NotNull String path) { + return getLong(path, 0L); + } + + @Contract("_, !null -> !null") + default @Nullable Long getLong(@NotNull String path, @Nullable Long def) { + return getWrapper().get(path, def, ConfigValueParser.longValue()); + } + + + default boolean isFloat(@NotNull String path) { + return getWrapper().isType(path, Float.class); + } + + + default @Nullable Float getFloat(@NotNull String path) { + return getFloat(path, 0.0F); + } + + @Contract("_, !null -> !null") + default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) { + return getWrapper().get(path, def, ConfigValueParser.floatValue()); + } + + + default boolean isDouble(@NotNull String path) { + return getWrapper().isType(path, Double.class); + } + + + default @Nullable Double getDouble(@NotNull String path) { + return getDouble(path, 0.0D); + } + + @Contract("_, !null -> !null") + default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) { + return getWrapper().get(path, def, ConfigValueParser.doubleValue()); + } + + + default boolean isChar(@NotNull String path) { + return getWrapper().isType(path, Boolean.class); + } + + + default @Nullable Character getChar(@NotNull String path) { + return getChar(path, null); + } + + @Contract("_, !null -> !null") + default @Nullable Character getChar(@NotNull String path, @Nullable Character def) { + return getWrapper().get(path, def, Character.class); + } + + + default boolean isString(@NotNull String path) { + return getWrapper().isType(path, String.class); + } + + default @Nullable String getString(@NotNull String path) { + return getString(path, null); + } + + @Contract("_, !null -> !null") + default @Nullable String getString(@NotNull String path, @Nullable String def) { + return getWrapper().get(path, def, String.class); + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationWrapper.java b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationWrapper.java new file mode 100644 index 0000000..6795775 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationWrapper.java @@ -0,0 +1,72 @@ +package cc.carm.lib.configuration.core.source; + +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ConfigurationWrapper extends ConfigurationReader{ + + @Override + default ConfigurationWrapper getWrapper() { + return this; + } + + @NotNull + Set getKeys(boolean deep); + + @NotNull + Map getValues(boolean deep); + + void set(@NotNull String path, @Nullable Object value); + + boolean contains(@NotNull String path); + + default boolean isType(@NotNull String path, @NotNull Class typeClass) { + return typeClass.isInstance(get(path)); + } + + @Nullable Object get(@NotNull String path); + + default @Nullable T get(@NotNull String path, @NotNull Class clazz) { + return get(path, null, clazz); + } + + default @Nullable T get(@NotNull String path, @NotNull ConfigValueParser parser) { + return get(path, null, parser); + } + + @Contract("_,!null,_->!null") + default @Nullable T get(@NotNull String path, @Nullable T defaultValue, @NotNull Class clazz) { + return get(path, defaultValue, ConfigValueParser.castObject(clazz)); + } + + @Contract("_,!null,_->!null") + default @Nullable T get(@NotNull String path, @Nullable T defaultValue, + @NotNull ConfigValueParser parser) { + Object value = get(path); + if (value != null) { + try { + return parser.parse(value, defaultValue); + } catch (Exception e) { + e.printStackTrace(); + } + } + return defaultValue; + } + + boolean isList(@NotNull String path); + + @Nullable List getList(@NotNull String path); + + boolean isConfigurationSection(@NotNull String path); + + @Nullable + ConfigurationWrapper getConfigurationSection(@NotNull String path); + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/source/impl/FileConfigProvider.java b/core/src/main/java/cc/carm/lib/configuration/core/source/impl/FileConfigProvider.java new file mode 100644 index 0000000..ff36ca5 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/source/impl/FileConfigProvider.java @@ -0,0 +1,83 @@ +package cc.carm.lib.configuration.core.source.impl; + +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.Objects; + +public abstract class FileConfigProvider extends ConfigurationProvider { + + protected final @NotNull File file; + + public FileConfigProvider(@NotNull File file) { + this.file = file; + } + + public @NotNull File getFile() { + return file; + } + + public void initializeFile(@Nullable String sourcePath) throws IOException { + if (getFile().exists()) return; + if (!getFile().getParentFile().exists() && !getFile().getParentFile().mkdirs()) { + throw new IOException("Failed to create directory " + file.getParentFile().getAbsolutePath()); + } + if (!getFile().createNewFile()) { + throw new IOException("Failed to create file " + file.getAbsolutePath()); + } + if (sourcePath == null) return; + try { + saveResource(sourcePath, true); + } catch (Exception ignored) { + } + } + + public void saveResource(@NotNull String resourcePath, boolean replace) + throws NullPointerException, IOException, IllegalArgumentException { + Objects.requireNonNull(resourcePath, "ResourcePath cannot be null"); + if (resourcePath.equals("")) throw new IllegalArgumentException("ResourcePath cannot be empty"); + + resourcePath = resourcePath.replace('\\', '/'); + InputStream in = getResource(resourcePath); + if (in == null) throw new IllegalArgumentException("The resource '" + resourcePath + "' not exists"); + + + int lastIndex = resourcePath.lastIndexOf('/'); + File outDir = new File(file, resourcePath.substring(0, Math.max(lastIndex, 0))); + + if (!outDir.exists() && !outDir.mkdirs()) throw new IOException("Failed to create directory " + outDir); + if (!file.exists() || replace) { + try { + + OutputStream out = new FileOutputStream(file); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + @Nullable + public InputStream getResource(@NotNull String filename) { + try { + URL url = this.getClass().getClassLoader().getResource(filename); + if (url == null) return null; + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } catch (IOException ex) { + return null; + } + } +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/util/MapFactory.java b/core/src/main/java/cc/carm/lib/configuration/core/util/MapFactory.java new file mode 100644 index 0000000..7fe98cc --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/util/MapFactory.java @@ -0,0 +1,67 @@ +package cc.carm.lib.configuration.core.util; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +public class MapFactory, K, V> { + + private final S map; + + protected MapFactory(S map) { + this.map = map; + } + + public MapFactory put(K key, V value) { + this.map.put(key, value); + return this; + } + + public MapFactory remove(K key) { + this.map.remove(key); + return this; + } + + public MapFactory clear() { + this.map.clear(); + return this; + } + + public S build() { + return get(); + } + + public S get() { + return map; + } + + public static MapFactory, K, V> hashMap() { + return new MapFactory<>(new HashMap<>()); + } + + public static MapFactory, K, V> hashMap(K firstKey, V firstValue) { + return MapFactory.hashMap().put(firstKey, firstValue); + } + + public static MapFactory, K, V> linkedMap() { + return of(new LinkedHashMap<>()); + } + + public static MapFactory, K, V> linkedMap(K firstKey, V firstValue) { + return MapFactory.linkedMap().put(firstKey, firstValue); + } + + public static , V> MapFactory, K, V> treeMap() { + return of(new TreeMap<>()); + } + + public static , V> MapFactory, K, V> treeMap(K firstKey, V firstValue) { + return MapFactory.treeMap().put(firstKey, firstValue); + } + + public static , K, V> MapFactory of(M map) { + return new MapFactory<>(map); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/CachedConfigValue.java b/core/src/main/java/cc/carm/lib/configuration/core/value/CachedConfigValue.java new file mode 100644 index 0000000..31d649e --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/CachedConfigValue.java @@ -0,0 +1,40 @@ +package cc.carm.lib.configuration.core.value; + +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class CachedConfigValue extends ConfigValue { + + + protected @Nullable T cachedValue; + protected long parsedTime = -1; + + public CachedConfigValue(@Nullable ConfigurationProvider provider, @Nullable String sectionPath, + @NotNull String[] comments, @Nullable T defaultValue) { + super(provider, sectionPath, comments, defaultValue); + } + + protected T updateCache(T value) { + this.parsedTime = System.currentTimeMillis(); + this.cachedValue = value; + return getCachedValue(); + } + + public @Nullable T getCachedValue() { + return cachedValue; + } + + public boolean isExpired() { + return this.parsedTime <= 0 || getProvider().isExpired(this.parsedTime); + } + + protected final T useDefault() { + return useOrDefault(null); + } + + protected final T useOrDefault(@Nullable T value) { + return updateCache(this.defaultValue == null ? value : this.defaultValue); + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/ConfigValue.java b/core/src/main/java/cc/carm/lib/configuration/core/value/ConfigValue.java new file mode 100644 index 0000000..5270809 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/ConfigValue.java @@ -0,0 +1,110 @@ +package cc.carm.lib.configuration.core.value; + +import cc.carm.lib.configuration.core.builder.ConfigBuilder; +import cc.carm.lib.configuration.core.builder.value.ConfigValueBuilder; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +public abstract class ConfigValue { + + public static ConfigValueBuilder builder(Class valueClass) { + return ConfigBuilder.asValue(valueClass); + } + + public static ConfigValue of(Class valueClass) { + return builder(valueClass).fromObject().build(); + } + + protected @Nullable T defaultValue; + + protected @Nullable ConfigurationProvider provider; + protected @Nullable String configPath; + protected @NotNull String[] comments; + + public ConfigValue(@Nullable ConfigurationProvider provider, @Nullable String configPath, + @NotNull String[] comments, @Nullable T defaultValue) { + this.provider = provider; + this.configPath = configPath; + this.comments = comments; + + this.defaultValue = defaultValue; + } + + public void initialize(@NotNull ConfigurationProvider provider, @NotNull String configPath, + @NotNull String... comments) { + if (this.provider == null) this.provider = provider; + if (this.configPath == null) this.configPath = configPath; + if (this.comments.length == 0) this.comments = comments; + + this.provider.setComments(this.configPath, this.comments); + get(); + } + + public @Nullable T getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(@Nullable T defaultValue) { + this.defaultValue = defaultValue; + } + + public abstract @Nullable T get(); + + public @Nullable T getOrDefault() { + return Optional.ofNullable(get()).orElse(getDefaultValue()); + } + + public @NotNull T getNotNull() { + return Objects.requireNonNull(getOrDefault(), "Value(" + configPath + ") is null."); + } + + public @NotNull Optional<@Nullable T> getOptional() { + return Optional.ofNullable(get()); + } + + public abstract void set(@Nullable T value); + + public void setDefault() { + Optional.ofNullable(getDefaultValue()).ifPresent(this::set); + } + + public @NotNull ConfigurationProvider getProvider() { + return Optional.ofNullable(this.provider) + .orElseThrow(() -> new IllegalStateException("Value(" + configPath + ") does not have a provider.")); + } + + public final @NotNull ConfigurationWrapper getConfiguration() { + try { + return getProvider().getConfiguration(); + } catch (Exception ex) { + throw new IllegalStateException("Value(" + configPath + ") has not been initialized", ex); + } + } + + public @NotNull String getConfigPath() { + return Optional.ofNullable(this.configPath) + .orElseThrow(() -> new IllegalStateException("No section path provided.")); + } + + protected Object getValue() { + return getConfiguration().get(getConfigPath()); + } + + protected void setValue(@Nullable Object value) { + getConfiguration().set(getConfigPath(), value); + } + + public String[] getComments() { + return comments; + } + + public void setComments(String[] comments) { + this.comments = comments; + } + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredList.java b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredList.java new file mode 100644 index 0000000..4f40410 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredList.java @@ -0,0 +1,72 @@ +package cc.carm.lib.configuration.core.value.type; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.value.CachedConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class ConfiguredList extends CachedConfigValue> { + + protected final @NotNull Class valueClass; + + protected final @NotNull ConfigDataFunction parser; + protected final @NotNull ConfigDataFunction serializer; + + public ConfiguredList(@Nullable ConfigurationProvider provider, + @Nullable String sectionPath, @NotNull String[] comments, + @NotNull Class valueClass, @Nullable List defaultValue, + @NotNull ConfigDataFunction parser, + @NotNull ConfigDataFunction serializer) { + super(provider, sectionPath, comments, defaultValue); + this.valueClass = valueClass; + this.parser = parser; + this.serializer = serializer; + } + + @Override + public @NotNull List get() { + if (isExpired()) { // 已过时的数据,需要重新解析一次。 + List list = new ArrayList<>(); + + List data = getConfiguration().getList(getConfigPath()); + if (data == null || data.isEmpty()) return useOrDefault(list); + + for (Object dataVal : data) { + if (dataVal == null) continue; + try { + list.add(parser.parse(dataVal)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return updateCache(list); + } else if (getCachedValue() != null) return getCachedValue(); + else if (getDefaultValue() != null) return getDefaultValue(); + else return new ArrayList<>(); + } + + @Override + public void set(@Nullable List value) { + updateCache(value); + if (value == null) setValue(null); + else { + List data = new ArrayList<>(); + for (V val : value) { + if (val == null) continue; + try { + data.add(serializer.parse(val)); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + setValue(data); + } + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredMap.java b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredMap.java new file mode 100644 index 0000000..e1a9801 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredMap.java @@ -0,0 +1,125 @@ +package cc.carm.lib.configuration.core.value.type; + +import cc.carm.lib.configuration.core.builder.ConfigBuilder; +import cc.carm.lib.configuration.core.builder.map.ConfigMapBuilder; +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.value.CachedConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class ConfiguredMap extends CachedConfigValue> { + + public static @NotNull ConfigMapBuilder, K, V> builder(@NotNull Class keyClass, + @NotNull Class valueClass) { + return ConfigBuilder.asMap(keyClass, valueClass); + } + + protected final @NotNull Supplier> supplier; + + protected final @NotNull Class keyClass; + protected final @NotNull Class valueClass; + + protected final @NotNull ConfigDataFunction keyParser; + protected final @NotNull ConfigDataFunction valueParser; + + protected final @NotNull ConfigDataFunction keySerializer; + protected final @NotNull ConfigDataFunction valueSerializer; + + public ConfiguredMap(@Nullable ConfigurationProvider provider, + @Nullable String sectionPath, @NotNull String[] comments, + @Nullable Map defaultValue, @NotNull Supplier> supplier, + @NotNull Class keyClass, @NotNull ConfigDataFunction keyParser, + @NotNull Class valueClass, @NotNull ConfigDataFunction valueParser, + @NotNull ConfigDataFunction keySerializer, + @NotNull ConfigDataFunction valueSerializer) { + super(provider, sectionPath, comments, defaultValue); + this.supplier = supplier; + this.keyClass = keyClass; + this.valueClass = valueClass; + this.keyParser = keyParser; + this.valueParser = valueParser; + this.keySerializer = keySerializer; + this.valueSerializer = valueSerializer; + } + + public @NotNull Class getKeyClass() { + return keyClass; + } + + public @NotNull Class getValueClass() { + return valueClass; + } + + public @NotNull ConfigDataFunction getKeyParser() { + return keyParser; + } + + public @NotNull ConfigDataFunction getValueParser() { + return valueParser; + } + + public @NotNull ConfigDataFunction getKeySerializer() { + return keySerializer; + } + + public @NotNull ConfigDataFunction getValueSerializer() { + return valueSerializer; + } + + @Override + public @NotNull Map get() { + if (isExpired()) { // 已过时的数据,需要重新解析一次。 + Map map = supplier.get(); + + ConfigurationWrapper section = getConfiguration().getConfigurationSection(getConfigPath()); + if (section == null) return useOrDefault(map); + + Set keys = section.getKeys(false); + if (keys.isEmpty()) return useOrDefault(map); + + for (String dataKey : keys) { + Object dataVal = section.get(dataKey); + if (dataVal == null) continue; + try { + K key = keyParser.parse(dataKey); + V value = valueParser.parse(dataVal); + map.put(key, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return updateCache(map); + } else if (getCachedValue() != null) return getCachedValue(); + else if (getDefaultValue() != null) return getDefaultValue(); + else return supplier.get(); + } + + @Override + public void set(Map value) { + updateCache(value); + if (value == null) setValue(null); + else { + Map data = new LinkedHashMap<>(); + for (Map.Entry entry : value.entrySet()) { + try { + String key = keySerializer.parse(entry.getKey()); + Object val = valueSerializer.parse(entry.getValue()); + data.put(key, val); + } catch (Exception e) { + e.printStackTrace(); + } + } + setValue(data); + } + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredSection.java b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredSection.java new file mode 100644 index 0000000..c886362 --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredSection.java @@ -0,0 +1,75 @@ +package cc.carm.lib.configuration.core.value.type; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.value.CachedConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; + +public class ConfiguredSection extends CachedConfigValue { + + protected final @NotNull Class valueClass; + + protected final @NotNull ConfigValueParser parser; + protected final @NotNull ConfigDataFunction> serializer; + + public ConfiguredSection(@Nullable ConfigurationProvider provider, + @Nullable String sectionPath, @NotNull String[] comments, + @NotNull Class valueClass, @Nullable V defaultValue, + @NotNull ConfigValueParser parser, + @NotNull ConfigDataFunction> serializer) { + super(provider, sectionPath, comments, defaultValue); + this.valueClass = valueClass; + this.parser = parser; + this.serializer = serializer; + } + + public @NotNull Class getValueClass() { + return valueClass; + } + + public @NotNull ConfigValueParser getParser() { + return parser; + } + + public @NotNull ConfigDataFunction> getSerializer() { + return serializer; + } + + @Override + public @Nullable V get() { + if (isExpired()) { // 已过时的数据,需要重新解析一次。 + ConfigurationWrapper section = getConfiguration().getConfigurationSection(getConfigPath()); + if (section == null) return useDefault(); + try { + // 若未出现错误,则直接更新缓存并返回。 + return updateCache(this.parser.parse(section, this.defaultValue)); + } catch (Exception e) { + // 出现了解析错误,提示并返回默认值。 + e.printStackTrace(); + return useDefault(); + } + } else return Optional.ofNullable(getCachedValue()).orElse(defaultValue); + } + + @Override + public void set(V value) { + updateCache(value); + if (value == null) setValue(null); + else { + try { + setValue(serializer.parse(value)); +// getConfiguration().createSection(getSectionPath(), serializer.parse(value)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + +} diff --git a/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredValue.java b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredValue.java new file mode 100644 index 0000000..57043cc --- /dev/null +++ b/core/src/main/java/cc/carm/lib/configuration/core/value/type/ConfiguredValue.java @@ -0,0 +1,69 @@ +package cc.carm.lib.configuration.core.value.type; + +import cc.carm.lib.configuration.core.function.ConfigDataFunction; +import cc.carm.lib.configuration.core.function.ConfigValueParser; +import cc.carm.lib.configuration.core.source.ConfigurationProvider; +import cc.carm.lib.configuration.core.value.CachedConfigValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class ConfiguredValue extends CachedConfigValue { + + protected final @NotNull Class valueClass; + + protected final @NotNull ConfigValueParser parser; + protected final @NotNull ConfigDataFunction serializer; + + public ConfiguredValue(@Nullable ConfigurationProvider provider, + @Nullable String sectionPath, @NotNull String[] comments, + @NotNull Class valueClass, @Nullable V defaultValue, + @NotNull ConfigValueParser parser, + @NotNull ConfigDataFunction serializer) { + + super(provider, sectionPath, comments, defaultValue); + this.valueClass = valueClass; + this.parser = parser; + this.serializer = serializer; + } + + public @NotNull Class getValueClass() { + return valueClass; + } + + public @NotNull ConfigValueParser getParser() { + return parser; + } + + @Override + public V get() { + if (isExpired()) { // 已过时的数据,需要重新解析一次。 + Object value = getConfiguration().get(getConfigPath()); + if (value == null) return useDefault(); // 获取的值不存在,直接使用默认值。 + try { + // 若未出现错误,则直接更新缓存并返回。 + return updateCache(this.parser.parse(value, this.defaultValue)); + } catch (Exception e) { + // 出现了解析错误,提示并返回默认值。 + e.printStackTrace(); + return useDefault(); + } + } else return Optional.ofNullable(getCachedValue()).orElse(defaultValue); + } + + @Override + public void set(V value) { + updateCache(value); + if (value == null) setValue(null); + else { + try { + setValue(serializer.parse(value)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} + diff --git a/impl/yaml/pom.xml b/impl/yaml/pom.xml new file mode 100644 index 0000000..9f2633a --- /dev/null +++ b/impl/yaml/pom.xml @@ -0,0 +1,47 @@ + + + + easyconfiguration-parent + cc.carm.lib + 1.0.0 + ../../pom.xml + + 4.0.0 + + ${java.version} + ${java.version} + + + easyconfiguration-yaml + jar + + + + github + GitHub Packages + https://maven.pkg.github.com/CarmJos/YamlConfiguration-Commented/ + + + + + + + ${project.parent.groupId} + easyconfiguration-core + ${project.parent.version} + compile + + + + org.bspfsystems + yamlconfiguration-commented + 2.0.1 + compile + + + + + + \ No newline at end of file diff --git a/impl/yaml/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java b/impl/yaml/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java new file mode 100644 index 0000000..571dd13 --- /dev/null +++ b/impl/yaml/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java @@ -0,0 +1,34 @@ +package cc.carm.lib.configuration; + +import cc.carm.lib.configuration.yaml.YamlConfigProvider; + +import java.io.File; +import java.io.IOException; + +public class EasyConfiguration { + + public static YamlConfigProvider from(File file, String source) { + YamlConfigProvider provider = new YamlConfigProvider(file); + try { + provider.initializeFile(source); + provider.initialize(); + } catch (IOException e) { + e.printStackTrace(); + } + return provider; + } + + public static YamlConfigProvider from(File file) { + return from(file, file.getName()); + } + + public static YamlConfigProvider from(String fileName) { + return from(fileName, fileName); + } + + public static YamlConfigProvider from(String fileName, String source) { + return from(new File(fileName), source); + } + + +} diff --git a/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlComments.java b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlComments.java new file mode 100644 index 0000000..96946ea --- /dev/null +++ b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlComments.java @@ -0,0 +1,35 @@ +package cc.carm.lib.configuration.yaml; + +import org.bspfsystems.yamlconfiguration.commented.CommentsProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class YamlComments implements CommentsProvider { + + Map comments = new HashMap<>(); + + protected Map getComments() { + return comments; + } + + public void set(@NotNull String path, @NotNull String... comments) { + if (comments.length == 0) { + getComments().remove(path); + } else { + getComments().put(path, comments); + } + } + + public @Nullable String[] get(@NotNull String path) { + return getComments().get(path); + } + + @Override + public String[] apply(String s) { + return get(s); + } + +} diff --git a/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlConfigProvider.java b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlConfigProvider.java new file mode 100644 index 0000000..8ada47d --- /dev/null +++ b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlConfigProvider.java @@ -0,0 +1,49 @@ +package cc.carm.lib.configuration.yaml; + +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; +import org.bspfsystems.yamlconfiguration.commented.CommentedYamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + +public class YamlConfigProvider extends FileConfigProvider { + + YamlComments comments = new YamlComments(); + CommentedYamlConfiguration configuration; + + public YamlConfigProvider(@NotNull File file) { + super(file); + } + + public void initialize() { + this.configuration = CommentedYamlConfiguration.loadConfiguration(comments, file); + } + + @Override + public @NotNull ConfigurationWrapper getConfiguration() { + return YamlSectionWrapper.of(configuration); + } + + @Override + public void reload() throws Exception { + configuration.load(getFile()); + } + + @Override + public void save() throws Exception { + configuration.save(getFile()); + } + + @Override + public void setComments(@NotNull String path, @NotNull String... comments) { + this.comments.set(path, comments); + } + + @Override + public @Nullable String[] getComments(@NotNull String path) { + return this.comments.get(path); + } + +} diff --git a/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlSectionWrapper.java b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlSectionWrapper.java new file mode 100644 index 0000000..e7aa29d --- /dev/null +++ b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YamlSectionWrapper.java @@ -0,0 +1,71 @@ +package cc.carm.lib.configuration.yaml; + +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import org.bspfsystems.yamlconfiguration.configuration.ConfigurationSection; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class YamlSectionWrapper implements ConfigurationWrapper { + + private final ConfigurationSection section; + + private YamlSectionWrapper(ConfigurationSection section) { + this.section = section; + } + + @Contract("!null->!null") + public static @Nullable YamlSectionWrapper of(@Nullable ConfigurationSection section) { + return section == null ? null : new YamlSectionWrapper(section); + } + + @Override + public @NotNull Set getKeys(boolean deep) { + return new LinkedHashSet<>(section.getKeys(deep)); + } + + @Override + public @NotNull Map getValues(boolean deep) { + return section.getValues(deep); + } + + @Override + public void set(@NotNull String path, @Nullable Object value) { + this.section.set(path, value); + } + + @Override + public boolean contains(@NotNull String path) { + return this.section.contains(path); + } + + @Override + public @Nullable Object get(@NotNull String path) { + return this.section.get(path); + } + + @Override + public boolean isList(@NotNull String path) { + return this.section.isList(path); + } + + @Override + public @Nullable List getList(@NotNull String path) { + return this.section.getList(path); + } + + @Override + public boolean isConfigurationSection(@NotNull String path) { + return this.section.isConfigurationSection(path); + } + + @Override + public @Nullable ConfigurationWrapper getConfigurationSection(@NotNull String path) { + return of(this.section.getConfigurationSection(path)); + } +} diff --git a/impl/yaml/src/test/java/config/ConfigTester.java b/impl/yaml/src/test/java/config/ConfigTester.java new file mode 100644 index 0000000..8762e67 --- /dev/null +++ b/impl/yaml/src/test/java/config/ConfigTester.java @@ -0,0 +1,53 @@ +package config; + +import cc.carm.lib.configuration.EasyConfiguration; +import cc.carm.lib.configuration.core.ConfigInitializer; +import cc.carm.lib.configuration.yaml.YamlConfigProvider; +import config.misc.TestUser; +import config.source.TestConfiguration; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class ConfigTester { + + @Test + public void onTest() { + + YamlConfigProvider provider = EasyConfiguration.from("target/config.yml", "config.yml"); + ConfigInitializer.initialize(provider, TestConfiguration.class, true); + + System.out.println("before: " + TestConfiguration.Sub.UUID_CONFIG_VALUE.get()); + TestConfiguration.Sub.UUID_CONFIG_VALUE.set(UUID.randomUUID()); + System.out.println("after: " + TestConfiguration.Sub.UUID_CONFIG_VALUE.get()); + + + TestConfiguration.Sub.That.Operators.getNotNull().forEach(System.out::println); + List operators = IntStream.range(0, 5).mapToObj(i -> UUID.randomUUID()).collect(Collectors.toList()); + TestConfiguration.Sub.That.Operators.set(operators); + + System.out.println(TestConfiguration.USER.get()); + TestUser b = new TestUser(UUID.randomUUID().toString().substring(0, 3), UUID.randomUUID()); + TestConfiguration.USER.set(b); + + TestConfiguration.USERS.getNotNull().forEach((k, v) -> System.out.println(k + ": " + v)); + LinkedHashMap data = new LinkedHashMap<>(); + for (int i = 0; i < 5; i++) { + data.put((int) (1000 * Math.random()), UUID.randomUUID()); + } + TestConfiguration.USERS.set(data); + + try { + provider.save(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + +} diff --git a/impl/yaml/src/test/java/config/misc/TestUser.java b/impl/yaml/src/test/java/config/misc/TestUser.java new file mode 100644 index 0000000..4f826eb --- /dev/null +++ b/impl/yaml/src/test/java/config/misc/TestUser.java @@ -0,0 +1,38 @@ +package config.misc; + +import java.util.UUID; + +public class TestUser { + + public String name; + public UUID uuid; + + public TestUser(String name, UUID uuid) { + this.name = name; + this.uuid = uuid; + } + + public void setName(String name) { + this.name = name; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public UUID getUuid() { + return uuid; + } + + @Override + public String toString() { + return "TestUser{" + + "name='" + name + '\'' + + ", uuid=" + uuid + + '}'; + } +} \ No newline at end of file diff --git a/impl/yaml/src/test/java/config/source/TestConfiguration.java b/impl/yaml/src/test/java/config/source/TestConfiguration.java new file mode 100644 index 0000000..ce6628b --- /dev/null +++ b/impl/yaml/src/test/java/config/source/TestConfiguration.java @@ -0,0 +1,58 @@ +package config.source; + +import cc.carm.lib.configuration.core.ConfigurationRoot; +import cc.carm.lib.configuration.core.annotation.ConfigComment; +import cc.carm.lib.configuration.core.annotation.ConfigPath; +import cc.carm.lib.configuration.core.builder.ConfigBuilder; +import cc.carm.lib.configuration.core.util.MapFactory; +import cc.carm.lib.configuration.core.value.ConfigValue; +import config.misc.TestUser; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +public class TestConfiguration extends ConfigurationRoot { + + @ConfigComment({"User测试"}) + public static final ConfigValue USER = ConfigBuilder + .asValue(TestUser.class).fromSection() + .defaults(new TestUser("Carm", UUID.randomUUID())) + .parseValue((section, defaultValue) -> new TestUser( + section.getString("name"), + UUID.fromString(section.getString("user.uuid", UUID.randomUUID().toString())) + )).serializeValue(user -> MapFactory.linkedMap() + .put("name", user.getName()) + .put("user.uuid", user.getUuid().toString()) + .build() + ).build(); + + @ConfigComment({"[ID-UUID] 对照表", "", "用于测试Map类型的解析与序列化保存"}) + public static final ConfigValue> USERS = ConfigBuilder + .asMap(Integer.class, UUID.class).fromString() + .parseKey(Integer::parseInt) + .parseValue(v -> Objects.requireNonNull(UUID.fromString(v))) + .build(); + + public static class Sub { + + @ConfigPath("uuid") + public static final ConfigValue UUID_CONFIG_VALUE = ConfigBuilder + .asValue(UUID.class).fromString() + .parseValue((data, defaultValue) -> UUID.fromString(data)) + .build(); + + @ConfigPath("nothing") + public static class That { + + public static final ConfigValue> Operators = ConfigBuilder.asList(UUID.class).fromString() + .parseValue(s -> Objects.requireNonNull(UUID.fromString(s))).build(); + + } + + + } + + +} diff --git a/impl/yaml/src/test/resources/config.yml b/impl/yaml/src/test/resources/config.yml new file mode 100644 index 0000000..749f192 --- /dev/null +++ b/impl/yaml/src/test/resources/config.yml @@ -0,0 +1 @@ +something: 123123 \ No newline at end of file diff --git a/platform/bungee/pom.xml b/platform/bungee/pom.xml new file mode 100644 index 0000000..df4d331 --- /dev/null +++ b/platform/bungee/pom.xml @@ -0,0 +1,47 @@ + + + + easyconfiguration-parent + cc.carm.lib + 1.0.0 + ../../pom.xml + + 4.0.0 + + ${java.version} + ${java.version} + + + easyconfiguration-bungee + jar + + + + + ${project.parent.groupId} + easyconfiguration-core + ${project.parent.version} + compile + + + + net.md-5 + bungeecord-api + 1.18-R0.1-SNAPSHOT + jar + provided + + + + net.md-5 + bungeecord-api + 1.18-R0.1-SNAPSHOT + javadoc + provided + + + + + \ No newline at end of file diff --git a/platform/bungee/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java b/platform/bungee/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java new file mode 100644 index 0000000..90a2397 --- /dev/null +++ b/platform/bungee/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java @@ -0,0 +1,34 @@ +package cc.carm.lib.configuration; + +import cc.carm.lib.configuration.bungee.BungeeConfigProvider; + +import java.io.File; +import java.io.IOException; + +public class EasyConfiguration { + + public static BungeeConfigProvider from(File file, String source) { + BungeeConfigProvider provider = new BungeeConfigProvider(file); + try { + provider.initializeFile(source); + provider.initialize(); + } catch (IOException e) { + e.printStackTrace(); + } + return provider; + } + + public static BungeeConfigProvider from(File file) { + return from(file, file.getName()); + } + + public static BungeeConfigProvider from(String fileName) { + return from(fileName, fileName); + } + + public static BungeeConfigProvider from(String fileName, String source) { + return from(new File(fileName), source); + } + + +} diff --git a/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeConfigProvider.java b/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeConfigProvider.java new file mode 100644 index 0000000..58b1cca --- /dev/null +++ b/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeConfigProvider.java @@ -0,0 +1,54 @@ +package cc.carm.lib.configuration.bungee; + +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +public class BungeeConfigProvider extends FileConfigProvider { + + Configuration configuration; + + public BungeeConfigProvider(@NotNull File file) { + super(file); + } + + public void initialize() throws IOException { + this.configuration = getLoader().load(file); + } + + @Override + public @NotNull ConfigurationWrapper getConfiguration() { + return BungeeSectionWrapper.of(configuration); + } + + @Override + public void reload() throws Exception { + this.configuration = getLoader().load(file); + } + + @Override + public void save() throws Exception { + getLoader().save(configuration, file); + } + + @Override + public void setComments(@NotNull String path, @NotNull String... comments) { + } + + @Override + public @Nullable String[] getComments(@NotNull String path) { + return null; + } + + public static ConfigurationProvider getLoader() { + return ConfigurationProvider.getProvider(YamlConfiguration.class); + } + +} diff --git a/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeSectionWrapper.java b/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeSectionWrapper.java new file mode 100644 index 0000000..fe9498e --- /dev/null +++ b/platform/bungee/src/main/java/cc/carm/lib/configuration/bungee/BungeeSectionWrapper.java @@ -0,0 +1,70 @@ +package cc.carm.lib.configuration.bungee; + +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import net.md_5.bungee.config.Configuration; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +public class BungeeSectionWrapper implements ConfigurationWrapper { + + private final Configuration section; + + private BungeeSectionWrapper(Configuration section) { + this.section = section; + } + + @Contract("!null->!null") + public static @Nullable BungeeSectionWrapper of(@Nullable Configuration section) { + return section == null ? null : new BungeeSectionWrapper(section); + } + + @Override + public @NotNull Set getKeys(boolean deep) { + return new LinkedHashSet<>(section.getKeys()); + } + + @Override + public @NotNull Map getValues(boolean deep) { + return section.getKeys().stream() + .collect(Collectors.toMap(key -> key, section::get, (a, b) -> b, LinkedHashMap::new)); + } + + @Override + public void set(@NotNull String path, @Nullable Object value) { + this.section.set(path, value); + } + + @Override + public boolean contains(@NotNull String path) { + return this.section.contains(path); + } + + @Override + public @Nullable Object get(@NotNull String path) { + return this.section.get(path); + } + + @Override + public boolean isList(@NotNull String path) { + return get(path) instanceof List; + } + + @Override + public @Nullable List getList(@NotNull String path) { + return this.section.getList(path); + } + + @Override + public boolean isConfigurationSection(@NotNull String path) { + return true; // No provided functions :( SRY + } + + @Override + public @Nullable ConfigurationWrapper getConfigurationSection(@NotNull String path) { + return of(this.section.getSection(path)); + } +} diff --git a/platform/spigot/pom.xml b/platform/spigot/pom.xml new file mode 100644 index 0000000..d866520 --- /dev/null +++ b/platform/spigot/pom.xml @@ -0,0 +1,39 @@ + + + + easyconfiguration-parent + cc.carm.lib + 1.0.0 + ../../pom.xml + + 4.0.0 + + ${java.version} + ${java.version} + + + easyconfiguration-spigot + jar + + + + + ${project.parent.groupId} + easyconfiguration-core + ${project.parent.version} + compile + + + + org.spigotmc + spigot-api + 1.18.1-R0.1-SNAPSHOT + provided + + + + + + \ No newline at end of file diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java new file mode 100644 index 0000000..9806662 --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/EasyConfiguration.java @@ -0,0 +1,35 @@ +package cc.carm.lib.configuration; + +import cc.carm.lib.configuration.spigot.SpigotConfigProvider; + +import java.io.File; +import java.io.IOException; + +public class EasyConfiguration { + + + public static SpigotConfigProvider from(File file, String source) { + SpigotConfigProvider provider = new SpigotConfigProvider(file); + try { + provider.initializeFile(source); + provider.initialize(); + } catch (IOException e) { + e.printStackTrace(); + } + return provider; + } + + public static SpigotConfigProvider from(File file) { + return from(file, file.getName()); + } + + public static SpigotConfigProvider from(String fileName) { + return from(fileName, fileName); + } + + public static SpigotConfigProvider from(String fileName, String source) { + return from(new File(fileName), source); + } + + +} diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedEmitter.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedEmitter.java new file mode 100644 index 0000000..06963ce --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedEmitter.java @@ -0,0 +1,1513 @@ +/* + * Copyright (c) 2008, http://www.snakeyaml.org + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cc.carm.lib.configuration.commented; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.Version; +import org.yaml.snakeyaml.emitter.Emitable; +import org.yaml.snakeyaml.emitter.EmitterException; +import org.yaml.snakeyaml.emitter.ScalarAnalysis; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.events.*; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.reader.StreamReader; +import org.yaml.snakeyaml.scanner.Constant; +import org.yaml.snakeyaml.util.ArrayStack; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.regex.Pattern; + +/** + * Copy of {@link org.yaml.snakeyaml.emitter.Emitter} with some ugly hooks. + */ +public final class CommentedEmitter implements Emitable { + public static final int MIN_INDENT = 1; + public static final int MAX_INDENT = 10; + private static final Map ESCAPE_REPLACEMENTS = new HashMap<>(); + private static final char[] SPACE = {' '}; + private final static Map DEFAULT_TAG_PREFIXES = new LinkedHashMap<>(); + private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$"); + private final static Pattern ANCHOR_FORMAT = Pattern.compile("^[-_\\w]*$"); + + static { + ESCAPE_REPLACEMENTS.put('\0', "0"); + ESCAPE_REPLACEMENTS.put('\u0007', "a"); + ESCAPE_REPLACEMENTS.put('\u0008', "b"); + ESCAPE_REPLACEMENTS.put('\u0009', "t"); + ESCAPE_REPLACEMENTS.put('\n', "n"); + ESCAPE_REPLACEMENTS.put('\u000B', "v"); + ESCAPE_REPLACEMENTS.put('\u000C', "f"); + ESCAPE_REPLACEMENTS.put('\r', "r"); + ESCAPE_REPLACEMENTS.put('\u001B', "e"); + ESCAPE_REPLACEMENTS.put('"', "\""); + ESCAPE_REPLACEMENTS.put('\\', "\\"); + ESCAPE_REPLACEMENTS.put('\u0085', "N"); + ESCAPE_REPLACEMENTS.put('\u00A0', "_"); + ESCAPE_REPLACEMENTS.put('\u2028', "L"); + ESCAPE_REPLACEMENTS.put('\u2029', "P"); + } + + // Encoding is defined by Writer (cannot be overriden by STREAM-START.) + // private Charset encoding; + + static { + DEFAULT_TAG_PREFIXES.put("!", "!"); + DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!"); + } + + // The stream should have the methods `write` and possibly `flush`. + private final Writer stream; + // Emitter is a state machine with a stack of states to handle nested + // structures. + private final ArrayStack states; + // Current event and the event queue. + private final Queue events; + // The current indentation level and the stack of previous indents. + private final ArrayStack indents; + // wytrem start + CommentsProvider commentsProvider; + // wytrem start + boolean isInSequence = false; + Stack paths = new Stack<>(); + String lastSimpleKey; + private EmitterState state; + private Event event; + private Integer indent; + // Flow level. + private int flowLevel; + // Contexts. + private boolean rootContext; + private boolean mappingContext; + private boolean simpleKeyContext; + // + // Characteristics of the last emitted character: + // - current position. + // - is it a whitespace? + // - is it an indention character + // (indentation space, '-', '?', or ':')? + // private int line; this variable is not used + private int column; + private boolean whitespace; + private boolean indention; + private boolean openEnded; + // Formatting details. + private final Boolean canonical; + // pretty print flow by adding extra line breaks + private final Boolean prettyFlow; + private final boolean allowUnicode; + private int bestIndent; + private final int indicatorIndent; + private int bestWidth; + private final char[] bestLineBreak; + private final boolean splitLines; + // wytrem end + // Tag prefixes. + private Map tagPrefixes; + // Prepared anchor and tag. + private String preparedAnchor; + private String preparedTag; + // Scalar analysis and style. + private ScalarAnalysis analysis; + // wytrem end + private DumperOptions.ScalarStyle style; + + // In some cases, we wait for a few next events before emitting. + + public CommentedEmitter(Writer stream, DumperOptions opts, CommentsProvider commentsProvider) { // wytrem + // wytrem start + this.commentsProvider = commentsProvider; + // wytrem end + + // The stream should have the methods `write` and possibly `flush`. + this.stream = stream; + // Emitter is a state machine with a stack of states to handle nested + // structures. + this.states = new ArrayStack<>(100); + this.state = new ExpectStreamStart(); + // Current event and the event queue. + this.events = new ArrayBlockingQueue<>(100); + this.event = null; + // The current indentation level and the stack of previous indents. + this.indents = new ArrayStack<>(10); + this.indent = null; + // Flow level. + this.flowLevel = 0; + // Contexts. + mappingContext = false; + simpleKeyContext = false; + + // + // Characteristics of the last emitted character: + // - current position. + // - is it a whitespace? + // - is it an indention character + // (indentation space, '-', '?', or ':')? + column = 0; + whitespace = true; + indention = true; + + // Whether the document requires an explicit document indicator + openEnded = false; + + // Formatting details. + this.canonical = opts.isCanonical(); + this.prettyFlow = opts.isPrettyFlow(); + this.allowUnicode = opts.isAllowUnicode(); + this.bestIndent = 2; + if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) { + this.bestIndent = opts.getIndent(); + } + this.indicatorIndent = opts.getIndicatorIndent(); + this.bestWidth = 80; + if (opts.getWidth() > this.bestIndent * 2) { + this.bestWidth = opts.getWidth(); + } + this.bestLineBreak = opts.getLineBreak().getString().toCharArray(); + this.splitLines = opts.getSplitLines(); + + // Tag prefixes. + this.tagPrefixes = new LinkedHashMap<>(); + + // Prepared anchor and tag. + this.preparedAnchor = null; + this.preparedTag = null; + + // Scalar analysis and style. + this.analysis = null; + this.style = null; + } + + static String prepareAnchor(String anchor) { + if (anchor.length() == 0) { + throw new EmitterException("anchor must not be empty"); + } + if (!ANCHOR_FORMAT.matcher(anchor).matches()) { + throw new EmitterException("invalid character in the anchor: " + anchor); + } + return anchor; + } + + public void emit(Event event) throws IOException { + this.events.add(event); + + while (!needMoreEvents()) { + this.event = this.events.poll(); + // wytrem start + if (this.event instanceof SequenceStartEvent) { + this.isInSequence = true; + } + + if (this.event instanceof SequenceEndEvent) { + this.isInSequence = false; + } + + if (this.event instanceof MappingStartEvent) { + if (!this.isInSequence) { + if (lastSimpleKey != null) { + this.paths.push(lastSimpleKey); + } + } + } + + if (this.event instanceof MappingEndEvent) { + if (!this.isInSequence) { + if (!this.paths.isEmpty()) { + this.paths.pop(); + } + } + } + // wytrem end + this.state.expect(); + this.event = null; + } + } + + // States + + // Stream handlers. + + private boolean needMoreEvents() { + if (events.isEmpty()) { + return true; + } + Event event = events.peek(); + if (event instanceof DocumentStartEvent) { + return needEvents(1); + } else if (event instanceof SequenceStartEvent) { + return needEvents(2); + } else if (event instanceof MappingStartEvent) { + return needEvents(3); + } else { + return false; + } + } + + private boolean needEvents(int count) { + int level = 0; + Iterator iter = events.iterator(); + iter.next(); + while (iter.hasNext()) { + Event event = iter.next(); + if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) { + level++; + } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) { + level--; + } else if (event instanceof StreamEndEvent) { + level = -1; + } + if (level < 0) { + return false; + } + } + return events.size() < count + 1; + } + + // Document handlers. + + private void increaseIndent(boolean flow, boolean indentless) { + indents.push(indent); + if (indent == null) { + if (flow) { + indent = bestIndent; + } else { + indent = 0; + } + } else if (!indentless) { + this.indent += bestIndent; + } + } + + private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException { + rootContext = root; + mappingContext = mapping; + simpleKeyContext = simpleKey; + if (event instanceof AliasEvent) { + expectAlias(); + } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) { + processAnchor("&"); + processTag(); + if (event instanceof ScalarEvent) { + expectScalar(); + } else if (event instanceof SequenceStartEvent) { + if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow() + || checkEmptySequence()) { + expectFlowSequence(); + } else { + expectBlockSequence(); + } + } else {// MappingStartEvent + if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow() + || checkEmptyMapping()) { + expectFlowMapping(); + } else { + expectBlockMapping(); + } + } + } else { + throw new EmitterException("expected NodeEvent, but got " + event); + } + } + + private void expectAlias() throws IOException { + if (((NodeEvent) event).getAnchor() == null) { + throw new EmitterException("anchor is not specified for alias"); + } + processAnchor("*"); + state = states.pop(); + } + + private void expectScalar() throws IOException { + // wytrem start + if (simpleKeyContext) { + String scalar = analyzeScalar(((ScalarEvent) event).getValue()).getScalar(); + String path = scalar; + + if (!this.paths.isEmpty()) { + path = String.join(".", this.paths) + "." + path; + } + + String[] comments = this.commentsProvider.apply(path); + if (comments != null && comments.length > 0) { + writeLineBreak(null); + for (String line : comments) { + if (line.contains("\n")) { + throw new IllegalArgumentException("Unexpected \\n in comment line."); + } + writeIndent(); + stream.write("# " + line); + writeLineBreak(null); + } + writeIndent(); + } + + lastSimpleKey = scalar; + } + // wytrem end + + increaseIndent(true, false); + processScalar(); + indent = indents.pop(); + state = states.pop(); + } + + // Node handlers. + + private void expectFlowSequence() throws IOException { + writeIndicator("[", true, true, false); + flowLevel++; + increaseIndent(true, false); + if (prettyFlow) { + writeIndent(); + } + state = new ExpectFirstFlowSequenceItem(); + } + + private void expectFlowMapping() throws IOException { + writeIndicator("{", true, true, false); + flowLevel++; + increaseIndent(true, false); + if (prettyFlow) { + writeIndent(); + } + state = new ExpectFirstFlowMappingKey(); + } + + private void expectBlockSequence() { + boolean indentless = mappingContext && !indention; + increaseIndent(false, indentless); + state = new ExpectFirstBlockSequenceItem(); + } + + // Flow sequence handlers. + + // Block mapping handlers. + private void expectBlockMapping() { + increaseIndent(false, false); + state = new ExpectFirstBlockMappingKey(); + } + + private boolean checkEmptySequence() { + return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent; + } + + private boolean checkEmptyMapping() { + return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent; + } + + // Flow mapping handlers. + + private boolean checkEmptyDocument() { + if (!(event instanceof DocumentStartEvent) || events.isEmpty()) { + return false; + } + Event event = events.peek(); + if (event instanceof ScalarEvent) { + ScalarEvent e = (ScalarEvent) event; + return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e + .getValue().length() == 0; + } + return false; + } + + private boolean checkSimpleKey() { + int length = 0; + if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) { + if (preparedAnchor == null) { + preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor()); + } + length += preparedAnchor.length(); + } + String tag = null; + if (event instanceof ScalarEvent) { + tag = ((ScalarEvent) event).getTag(); + } else if (event instanceof CollectionStartEvent) { + tag = ((CollectionStartEvent) event).getTag(); + } + if (tag != null) { + if (preparedTag == null) { + preparedTag = prepareTag(tag); + } + length += preparedTag.length(); + } + if (event instanceof ScalarEvent) { + if (analysis == null) { + analysis = analyzeScalar(((ScalarEvent) event).getValue()); + } + length += analysis.getScalar().length(); + } + return length < 128 && (event instanceof AliasEvent + || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline()) + || checkEmptySequence() || checkEmptyMapping()); + } + + private void processAnchor(String indicator) throws IOException { + NodeEvent ev = (NodeEvent) event; + if (ev.getAnchor() == null) { + preparedAnchor = null; + return; + } + if (preparedAnchor == null) { + preparedAnchor = prepareAnchor(ev.getAnchor()); + } + writeIndicator(indicator + preparedAnchor, true, false, false); + preparedAnchor = null; + } + + private void processTag() throws IOException { + String tag; + if (event instanceof ScalarEvent) { + ScalarEvent ev = (ScalarEvent) event; + tag = ev.getTag(); + if (style == null) { + style = chooseScalarStyle(); + } + if ((!canonical || tag == null) && ((style == null && ev.getImplicit() + .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit() + .canOmitTagInNonPlainScalar()))) { + preparedTag = null; + return; + } + if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) { + tag = "!"; + preparedTag = null; + } + } else { + CollectionStartEvent ev = (CollectionStartEvent) event; + tag = ev.getTag(); + if ((!canonical || tag == null) && ev.getImplicit()) { + preparedTag = null; + return; + } + } + if (tag == null) { + throw new EmitterException("tag is not specified"); + } + if (preparedTag == null) { + preparedTag = prepareTag(tag); + } + writeIndicator(preparedTag, true, false, false); + preparedTag = null; + } + + private DumperOptions.ScalarStyle chooseScalarStyle() { + ScalarEvent ev = (ScalarEvent) event; + if (analysis == null) { + analysis = analyzeScalar(ev.getValue()); + } + + if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED || this.canonical) { + return DumperOptions.ScalarStyle.DOUBLE_QUOTED; + } + if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) { + if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline())) + && ((flowLevel != 0 && analysis.isAllowFlowPlain()) || (flowLevel == 0 && analysis.isAllowBlockPlain()))) { + return null; + } + } + if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) { + if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) { + return ev.getScalarStyle(); + } + } + if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) { + if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) { + return DumperOptions.ScalarStyle.SINGLE_QUOTED; + } + } + return DumperOptions.ScalarStyle.DOUBLE_QUOTED; + } + + // Block sequence handlers. + + private void processScalar() throws IOException { + ScalarEvent ev = (ScalarEvent) event; + + if (analysis == null) { + analysis = analyzeScalar(ev.getValue()); + } + if (style == null) { + style = chooseScalarStyle(); + } + boolean split = !simpleKeyContext && splitLines; + if (style == null) { + + writePlain(analysis.getScalar(), split); + } else { + switch (style) { + case DOUBLE_QUOTED: + writeDoubleQuoted(analysis.getScalar(), split); + break; + case SINGLE_QUOTED: + writeSingleQuoted(analysis.getScalar(), split); + break; + case FOLDED: + writeFolded(analysis.getScalar(), split); + break; + case LITERAL: + writeLiteral(analysis.getScalar()); + break; + default: + throw new YAMLException("Unexpected style: " + style); + } + } + analysis = null; + style = null; + } + + private String prepareVersion(Version version) { + if (version.major() != 1) { + throw new EmitterException("unsupported YAML version: " + version); + } + return version.getRepresentation(); + } + + private String prepareTagHandle(String handle) { + if (handle.length() == 0) { + throw new EmitterException("tag handle must not be empty"); + } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') { + throw new EmitterException("tag handle must start and end with '!': " + handle); + } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) { + throw new EmitterException("invalid character in the tag handle: " + handle); + } + return handle; + } + + private String prepareTagPrefix(String prefix) { + if (prefix.length() == 0) { + throw new EmitterException("tag prefix must not be empty"); + } + StringBuilder chunks = new StringBuilder(); + int start = 0; + int end = 0; + if (prefix.charAt(0) == '!') { + end = 1; + } + while (end < prefix.length()) { + end++; + } + chunks.append(prefix, start, end); + return chunks.toString(); + } + + private String prepareTag(String tag) { + if (tag.length() == 0) { + throw new EmitterException("tag must not be empty"); + } + if ("!".equals(tag)) { + return tag; + } + String handle = null; + String suffix = tag; + // shall the tag prefixes be sorted as in PyYAML? + for (String prefix : tagPrefixes.keySet()) { + if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) { + handle = prefix; + } + } + if (handle != null) { + suffix = tag.substring(handle.length()); + handle = tagPrefixes.get(handle); + } + + int end = suffix.length(); + String suffixText = end > 0 ? suffix.substring(0, end) : ""; + + if (handle != null) { + return handle + suffixText; + } + return "!<" + suffixText + ">"; + } + + private ScalarAnalysis analyzeScalar(String scalar) { + // Empty scalar is a special case. + if (scalar.length() == 0) { + return new ScalarAnalysis(scalar, true, false, false, true, true, false); + } + // Indicators and special characters. + boolean blockIndicators = false; + boolean flowIndicators = false; + boolean lineBreaks = false; + boolean specialCharacters = false; + + // Important whitespace combinations. + boolean leadingSpace = false; + boolean leadingBreak = false; + boolean trailingSpace = false; + boolean trailingBreak = false; + boolean breakSpace = false; + boolean spaceBreak = false; + + // Check document indicators. + if (scalar.startsWith("---") || scalar.startsWith("...")) { + blockIndicators = true; + flowIndicators = true; + } + // First character or preceded by a whitespace. + boolean preceededByWhitespace = true; + boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1)); + // The previous character is a space. + boolean previousSpace = false; + + // The previous character is a break. + boolean previousBreak = false; + + int index = 0; + + while (index < scalar.length()) { + int c = scalar.codePointAt(index); + // Check for indicators. + if (index == 0) { + // Leading indicators are special characters. + if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) { + flowIndicators = true; + blockIndicators = true; + } + if (c == '?' || c == ':') { + flowIndicators = true; + if (followedByWhitespace) { + blockIndicators = true; + } + } + if (c == '-' && followedByWhitespace) { + flowIndicators = true; + blockIndicators = true; + } + } else { + // Some indicators cannot appear within a scalar as well. + if (",?[]{}".indexOf(c) != -1) { + flowIndicators = true; + } + if (c == ':') { + flowIndicators = true; + if (followedByWhitespace) { + blockIndicators = true; + } + } + if (c == '#' && preceededByWhitespace) { + flowIndicators = true; + blockIndicators = true; + } + } + // Check for line breaks, special, and unicode characters. + boolean isLineBreak = Constant.LINEBR.has(c); + if (isLineBreak) { + lineBreaks = true; + } + if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) { + if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) + || (c >= 0xE000 && c <= 0xFFFD) + || (c >= 0x10000 && c <= 0x10FFFF)) { + // unicode is used + if (!this.allowUnicode) { + specialCharacters = true; + } + } else { + specialCharacters = true; + } + } + // Detect important whitespace combinations. + if (c == ' ') { + if (index == 0) { + leadingSpace = true; + } + if (index == scalar.length() - 1) { + trailingSpace = true; + } + if (previousBreak) { + breakSpace = true; + } + previousSpace = true; + previousBreak = false; + } else if (isLineBreak) { + if (index == 0) { + leadingBreak = true; + } + if (index == scalar.length() - 1) { + trailingBreak = true; + } + if (previousSpace) { + spaceBreak = true; + } + previousSpace = false; + previousBreak = true; + } else { + previousSpace = false; + previousBreak = false; + } + + // Prepare for the next character. + index += Character.charCount(c); + preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak; + followedByWhitespace = true; + if (index + 1 < scalar.length()) { + int nextIndex = index + Character.charCount(scalar.codePointAt(index)); + if (nextIndex < scalar.length()) { + followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak; + } + } + } + // Let's decide what styles are allowed. + boolean allowFlowPlain = true; + boolean allowBlockPlain = true; + boolean allowSingleQuoted = true; + boolean allowBlock = true; + // Leading and trailing whitespaces are bad for plain scalars. + if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) { + allowFlowPlain = allowBlockPlain = false; + } + // We do not permit trailing spaces for block scalars. + if (trailingSpace) { + allowBlock = false; + } + // Spaces at the beginning of a new line are only acceptable for block + // scalars. + if (breakSpace) { + allowFlowPlain = allowBlockPlain = allowSingleQuoted = false; + } + // Spaces followed by breaks, as well as special character are only + // allowed for double quoted scalars. + if (spaceBreak || specialCharacters) { + allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false; + } + // Although the plain scalar writer supports breaks, we never emit + // multiline plain scalars in the flow context. + if (lineBreaks) { + allowFlowPlain = false; + } + // Flow indicators are forbidden for flow plain scalars. + if (flowIndicators) { + allowFlowPlain = false; + } + // Block indicators are forbidden for block plain scalars. + if (blockIndicators) { + allowBlockPlain = false; + } + + return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain, + allowSingleQuoted, allowBlock); + } + + void flushStream() throws IOException { + stream.flush(); + } + + void writeStreamStart() { + // BOM is written by Writer. + } + + // Checkers. + + void writeStreamEnd() throws IOException { + flushStream(); + } + + void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace, + boolean indentation) throws IOException { + if (!this.whitespace && needWhitespace) { + this.column++; + stream.write(SPACE); + } + this.whitespace = whitespace; + this.indention = this.indention && indentation; + this.column += indicator.length(); + openEnded = false; + stream.write(indicator); + } + + void writeIndent() throws IOException { + int indent; + if (this.indent != null) { + indent = this.indent; + } else { + indent = 0; + } + + if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) { + writeLineBreak(null); + } + + writeWhitespace(indent - this.column); + } + + private void writeWhitespace(int length) throws IOException { + if (length <= 0) { + return; + } + this.whitespace = true; + char[] data = new char[length]; + Arrays.fill(data, ' '); + this.column += length; + stream.write(data); + } + + // Anchor, Tag, and Scalar processors. + + private void writeLineBreak(String data) throws IOException { + this.whitespace = true; + this.indention = true; + this.column = 0; + if (data == null) { + stream.write(this.bestLineBreak); + } else { + stream.write(data); + } + } + + void writeVersionDirective(String versionText) throws IOException { + stream.write("%YAML "); + stream.write(versionText); + writeLineBreak(null); + } + + void writeTagDirective(String handleText, String prefixText) throws IOException { + // XXX: not sure 4 invocations better than StringBuilders created by str + // + str + stream.write("%TAG "); + stream.write(handleText); + stream.write(SPACE); + stream.write(prefixText); + writeLineBreak(null); + } + + // Scalar streams. + private void writeSingleQuoted(String text, boolean split) throws IOException { + writeIndicator("'", true, false, false); + boolean spaces = false; + boolean breaks = false; + int start = 0, end = 0; + char ch; + while (end <= text.length()) { + ch = 0; + if (end < text.length()) { + ch = text.charAt(end); + } + if (spaces) { + if (ch != ' ') { + if (start + 1 == end && this.column > this.bestWidth && split && start != 0 + && end != text.length()) { + writeIndent(); + } else { + int len = end - start; + this.column += len; + stream.write(text, start, len); + } + start = end; + } + } else if (breaks) { + if (ch == 0 || Constant.LINEBR.hasNo(ch)) { + nextLine(text, start, end); + start = end; + } + } else { + if (Constant.LINEBR.has(ch, "\0 '")) { + if (start < end) { + int len = end - start; + this.column += len; + stream.write(text, start, len); + start = end; + } + } + } + if (ch == '\'') { + this.column += 2; + stream.write("''"); + start = end + 1; + } + if (ch != 0) { + spaces = ch == ' '; + breaks = Constant.LINEBR.has(ch); + } + end++; + } + writeIndicator("'", false, false, false); + } + + private void nextLine(String text, int start, int end) throws IOException { + if (text.charAt(start) == '\n') { + writeLineBreak(null); + } + String data = text.substring(start, end); + for (char br : data.toCharArray()) { + if (br == '\n') { + writeLineBreak(null); + } else { + writeLineBreak(String.valueOf(br)); + } + } + writeIndent(); + } + + // Analyzers. + + private void writeDoubleQuoted(String text, boolean split) throws IOException { + writeIndicator("\"", true, false, false); + int start = 0; + int end = 0; + while (end <= text.length()) { + Character ch = null; + if (end < text.length()) { + ch = text.charAt(end); + } + if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1 + || !('\u0020' <= ch && ch <= '\u007E')) { + if (start < end) { + int len = end - start; + this.column += len; + stream.write(text, start, len); + start = end; + } + if (ch != null) { + String data; + if (ESCAPE_REPLACEMENTS.containsKey(ch)) { + data = "\\" + ESCAPE_REPLACEMENTS.get(ch); + } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) { + // if !allowUnicode or the character is not printable, + // we must encode it + if (ch <= '\u00FF') { + String s = "0" + Integer.toString(ch, 16); + data = "\\x" + s.substring(s.length() - 2); + } else if (ch >= '\uD800' && ch <= '\uDBFF') { + if (end + 1 < text.length()) { + char ch2 = text.charAt(++end); + String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2)); + data = "\\U" + s.substring(s.length() - 8); + } else { + String s = "000" + Integer.toString(ch, 16); + data = "\\u" + s.substring(s.length() - 4); + } + } else { + String s = "000" + Integer.toString(ch, 16); + data = "\\u" + s.substring(s.length() - 4); + } + } else { + data = String.valueOf(ch); + } + this.column += data.length(); + stream.write(data); + start = end + 1; + } + } + if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end) + && (this.column + (end - start)) > this.bestWidth && split) { + String data; + if (start >= end) { + data = "\\"; + } else { + data = text.substring(start, end) + "\\"; + } + if (start < end) { + start = end; + } + this.column += data.length(); + stream.write(data); + writeIndent(); + this.whitespace = false; + this.indention = false; + if (text.charAt(start) == ' ') { + data = "\\"; + this.column += data.length(); + stream.write(data); + } + } + end += 1; + } + writeIndicator("\"", false, false, false); + } + + private String determineBlockHints(String text) { + StringBuilder hints = new StringBuilder(); + if (Constant.LINEBR.has(text.charAt(0), " ")) { + hints.append(bestIndent); + } + char ch1 = text.charAt(text.length() - 1); + if (Constant.LINEBR.hasNo(ch1)) { + hints.append("-"); + } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) { + hints.append("+"); + } + return hints.toString(); + } + + void writeFolded(String text, boolean split) throws IOException { + String hints = determineBlockHints(text); + writeIndicator(">" + hints, true, false, false); + if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { + openEnded = true; + } + writeLineBreak(null); + boolean leadingSpace = true; + boolean spaces = false; + boolean breaks = true; + int start = 0, end = 0; + while (end <= text.length()) { + char ch = 0; + if (end < text.length()) { + ch = text.charAt(end); + } + if (breaks) { + if (ch == 0 || Constant.LINEBR.hasNo(ch)) { + if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') { + writeLineBreak(null); + } + leadingSpace = ch == ' '; + String data = text.substring(start, end); + for (char br : data.toCharArray()) { + if (br == '\n') { + writeLineBreak(null); + } else { + writeLineBreak(String.valueOf(br)); + } + } + if (ch != 0) { + writeIndent(); + } + start = end; + } + } else if (spaces) { + if (ch != ' ') { + if (start + 1 == end && this.column > this.bestWidth && split) { + writeIndent(); + } else { + int len = end - start; + this.column += len; + stream.write(text, start, len); + } + start = end; + } + } else { + if (Constant.LINEBR.has(ch, "\0 ")) { + int len = end - start; + this.column += len; + stream.write(text, start, len); + if (ch == 0) { + writeLineBreak(null); + } + start = end; + } + } + if (ch != 0) { + breaks = Constant.LINEBR.has(ch); + spaces = ch == ' '; + } + end++; + } + } + + void writeLiteral(String text) throws IOException { + String hints = determineBlockHints(text); + writeIndicator("|" + hints, true, false, false); + if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { + openEnded = true; + } + writeLineBreak(null); + boolean breaks = true; + int start = 0, end = 0; + while (end <= text.length()) { + char ch = 0; + if (end < text.length()) { + ch = text.charAt(end); + } + if (breaks) { + if (ch == 0 || Constant.LINEBR.hasNo(ch)) { + String data = text.substring(start, end); + for (char br : data.toCharArray()) { + if (br == '\n') { + writeLineBreak(null); + } else { + writeLineBreak(String.valueOf(br)); + } + } + if (ch != 0) { + writeIndent(); + } + start = end; + } + } else { + if (ch == 0 || Constant.LINEBR.has(ch)) { + stream.write(text, start, end - start); + if (ch == 0) { + writeLineBreak(null); + } + start = end; + } + } + if (ch != 0) { + breaks = Constant.LINEBR.has(ch); + } + end++; + } + } + + void writePlain(String text, boolean split) throws IOException { + if (rootContext) { + openEnded = true; + } + if (text.length() == 0) { + return; + } + if (!this.whitespace) { + this.column++; + stream.write(SPACE); + } + this.whitespace = false; + this.indention = false; + boolean spaces = false; + boolean breaks = false; + int start = 0, end = 0; + while (end <= text.length()) { + char ch = 0; + if (end < text.length()) { + ch = text.charAt(end); + } + if (spaces) { + if (ch != ' ') { + if (start + 1 == end && this.column > this.bestWidth && split) { + writeIndent(); + this.whitespace = false; + this.indention = false; + } else { + int len = end - start; + this.column += len; + stream.write(text, start, len); + } + start = end; + } + } else if (breaks) { + if (Constant.LINEBR.hasNo(ch)) { + nextLine(text, start, end); + this.whitespace = false; + this.indention = false; + start = end; + } + } else { + if (Constant.LINEBR.has(ch, "\0 ")) { + int len = end - start; + this.column += len; + stream.write(text, start, len); + start = end; + } + } + if (ch != 0) { + spaces = ch == ' '; + breaks = Constant.LINEBR.has(ch); + } + end++; + } + } + + /** + * + */ + public interface EmitterState { + void expect() throws IOException; + } + + private class ExpectStreamStart implements EmitterState { + public void expect() { + if (event instanceof StreamStartEvent) { + writeStreamStart(); + state = new ExpectFirstDocumentStart(); + } else { + throw new EmitterException("expected StreamStartEvent, but got " + event); + } + } + } + + private class ExpectNothing implements EmitterState { + public void expect() { + throw new EmitterException("expecting nothing, but got " + event); + } + } + + // Writers. + + private class ExpectFirstDocumentStart implements EmitterState { + public void expect() throws IOException { + new ExpectDocumentStart(true).expect(); + } + } + + private class ExpectDocumentStart implements EmitterState { + private final boolean first; + + public ExpectDocumentStart(boolean first) { + this.first = first; + } + + public void expect() throws IOException { + if (event instanceof DocumentStartEvent) { + DocumentStartEvent ev = (DocumentStartEvent) event; + if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) { + writeIndicator("...", true, false, false); + writeIndent(); + } + if (ev.getVersion() != null) { + String versionText = prepareVersion(ev.getVersion()); + writeVersionDirective(versionText); + } + tagPrefixes = new LinkedHashMap<>(DEFAULT_TAG_PREFIXES); + if (ev.getTags() != null) { + Set handles = new TreeSet<>(ev.getTags().keySet()); + for (String handle : handles) { + String prefix = ev.getTags().get(handle); + tagPrefixes.put(prefix, handle); + String handleText = prepareTagHandle(handle); + String prefixText = prepareTagPrefix(prefix); + writeTagDirective(handleText, prefixText); + } + } + boolean implicit = first && !ev.getExplicit() && !canonical + && ev.getVersion() == null + && (ev.getTags() == null || ev.getTags().isEmpty()) + && !checkEmptyDocument(); + if (!implicit) { + writeIndent(); + writeIndicator("---", true, false, false); + if (canonical) { + writeIndent(); + } + } + state = new ExpectDocumentRoot(); + } else if (event instanceof StreamEndEvent) { + // fix 313 PyYAML changeset + // if (openEnded) { + // writeIndicator("...", true, false, false); + // writeIndent(); + // } + writeStreamEnd(); + state = new ExpectNothing(); + } else { + throw new EmitterException("expected DocumentStartEvent, but got " + event); + } + } + } + + private class ExpectDocumentEnd implements EmitterState { + public void expect() throws IOException { + if (event instanceof DocumentEndEvent) { + writeIndent(); + if (((DocumentEndEvent) event).getExplicit()) { + writeIndicator("...", true, false, false); + writeIndent(); + } + flushStream(); + state = new ExpectDocumentStart(false); + } else { + throw new EmitterException("expected DocumentEndEvent, but got " + event); + } + } + } + + private class ExpectDocumentRoot implements EmitterState { + public void expect() throws IOException { + states.push(new ExpectDocumentEnd()); + expectNode(true, false, false); + } + } + + private class ExpectFirstFlowSequenceItem implements EmitterState { + public void expect() throws IOException { + if (event instanceof SequenceEndEvent) { + indent = indents.pop(); + flowLevel--; + writeIndicator("]", false, false, false); + state = states.pop(); + } else { + if (canonical || (column > bestWidth && splitLines) || prettyFlow) { + writeIndent(); + } + states.push(new ExpectFlowSequenceItem()); + expectNode(false, false, false); + } + } + } + + private class ExpectFlowSequenceItem implements EmitterState { + public void expect() throws IOException { + if (event instanceof SequenceEndEvent) { + indent = indents.pop(); + flowLevel--; + if (canonical) { + writeIndicator(",", false, false, false); + writeIndent(); + } + writeIndicator("]", false, false, false); + if (prettyFlow) { + writeIndent(); + } + state = states.pop(); + } else { + writeIndicator(",", false, false, false); + if (canonical || (column > bestWidth && splitLines) || prettyFlow) { + writeIndent(); + } + states.push(new ExpectFlowSequenceItem()); + expectNode(false, false, false); + } + } + } + + private class ExpectFirstFlowMappingKey implements EmitterState { + public void expect() throws IOException { + if (event instanceof MappingEndEvent) { + indent = indents.pop(); + flowLevel--; + writeIndicator("}", false, false, false); + state = states.pop(); + } else { + if (canonical || (column > bestWidth && splitLines) || prettyFlow) { + writeIndent(); + } + if (!canonical && checkSimpleKey()) { + states.push(new ExpectFlowMappingSimpleValue()); + expectNode(false, true, true); + } else { + writeIndicator("?", true, false, false); + states.push(new ExpectFlowMappingValue()); + expectNode(false, true, false); + } + } + } + } + + private class ExpectFlowMappingKey implements EmitterState { + public void expect() throws IOException { + if (event instanceof MappingEndEvent) { + indent = indents.pop(); + flowLevel--; + if (canonical) { + writeIndicator(",", false, false, false); + writeIndent(); + } + if (prettyFlow) { + writeIndent(); + } + writeIndicator("}", false, false, false); + state = states.pop(); + } else { + writeIndicator(",", false, false, false); + if (canonical || (column > bestWidth && splitLines) || prettyFlow) { + writeIndent(); + } + if (!canonical && checkSimpleKey()) { + states.push(new ExpectFlowMappingSimpleValue()); + expectNode(false, true, true); + } else { + writeIndicator("?", true, false, false); + states.push(new ExpectFlowMappingValue()); + expectNode(false, true, false); + } + } + } + } + + private class ExpectFlowMappingSimpleValue implements EmitterState { + public void expect() throws IOException { + writeIndicator(":", false, false, false); + states.push(new ExpectFlowMappingKey()); + expectNode(false, true, false); + } + } + + private class ExpectFlowMappingValue implements EmitterState { + public void expect() throws IOException { + if (canonical || (column > bestWidth) || prettyFlow) { + writeIndent(); + } + writeIndicator(":", true, false, false); + states.push(new ExpectFlowMappingKey()); + expectNode(false, true, false); + } + } + + private class ExpectFirstBlockSequenceItem implements EmitterState { + public void expect() throws IOException { + new ExpectBlockSequenceItem(true).expect(); + } + } + + private class ExpectBlockSequenceItem implements EmitterState { + private final boolean first; + + public ExpectBlockSequenceItem(boolean first) { + this.first = first; + } + + public void expect() throws IOException { + if (!this.first && event instanceof SequenceEndEvent) { + indent = indents.pop(); + state = states.pop(); + } else { + writeIndent(); + writeWhitespace(indicatorIndent); + writeIndicator("-", true, false, true); + states.push(new ExpectBlockSequenceItem(false)); + expectNode(false, false, false); + } + } + } + + private class ExpectFirstBlockMappingKey implements EmitterState { + public void expect() throws IOException { + new ExpectBlockMappingKey(true).expect(); + } + } + + private class ExpectBlockMappingKey implements EmitterState { + private final boolean first; + + public ExpectBlockMappingKey(boolean first) { + this.first = first; + } + + public void expect() throws IOException { + if (!this.first && event instanceof MappingEndEvent) { + indent = indents.pop(); + state = states.pop(); + } else { + writeIndent(); + if (checkSimpleKey()) { + states.push(new ExpectBlockMappingSimpleValue()); + expectNode(false, true, true); + } else { + writeIndicator("?", true, false, true); + states.push(new ExpectBlockMappingValue()); + expectNode(false, true, false); + } + } + } + } + + private class ExpectBlockMappingSimpleValue implements EmitterState { + public void expect() throws IOException { + writeIndicator(":", false, false, false); + states.push(new ExpectBlockMappingKey(false)); + expectNode(false, true, false); + } + } + + private class ExpectBlockMappingValue implements EmitterState { + public void expect() throws IOException { + writeIndent(); + writeIndicator(":", true, false, true); + states.push(new ExpectBlockMappingKey(false)); + expectNode(false, true, false); + } + } +} \ No newline at end of file diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYaml.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYaml.java new file mode 100644 index 0000000..7df9a8f --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYaml.java @@ -0,0 +1,73 @@ +package cc.carm.lib.configuration.commented; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.BaseConstructor; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Representer; +import org.yaml.snakeyaml.serializer.Serializer; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An hacky extension of {@link Yaml} that allows to write comments when dumping. + */ +public class CommentedYaml extends Yaml { + + private final CommentsProvider commentsProvider; + + public CommentedYaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions, CommentsProvider commentsProvider) { + super(constructor, representer, dumperOptions); + this.commentsProvider = commentsProvider; + } + + @Override + public String dump(Object data) { + List list = new ArrayList<>(1); + list.add(data); + return this.dumpAll(list.iterator()); + } + + @Override + public void dump(Object data, Writer output) { + List list = new ArrayList<>(1); + list.add(data); + this.dumpAll(list.iterator(), output, null); + } + + @Override + public String dumpAll(Iterator data) { + StringWriter buffer = new StringWriter(); + this.dumpAll(data, buffer, null); + return buffer.toString(); + } + + @Override + public void dumpAll(Iterator data, Writer output) { + this.dumpAll(data, output, null); + } + + private void dumpAll(Iterator data, Writer output, Tag rootTag) { + Serializer serializer = new Serializer(new CommentedEmitter(output, this.dumperOptions, this.commentsProvider), this.resolver, this.dumperOptions, rootTag); + + try { + serializer.open(); + + while (data.hasNext()) { + Node node = this.representer.represent(data.next()); + serializer.serialize(node); + } + + serializer.close(); + } catch (IOException var6) { + throw new YAMLException(var6); + } + } +} \ No newline at end of file diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYamlConfiguration.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYamlConfiguration.java new file mode 100644 index 0000000..d356200 --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentedYamlConfiguration.java @@ -0,0 +1,69 @@ +package cc.carm.lib.configuration.commented; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.file.YamlConstructor; +import org.bukkit.configuration.file.YamlRepresenter; +import org.jetbrains.annotations.NotNull; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; + +/** + * A yaml file with comments on certain properties, as returned by the given {@link CommentsProvider}. + * Unlike {@link YamlConfiguration}, this class does not provide a header support. + */ +public class CommentedYamlConfiguration extends YamlConfiguration { + + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new YamlRepresenter(); + private final CommentedYaml yaml; + + public CommentedYamlConfiguration(CommentsProvider commentsProvider) { + this.yaml = new CommentedYaml(new YamlConstructor(), this.yamlRepresenter, this.yamlOptions, commentsProvider); + } + + public static CommentedYamlConfiguration loadConfiguration(CommentsProvider commentsProvider, File file) { + CommentedYamlConfiguration config = new CommentedYamlConfiguration(commentsProvider); + + try { + config.load(file); + } catch (FileNotFoundException ignored) { + } catch (IOException | InvalidConfigurationException var4) { + var4.printStackTrace(); + } + + return config; + } + + public static CommentedYamlConfiguration loadConfiguration(CommentsProvider commentsProvider, Reader reader) { + CommentedYamlConfiguration config = new CommentedYamlConfiguration(commentsProvider); + + try { + config.load(reader); + } catch (IOException | InvalidConfigurationException ex) { + ex.printStackTrace(); + } + + return config; + } + + @Override + public @NotNull String saveToString() { + this.yamlOptions.setIndent(this.options().indent()); + this.yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + this.yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + String dump = this.yaml.dump(this.getValues(false)); + if (dump.equals("{}\n")) { + dump = ""; + } + + // No header support. + + return dump; + } +} \ No newline at end of file diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentsProvider.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentsProvider.java new file mode 100644 index 0000000..c1c17b1 --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/commented/CommentsProvider.java @@ -0,0 +1,7 @@ +package cc.carm.lib.configuration.commented; + +import java.util.function.Function; + + +public interface CommentsProvider extends Function { +} \ No newline at end of file diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/ConfigComments.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/ConfigComments.java new file mode 100644 index 0000000..42d6516 --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/ConfigComments.java @@ -0,0 +1,35 @@ +package cc.carm.lib.configuration.spigot; + +import cc.carm.lib.configuration.commented.CommentsProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class ConfigComments implements CommentsProvider { + + Map comments = new HashMap<>(); + + protected Map getComments() { + return comments; + } + + public void set(@NotNull String path, @NotNull String... comments) { + if (comments.length == 0) { + getComments().remove(path); + } else { + getComments().put(path, comments); + } + } + + public @Nullable String[] get(@NotNull String path) { + return getComments().get(path); + } + + @Override + public String[] apply(String s) { + return get(s); + } + +} diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotConfigProvider.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotConfigProvider.java new file mode 100644 index 0000000..fbf9cbd --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotConfigProvider.java @@ -0,0 +1,49 @@ +package cc.carm.lib.configuration.spigot; + +import cc.carm.lib.configuration.commented.CommentedYamlConfiguration; +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + +public class SpigotConfigProvider extends FileConfigProvider { + + ConfigComments comments = new ConfigComments(); + CommentedYamlConfiguration configuration; + + public SpigotConfigProvider(@NotNull File file) { + super(file); + } + + public void initialize() { + this.configuration = CommentedYamlConfiguration.loadConfiguration(comments, file); + } + + @Override + public @NotNull ConfigurationWrapper getConfiguration() { + return SpigotSectionWrapper.of(configuration); + } + + @Override + public void reload() throws Exception { + configuration.load(getFile()); + } + + @Override + public void save() throws Exception { + configuration.save(getFile()); + } + + @Override + public void setComments(@NotNull String path, @NotNull String... comments) { + this.comments.set(path, comments); + } + + @Override + public @Nullable String[] getComments(@NotNull String path) { + return this.comments.get(path); + } + +} diff --git a/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotSectionWrapper.java b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotSectionWrapper.java new file mode 100644 index 0000000..29e65f9 --- /dev/null +++ b/platform/spigot/src/main/java/cc/carm/lib/configuration/spigot/SpigotSectionWrapper.java @@ -0,0 +1,71 @@ +package cc.carm.lib.configuration.spigot; + +import cc.carm.lib.configuration.core.source.ConfigurationWrapper; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class SpigotSectionWrapper implements ConfigurationWrapper { + + private final ConfigurationSection section; + + private SpigotSectionWrapper(ConfigurationSection section) { + this.section = section; + } + + @Contract("!null->!null") + public static @Nullable SpigotSectionWrapper of(@Nullable ConfigurationSection section) { + return section == null ? null : new SpigotSectionWrapper(section); + } + + @Override + public @NotNull Set getKeys(boolean deep) { + return new LinkedHashSet<>(section.getKeys(deep)); + } + + @Override + public @NotNull Map getValues(boolean deep) { + return section.getValues(deep); + } + + @Override + public void set(@NotNull String path, @Nullable Object value) { + this.section.set(path, value); + } + + @Override + public boolean contains(@NotNull String path) { + return this.section.contains(path); + } + + @Override + public @Nullable Object get(@NotNull String path) { + return this.section.get(path); + } + + @Override + public boolean isList(@NotNull String path) { + return this.section.isList(path); + } + + @Override + public @Nullable List getList(@NotNull String path) { + return this.section.getList(path); + } + + @Override + public boolean isConfigurationSection(@NotNull String path) { + return this.section.isConfigurationSection(path); + } + + @Override + public @Nullable ConfigurationWrapper getConfigurationSection(@NotNull String path) { + return of(this.section.getConfigurationSection(path)); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a45860b --- /dev/null +++ b/pom.xml @@ -0,0 +1,302 @@ + + + 4.0.0 + + + 8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + cc.carm.lib + easyconfiguration-parent + pom + 1.0.0 + + core + + impl/yaml + + platform/spigot + platform/bungee + + + EasyConfiguration + 轻松(做)配置,简单便捷的通用配置文件加载、读取与更新工具,可自定义配置格式。 + https://github.com/CarmJos/EasyConfiguration + + + + CarmJos + Carm Jos + carm@carm.cc + https://www.carm.cc + Asia/Shanghai + + + + + scm:git:git@github.com:CarmJos/Easy.EasyConfiguration + scm:git:git@github.com:CarmJos/EasyConfiguration.git + https://github.com/CarmJos/EasyConfiguration + HEAD + + + + + The MIT License + https://opensource.org/licenses/MIT + + + + + GitHub Issues + https://github.com/CarmJos/EasyConfiguration/issues + + + + GitHub Actions + https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml + + + + + + carm-repo + Carm's Repo + https://repo.carm.cc/repository/maven-public/ + + + + nexus + https://mvn.lumine.io/repository/maven-public/ + + + + central + https://repo1.maven.org/maven2/ + + + + github + GitHub Packages + https://maven.pkg.github.com/CarmJos/EasyConfiguration + + + + + + https://github.com/CarmJos/EasyConfiguration/releases + + javadoc + EasySQL JavaDoc (on Github Pages) + https://CarmJos.github.io/EasyConfiguration + + + + + + + junit + junit + 4.13.2 + test + + + + org.jetbrains + annotations + 23.0.0 + provided + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + true + false + release + deploy + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.2 + + javadoc + false + UTF-8 + UTF-8 + UTF-8 + zh_CN + + true + + cc.carm.lib:* + + + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${java.version} + ${java.version} + UTF-8 + -parameters + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + package + + shade + + + false + + + + + + + *:* + + META-INF/MANIFEST.MF + META-INF/*.txt + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + false + + + + + + + + + src/main/resources + true + + + + + + + + + ossrh + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + github + + + github + GitHub Packages + https://maven.pkg.github.com/CarmJos/EasyConfiguration + + + + + + + + \ No newline at end of file