1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-05 02:58:20 +08:00

Compare commits

..

55 Commits

Author SHA1 Message Date
carm dbec551c84 chore: Fixed the param errors. 2025-03-17 01:39:08 +08:00
carm 82cca5eca2 feat(exception): Supported ConfigExceptionHandler for holders. 2025-03-17 01:06:57 +08:00
carm 65f3cc1b3d feat(validator): Support validators for config values.
BREAKING CHANGE: `ValueManifest` and `ConfigValue` added a new type "UNIT" to mark the minimal unit value of this instance.

link #132
2025-03-17 00:26:47 +08:00
carm fae048dd69 feat(validator): Support validators for config values.
BREAKING CHANGE: `ValueManifest` and `ConfigValue` added a new type "UNIT" to mark the minimal unit value of this instance.

link #132
2025-03-16 23:56:36 +08:00
carm 854e3df49f feat: Upgrade configured to v4.1.0 2025-03-15 01:52:52 +08:00
carm f6167c3b5e docs: Update license [skip ci] 2025-03-15 01:51:07 +08:00
carm ffe3e88b3b docs: Translated to English 2025-03-14 19:34:06 +08:00
carm caa3077f48 chore: Add missing @Nullable 2025-03-14 19:33:30 +08:00
carm 05dbf0b504 docs: Redesign readme 2025-03-14 19:20:31 +08:00
carm 810e95198e docs: Redesign readme 2025-03-14 19:17:26 +08:00
carm 10004f16b4 docs(logo): Add project banner & logo 2025-03-14 19:09:43 +08:00
carm 8e19748c7c feat(map): Add more builder functions 2025-03-12 04:04:00 +08:00
carm 17762a2e70 refactor(proj): Change project name to "configured" 2025-03-12 03:27:35 +08:00
carm 7dbd607a3f refactor(proj): Change project name to "configured" 2025-03-12 03:22:40 +08:00
carm 035e8a227e test(parse): Add more tests 2025-03-06 20:12:33 +08:00
carm 04eaf6606d feat: Jump to 4.0.11 2025-03-06 18:19:25 +08:00
carm e558e93410 feat(sql): Support custom sql table schema 2025-03-06 18:19:06 +08:00
flowerinsnow e55fe3a8d5 feat: hocon supported 2025-03-06 18:03:27 +08:00
carm 8f075b99b5 chore(deps): use parent dependencies' version 2025-03-04 11:58:01 +08:00
carm 09a0b3c373 chore(deps): use parent dependencies' version 2025-03-04 11:34:16 +08:00
carm 4df4977733 feat(msg): add enhanced text support 2025-03-04 04:21:58 +08:00
carm 6434feb980 fix(sql): Fixed sql table format 2025-03-04 01:57:04 +08:00
carm bc3e4b3e6f fix(comment): Fixed yaml comments 2025-03-04 01:24:12 +08:00
carm c2a9e2254c feat(section): Add ConfigureSection#asMap function 2025-03-04 01:06:21 +08:00
carm 251dd208af feat(sql): Support SQL sources 2025-03-03 19:00:28 +08:00
carm afd1bbfc0f feat(sql): Support SQL sources 2025-03-03 18:52:41 +08:00
carm a699f6c164 feat(sql): Support SQL sources 2025-03-03 18:51:21 +08:00
carm 844cbfab53 feat(sql): Try to implement sql source 2025-02-27 13:32:12 +08:00
carm f74d5d29f9 feat(mongo): Finished source for MongoDB #105 2025-02-27 00:46:44 +08:00
carm 6f28abebb9 feat(section): Add #path and #fullPath for sections 2025-02-25 00:04:24 +08:00
LSeng 842cd78ce3 feat(section): Implement createSection() for ShadedSection 2025-02-22 23:56:45 +08:00
carm 11f1f36a15 docs: Change logo file 2025-02-22 10:13:54 +08:00
renovate[bot] ee9f29ba30 chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 2025-02-22 09:57:51 +08:00
carm f5bccdaad5 chore: Upgraded to 4.0.6 2025-02-22 09:57:24 +08:00
carm f5f70ff69b chore(source): Add more pre implemented functions 2025-02-22 09:56:59 +08:00
carm 3e221740bc feat(section): Implement shaded sections 2025-02-22 09:55:28 +08:00
LSeng 96d09be977 feat(section): support ShadedSection 2025-02-21 20:48:37 +08:00
carm 5d7c946db5 feat(section): Implement more sections 2025-02-21 18:45:15 +08:00
carm 9e008ff4cd feat(section): Add shaded section 2025-02-21 17:31:05 +08:00
carm df19da170b feat(section): Add #contains and #containsValue methods 2025-02-21 17:15:16 +08:00
carm 3473ef2247 feat: Implement more sections functions 2025-02-21 16:17:12 +08:00
carm d543530305 feat: Implement more sections functions 2025-02-21 16:09:58 +08:00
carm d81855697c feat: Implement more sections functions 2025-02-21 11:27:03 +08:00
carm 5c16e98f30 feat(section): Implement more sections 2025-02-21 03:20:11 +08:00
carm 3f1ffadeff feat(section): Implement more sections 2025-02-21 03:07:43 +08:00
carm d8191b7c6d feat(section): Add original method 2025-02-21 02:00:36 +08:00
carm ba66220f92 docs(sponsor): Add sponsors banner 2025-02-21 01:00:27 +08:00
carm 730d6d7e9c docs(sponsor): Add sponsors banner 2025-02-21 00:58:32 +08:00
carm 4523190cb0 feat: Upgraded to 4.0.5 2025-02-20 16:50:07 +08:00
carm 2a49e2ee6b docs: Fixed quantity usage 2025-02-20 16:49:36 +08:00
carm 77b223b2cb feat: Add stream functions 2025-02-20 16:38:49 +08:00
carm 8e7ac263e7 feat: Split MapSection and MemorySection 2025-02-20 03:01:45 +08:00
carm 80f03ec501 feat: Split MapSection and MemorySection 2025-02-20 02:52:19 +08:00
carm 1c002ae535 feat: Split MapSection and MemorySection 2025-02-20 02:50:00 +08:00
carm a4659c5c9f docs: Englished documents 2025-02-20 00:11:06 +08:00
112 changed files with 3264 additions and 1471 deletions
+2 -8
View File
@@ -1,9 +1,3 @@
# EasyConfiguration Javadoc
# configured Javadoc
基于 [Github Pages](https://pages.github.com/) 搭建,请访问 [JavaDoc](https://carmjos.github.io/EasyConfiguration) 。
## 如何实现?
若您也想通过 [Github Actions](https://docs.github.com/en/actions/learn-github-actions)
自动部署项目的Javadoc到 [Github Pages](https://pages.github.com/)
可以参考我的文章 [《自动部署Javadoc到Github Pages》](https://pages.carm.cc/doc/javadoc-in-github.html) 。
Based on [Github Pages](https://pages.github.com/), please see [JavaDoc](https://carmjos.github.io/configured) 。
+1 -16
View File
@@ -1,16 +1 @@
# 欢迎使用 EasyConfiguration
这个项目刚刚创建,详细的Javadoc与开发指南还在补充,请给我一点时间~
## 基本定义
Value: 实际配置的单例值。
Manifest: 用于描述值基本配置的对象。
Provider: 用于提供配置文件的接口。
Wrapper: 用于包装配置文件的接口。
Initializer: 用于初始化的接口
# Documentation
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+2 -2
View File
@@ -47,7 +47,7 @@ jobs:
id: sitemap
uses: cicirello/generate-sitemap@v1
with:
base-url-path: https://CarmJos.github.io/EasyConfiguration
base-url-path: https://CarmJos.github.io/configured
path-to-root: docs
- name: "Output stats"
@@ -72,7 +72,7 @@ jobs:
run: |
cd docs
git init
git remote add origin git@github.com:CarmJos/EasyConfiguration.git
git remote add origin git@github.com:CarmJos/configured.git
git checkout -b gh-pages
git add -A
git commit -m "API Document generated."
+1
View File
@@ -1,6 +1,7 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
+69 -64
View File
@@ -1,26 +1,24 @@
```text
____ _____ ____ __ _
/ __/__ ____ __ __ / ___/__ ___ / _(_)__ ___ _________ _/ /_(_)__ ___
/ _// _ `(_-</ // / / /__/ _ \/ _ \/ _/ / _ `/ // / __/ _ `/ __/ / _ \/ _ \
/___/\_,_/___/\_, / \___/\___/_//_/_//_/\_, /\_,_/_/ \_,_/\__/_/\___/_//_/
/___/ /___/
```
<div align=center>
<img src=".doc/images/banner.png" alt="Banner"/>
[![version](https://img.shields.io/github/v/release/CarmJos/configured)](https://github.com/CarmJos/configured/releases)
[![License](https://img.shields.io/github/license/CarmJos/configured)](https://www.gnu.org/licenses/lgpl-3.0.html)
[![workflow](https://github.com/CarmJos/configured/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/CarmJos/configured/actions/workflows/maven.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/carmjos/configured/badge)](https://www.codefactor.io/repository/github/carmjos/configured)
![CodeSize](https://img.shields.io/github/languages/code-size/CarmJos/configured)
![](https://visitor-badge.glitch.me/badge?page_id=configured.readme)
README LANGUAGES [ [**English**](README.md) | [中文](README_CN.md) ]
</div>
# EasyConfiguration
# configured (config-framework)
[![version](https://img.shields.io/github/v/release/CarmJos/EasyConfiguration)](https://github.com/CarmJos/EasyConfiguration/releases)
[![License](https://img.shields.io/github/license/CarmJos/EasyConfiguration)](https://www.gnu.org/licenses/lgpl-3.0.html)
[![workflow](https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/carmjos/easyconfiguration/badge)](https://www.codefactor.io/repository/github/carmjos/easyconfiguration)
![CodeSize](https://img.shields.io/github/languages/code-size/CarmJos/EasyConfiguration)
![](https://visitor-badge.glitch.me/badge?page_id=EasyConfiguration.readme)
_**"Once set, Simple get."**_
**Easy _(to make)_ Configurations!**
A simple, easy-to-use and universal solution for managing, loading, reading,
and updating configuration files.
A simple, easy-to-use and universal solution for managing configuration files.
Enjoy the ease of use with customizable formats for loading, reading, and updating your configuration files.
Supported **JSON**, **YAML**, **Hocon**, **TOML**, **SQL**, **MongoDB**... and much more!
## Features & Advantages
@@ -35,13 +33,14 @@ format.
## Development
For the latest JavaDoc release, [CLICK HERE](https://CarmJos.github.io/EasyConfiguration).
For the latest JavaDoc release, [CLICK HERE](https://CarmJos.github.io/configured).
For a detailed development guide, [CLICK HERE](.doc/README.md).
### Code Samples
To quickly demonstrate the applicability of the project, here are a few practical demonstrations:
- [Database configuration.](demo/src/main/java/cc/carm/lib/configuration/demo/DatabaseConfiguration.java)
- [Demonstration of all types of configuration instance classes.](demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java)
@@ -49,35 +48,36 @@ Check out all code demonstrations [HERE](demo/src/main/java/cc/carm/lib/configur
For more examples, see the [Development Guide](.doc/README.md).
```java
@ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // Inline comment
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@InlineComment("Enabled?") // Inline comment
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
@ConfigPath("info") // Custom path
interface INFO extends Configuration {
@ConfigPath("info") // Custom path
interface INFO extends Configuration {
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
@ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
}
}
}
@@ -85,23 +85,23 @@ public interface SampleConfig extends Configuration {
```java
public class Sample {
public static void main(String[] args) {
// 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.build();
public static void main(String[] args) {
// 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. Initialize the configuration classes or instances.
holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false);
System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. Changes not save so enable value will still be true in the next run.
// 2. Initialize the configuration classes or instances.
holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve()); // true
SampleConfig.ENABLED.set(false);
System.out.println("And now? -> " + SampleConfig.ENABLED.resolve()); // false
// p.s. Changes not save so enable value will still be true in the next run.
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
}
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
}
}
```
@@ -148,9 +148,9 @@ info:
<repository>
<!-- Using GitHub dependencies for real-time updates, configuration required (recommended). -->
<id>EasyConfiguration</id>
<id>configured</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/EasyConfiguration</url>
<url>https://maven.pkg.github.com/CarmJos/configured</url>
</repository>
</repositories>
@@ -169,7 +169,7 @@ info:
<!-- Basic implementation part, requiring custom implementation of “Provider” and “Wrapper”. -->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -177,7 +177,7 @@ info:
<!-- YAML file-based implementation, compatible with all Java environments. -->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-yaml</artifactId>
<artifactId>configured-yaml</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -185,7 +185,7 @@ info:
<!-- JSON file-based implementation, compatible with all Java environments. -->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<artifactId>configured-gson</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -208,7 +208,7 @@ repositories {
mavenCentral()
// Using GitHub dependencies for real-time updates, configuration required (recommended).
maven { url 'https://maven.pkg.github.com/CarmJos/EasyConfiguration' }
maven { url 'https://maven.pkg.github.com/CarmJos/configured' }
}
```
@@ -223,13 +223,13 @@ repositories {
dependencies {
// Basic implementation part, requiring custom implementation of “Provider” and “Wrapper”.
api "cc.carm.lib:easyconfiguration-core:[LATEST RELEASE]"
api "cc.carm.lib:configured-core:[LATEST RELEASE]"
// YAML file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
api "cc.carm.lib:configured-yaml:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
api "cc.carm.lib:configured-gson:[LATEST RELEASE]"
}
```
@@ -240,10 +240,10 @@ dependencies {
### [**MineConfiguration**](https://github.com/CarmJos/MineConfiguration) (by @CarmJos)
EasyConfiguration for MineCraft!
configured for MineCraft!
Easily manage configurations on MineCraft-related server platforms.
Currently, it supports BungeeCord, Velocity, Bukkit (Spigot) servers,
Currently, it supports BungeeCord, Velocity, Bukkit (Spigot) servers,
with more platforms to be supported soon.
## Support and Donation
@@ -254,7 +254,12 @@ Thank you for supporting open-source projects!
Many thanks to Jetbrains for kindly providing a license for us to work on this and other open-source projects.
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration)
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/configured)
Many thanks to [ArtformGames](https://github.com/ArtformGames) for their
strong support and active contribution to this project!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## Open Source License
+63 -60
View File
@@ -1,29 +1,26 @@
```text
____ _____ ____ __ _
/ __/__ ____ __ __ / ___/__ ___ / _(_)__ ___ _________ _/ /_(_)__ ___
/ _// _ `(_-</ // / / /__/ _ \/ _ \/ _/ / _ `/ // / __/ _ `/ __/ / _ \/ _ \
/___/\_,_/___/\_, / \___/\___/_//_/_//_/\_, /\_,_/_/ \_,_/\__/_/\___/_//_/
/___/ /___/
```
<div align=center>
<img src=".doc/images/banner.png" alt="Banner"/>
[![version](https://img.shields.io/github/v/release/CarmJos/configured)](https://github.com/CarmJos/configured/releases)
[![License](https://img.shields.io/github/license/CarmJos/configured)](https://www.gnu.org/licenses/lgpl-3.0.html)
[![workflow](https://github.com/CarmJos/configured/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/CarmJos/configured/actions/workflows/maven.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/carmjos/configured/badge)](https://www.codefactor.io/repository/github/carmjos/configured)
![CodeSize](https://img.shields.io/github/languages/code-size/CarmJos/configured)
![](https://visitor-badge.glitch.me/badge?page_id=configured.readme)
README LANGUAGES [ [English](README.md) | [**中文**](README_CN.md) ]
# EasyConfiguration
</div>
[![version](https://img.shields.io/github/v/release/CarmJos/EasyConfiguration)](https://github.com/CarmJos/EasyConfiguration/releases)
[![License](https://img.shields.io/github/license/CarmJos/EasyConfiguration)](https://www.gnu.org/licenses/lgpl-3.0.html)
[![workflow](https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/carmjos/easyconfiguration/badge)](https://www.codefactor.io/repository/github/carmjos/easyconfiguration)
![CodeSize](https://img.shields.io/github/languages/code-size/CarmJos/EasyConfiguration)
![](https://visitor-badge.glitch.me/badge?page_id=EasyConfiguration.readme)
# configured (config-framework)
**轻松(做)配置**
**一次配置,轻松读取**
一款简单便捷的通用配置文件加载、读取与更新工具,可自定义配置的格式。
## 特性 & 优势
支持 [YAML](impl/yaml), [JSON](impl/json), [HOCON](impl/hocon) 和 [SQL](impl/sql) 等多种配置文件格式。
支持 [YAML](providers/yaml), [JSON](providers/gson), [HOCON](providers/hocon) 和 [SQL](providers/sql) 等多种配置文件格式。
- 基于类的配置文件初始化、加载、获取与更新机制,方便快捷。
- 支持复杂配置的手动序列化、反序列化。
@@ -32,47 +29,49 @@ README LANGUAGES [ [English](README.md) | [**中文**](README_CN.md) ]
## 开发
详细开发介绍请 [点击这里](.doc/README.md) , JavaDoc(最新Release) 请 [点击这里](https://CarmJos.github.io/EasyConfiguration) 。
详细开发介绍请 [点击这里](.doc/README.md) , JavaDoc(最新Release)
请 [点击这里](https://CarmJos.github.io/configured) 。
### 示例代码
为快速的展示该项目的适用性,这里有几个实际演示:
- [数据库配置文件实例](demo/src/main/java/cc/carm/lib/configuration/demo/DatabaseConfiguration.java)
- [全种类配置实例类演示](demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java)
您可以 [点击这里](demo/src/main/java/cc/carm/lib/configuration/demo) 直接查看现有的代码演示,更多复杂情况演示详见 [开发介绍](.doc/README.md) 。
您可以 [点击这里](demo/src/main/java/cc/carm/lib/configuration/demo)
直接查看现有的代码演示,更多复杂情况演示详见 [开发介绍](.doc/README.md) 。
```java
@ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行后注释
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@InlineComment("Enabled?") // 行后注释
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // 头部注释
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments("Server configurations") // 头部注释
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
interface INFO extends Configuration {
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
interface INFO extends Configuration {
@ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
}
@ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
}
}
@@ -137,12 +136,12 @@ info:
<name>Maven Central</name>
<url>https://repo1.maven.org/maven2</url>
</repository>
<repository>
<!--采用github依赖库,实时更新,但需要配置 (推荐) -->
<id>EasyConfiguration</id>
<id>configured</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/EasyConfiguration</url>
<url>https://maven.pkg.github.com/CarmJos/configured</url>
</repository>
<repository>
@@ -168,7 +167,7 @@ info:
<!--基础实现部分,需要自行实现“Provider”与“Wrapper”。-->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -176,7 +175,7 @@ info:
<!--基于YAML文件的实现版本,可用于全部Java环境。-->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-yaml</artifactId>
<artifactId>configured-yaml</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -185,25 +184,25 @@ info:
<!--需要注意的是,JSON不支持文件注释。-->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<artifactId>configured-gson</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-hocon</artifactId>
<artifactId>configured-hocon</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-sql</artifactId>
<artifactId>configured-sql</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
```
@@ -220,9 +219,9 @@ repositories {
// 采用Maven中心库,安全稳定,但版本更新需要等待同步
mavenCentral()
// 采用github依赖库,实时更新,但需要配置 (推荐)
maven { url 'https://maven.pkg.github.com/CarmJos/EasyConfiguration' }
maven { url 'https://maven.pkg.github.com/CarmJos/configured' }
// 采用我的私人依赖库,简单方便,但可能因为变故而无法使用
maven { url 'https://repo.carm.cc/repository/maven-public/' }
@@ -239,18 +238,18 @@ repositories {
dependencies {
//基础实现部分,需要自行实现“Provider”与“Wrapper”。
api "cc.carm.lib:easyconfiguration-core:[LATEST RELEASE]"
api "cc.carm.lib:configured-core:[LATEST RELEASE]"
//基于YAML文件的实现版本,可用于全部Java环境。
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
api "cc.carm.lib:configured-yaml:[LATEST RELEASE]"
//基于JSON文件的实现版本,可用于全部Java环境。
//基于JSON文件的实现版本,可用于全部Java环境。
//需要注意的是,JSON不支持文件注释。
api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]"
api "cc.carm.lib:easyconfiguration-sql:[LATEST RELEASE]"
api "cc.carm.lib:configured-gson:[LATEST RELEASE]"
api "cc.carm.lib:configured-hocon:[LATEST RELEASE]"
api "cc.carm.lib:configured-sql:[LATEST RELEASE]"
}
```
@@ -261,7 +260,7 @@ dependencies {
### [**MineConfiguration**](https://github.com/CarmJos/MineConfiguration) (by @CarmJos )
EasyConfiguration for MineCraft!
configured for MineCraft!
开始在 MineCraft 相关服务器平台上轻松(做)配置吧!
目前支持 BungeeCord, Bukkit(Spigot) 服务端,后续将支持更多平台。
@@ -274,7 +273,11 @@ EasyConfiguration for MineCraft!
万分感谢 Jetbrains 为我们提供了从事此项目和其他开源项目的许可!
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/EasyConfiguration)
[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/CarmJos/configured)
万分感谢来自 [ArtformGames](https://github.com/ArtformGames) 对本项目的大力支持与积极贡献!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## 开源协议
+3 -3
View File
@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<artifactId>configured-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.2</version>
<version>4.1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
@@ -15,7 +15,7 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<packaging>jar</packaging>
<build>
@@ -15,6 +15,7 @@ public class ValueAdapter<TYPE>
implements ValueSerializer<TYPE>, ValueParser<TYPE> {
protected final @NotNull ValueType<TYPE> type;
protected @Nullable ValueSerializer<TYPE> serializer;
protected @Nullable ValueParser<TYPE> deserializer;
@@ -53,13 +54,17 @@ public class ValueAdapter<TYPE>
}
@Override
public Object serialize(@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type, @NotNull TYPE value) throws Exception {
public @Nullable Object serialize(
@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type,
@NotNull TYPE value) throws Exception {
if (serializer == null) throw new UnsupportedOperationException("Serializer is not supported");
return serializer.serialize(holder, type, value);
}
@Override
public TYPE parse(@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type, @NotNull Object value) throws Exception {
public @Nullable TYPE parse(
@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type,
@NotNull Object value) throws Exception {
if (deserializer == null) throw new UnsupportedOperationException("Deserializer is not supported");
return deserializer.parse(holder, type, value);
}
@@ -61,7 +61,9 @@ public class ValueAdapterRegistry {
}
}
public <T> void register(@NotNull ValueType<T> type, @Nullable ValueSerializer<T> serializer, @Nullable ValueParser<T> deserializer) {
public <T> void register(@NotNull ValueType<T> type,
@Nullable ValueSerializer<T> serializer,
@Nullable ValueParser<T> deserializer) {
if (serializer == null && deserializer == null) return;
ValueAdapter<T> existing = adapterOf(type);
if (existing != null) {
@@ -2,6 +2,7 @@ package cc.carm.lib.configuration.adapter;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Value deserializer, convert base data to target value.
@@ -11,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ValueParser<TYPE> {
TYPE parse(
@Nullable TYPE parse(
@NotNull ConfigurationHolder<?> holder,
@NotNull ValueType<? super TYPE> type, @NotNull Object data
) throws Exception;
@@ -2,6 +2,7 @@ package cc.carm.lib.configuration.adapter;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Value serializer, convert target value to base data.
@@ -11,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ValueSerializer<TYPE> {
Object serialize(
@Nullable Object serialize(
@NotNull ConfigurationHolder<?> holder,
@NotNull ValueType<? super TYPE> type, @NotNull TYPE value
) throws Exception;
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,7 +158,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName());
sb.append(raw.getClass().getName());
sb.append('<');
Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) {
@@ -166,6 +167,7 @@ public abstract class ValueType<T> {
}
sb.append(args[i].getTypeName());
}
sb.append('>');
return sb.toString();
}
return type.getTypeName();
@@ -5,6 +5,8 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import static cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapter.*;
public interface StandardAdapters {
@@ -17,6 +19,12 @@ public interface StandardAdapters {
@NotNull ValueAdapter<Enum<?>> ENUMS = PrimitiveAdapter.ofEnum();
@NotNull ValueAdapter<UUID> UUID = new ValueAdapter<>(
ValueType.of(UUID.class),
(provider, type, value) -> value.toString(),
(provider, type, value) -> java.util.UUID.fromString(value.toString())
);
@NotNull ValueAdapter<ConfigureSection> SECTIONS = new ValueAdapter<>(
ValueType.of(ConfigureSection.class),
(provider, type, value) -> value,
@@ -1,21 +1,26 @@
package cc.carm.lib.configuration.builder;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataValidator;
import cc.carm.lib.configuration.function.ValueValidator;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@NotNullByDefault
public abstract class AbstractConfigBuilder<
TYPE, RESULT extends ConfigValue<TYPE>, HOLDER extends ConfigurationHolder<?>,
SELF extends AbstractConfigBuilder<TYPE, RESULT, HOLDER, SELF>
TYPE, UNIT, RESULT extends ConfigValue<TYPE, UNIT>, HOLDER extends ConfigurationHolder<?>,
SELF extends AbstractConfigBuilder<TYPE, UNIT, RESULT, HOLDER, SELF>
> {
protected final Class<? super HOLDER> providerClass;
@@ -24,6 +29,7 @@ public abstract class AbstractConfigBuilder<
protected @Nullable HOLDER holder;
protected @Nullable String path;
protected @NotNull ValueValidator<UNIT> valueValidator = ValueValidator.none();
protected @NotNull Supplier<@Nullable TYPE> defaultValueSupplier = () -> null;
protected @NotNull BiConsumer<ConfigurationHolder<?>, String> initializer = (h, p) -> {
};
@@ -37,54 +43,121 @@ public abstract class AbstractConfigBuilder<
return type;
}
protected abstract @NotNull SELF self();
protected abstract SELF self();
public abstract @NotNull RESULT build();
public @NotNull SELF holder(@Nullable HOLDER holder) {
public SELF holder(@Nullable HOLDER holder) {
this.holder = holder;
return self();
}
public @NotNull SELF path(@Nullable String path) {
public SELF path(@Nullable String path) {
this.path = path;
return self();
}
public @NotNull SELF initializer(@NotNull BiConsumer<ConfigurationHolder<?>, String> initializer) {
/**
* Set the {@link ValueValidator} for the value.
*
* @param validator The validator to set.
* @return this builder
*/
public SELF validator(@NotNull ValueValidator<UNIT> validator) {
this.valueValidator = validator;
return self();
}
/**
* Set the {@link DataValidator} for the value.
*
* @param validator The validator to set.
* @return this builder
*/
public SELF validator(@NotNull DataValidator<? super UNIT> validator) {
return validator((h, value) -> validator.validate(value));
}
/**
* Validate the value with the specified condition.
*
* @param validator The validator to append.
* @return this builder
*/
public SELF validate(@NotNull ValueValidator<? super UNIT> validator) {
return validator(this.valueValidator.and(validator));
}
/**
* Validate the value with the specified condition.
*
* @param validator The validator to append.
* @return this builder
*/
public SELF validate(@NotNull DataValidator<? super UNIT> validator) {
return validate((h, value) -> validator.validate(value));
}
/**
* Validate the value with the specified condition.
*
* @param condition The condition to check, if the condition is false, an exception will be thrown.
* @param exception The exception to throw if the condition is false.
* @return this builder
*/
public SELF validate(@NotNull Predicate<? super UNIT> condition, @NotNull Exception exception) {
return validate((h, value) -> {
if (!condition.test(value)) throw exception;
});
}
/**
* Validate the value with the specified condition.
*
* @param condition The condition to check, if the condition is false, an exception will be thrown.
* @param msg The message to throw if the condition is false.
* @return this builder
*/
public SELF validate(@NotNull Predicate<? super UNIT> condition, @NotNull String msg) {
return validate((h, value) -> {
if (!condition.test(value)) throw new IllegalArgumentException(msg);
});
}
public SELF initializer(@NotNull BiConsumer<ConfigurationHolder<?>, String> initializer) {
this.initializer = initializer;
return self();
}
public @NotNull SELF append(@NotNull BiConsumer<ConfigurationHolder<?>, String> initializer) {
public SELF append(@NotNull BiConsumer<ConfigurationHolder<?>, String> initializer) {
return initializer(initializer.andThen(initializer));
}
public @NotNull SELF append(@NotNull Consumer<ConfigurationHolder<?>> initializer) {
public SELF append(@NotNull Consumer<ConfigurationHolder<?>> initializer) {
return append((provider, valuePath) -> initializer.accept(provider));
}
public @NotNull SELF defaults(@Nullable TYPE defaultValue) {
public SELF defaults(@Nullable TYPE defaultValue) {
return defaults(() -> defaultValue);
}
public @NotNull SELF defaults(@NotNull Supplier<@Nullable TYPE> supplier) {
public SELF defaults(@NotNull Supplier<@Nullable TYPE> supplier) {
this.defaultValueSupplier = supplier;
return self();
}
public <M> @NotNull SELF meta(@NotNull Consumer<@NotNull ConfigurationMetaHolder> metaConsumer) {
public SELF meta(@NotNull Consumer<@NotNull ConfigurationMetaHolder> metaConsumer) {
return append((h, p) -> metaConsumer.accept(h.metadata(p)));
}
public <M> @NotNull SELF meta(@NotNull ConfigurationMetadata<M> type, @Nullable M value) {
public <M> SELF meta(@NotNull ConfigurationMetadata<M> type, @Nullable M value) {
return meta(h -> h.set(type, value));
}
protected @NotNull ValueManifest<TYPE> buildManifest() {
protected @NotNull ValueManifest<TYPE, UNIT> buildManifest() {
return new ValueManifest<>(
type(), this.defaultValueSupplier, this.initializer,
this.holder, this.path
type(), this.defaultValueSupplier, this.valueValidator,
this.initializer, this.holder, this.path
);
}
@@ -4,8 +4,11 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.value.ConfigValue;
public abstract class CommonConfigBuilder<TYPE, RESULT extends ConfigValue<TYPE>, SELF extends CommonConfigBuilder<TYPE, RESULT, SELF>>
extends AbstractConfigBuilder<TYPE, RESULT, ConfigurationHolder<?>, SELF> {
public abstract class CommonConfigBuilder<
TYPE, UNIT,
RESULT extends ConfigValue<TYPE, UNIT>,
SELF extends CommonConfigBuilder<TYPE, UNIT, RESULT, SELF>
> extends AbstractConfigBuilder<TYPE, UNIT, RESULT, ConfigurationHolder<?>, SELF> {
protected CommonConfigBuilder(ValueType<TYPE> type) {
super(ConfigurationHolder.class, type);
@@ -4,7 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.function.ValueConsumer;
import cc.carm.lib.configuration.function.ValueComposer;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ConfigValue;
@@ -14,47 +14,45 @@ import java.util.LinkedHashMap;
import java.util.Map;
public abstract class AbstractSectionBuilder<
TYPE, PARAM,
RESULT extends ConfigValue<TYPE>,
SELF extends AbstractSectionBuilder<TYPE, PARAM, RESULT, SELF>
> extends CommonConfigBuilder<TYPE, RESULT, SELF> {
TYPE, UNIT,
RESULT extends ConfigValue<TYPE, UNIT>,
SELF extends AbstractSectionBuilder<TYPE, UNIT, RESULT, SELF>
> extends CommonConfigBuilder<TYPE, UNIT, RESULT, SELF> {
protected final @NotNull ValueType<PARAM> paramType;
protected final @NotNull ValueType<UNIT> paramType;
protected @NotNull ValueHandler<ConfigureSection, PARAM> parser;
protected @NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer;
protected @NotNull ValueHandler<ConfigureSection, UNIT> parser;
protected @NotNull ValueHandler<UNIT, ? extends Map<String, Object>> serializer;
protected AbstractSectionBuilder(@NotNull ValueType<TYPE> type, @NotNull ValueType<PARAM> paramType,
@NotNull ValueHandler<ConfigureSection, PARAM> parser,
@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
protected AbstractSectionBuilder(@NotNull ValueType<TYPE> type, @NotNull ValueType<UNIT> paramType,
@NotNull ValueHandler<ConfigureSection, UNIT> parser,
@NotNull ValueHandler<UNIT, ? extends Map<String, Object>> serializer) {
super(type);
this.paramType = paramType;
this.parser = parser;
this.serializer = serializer;
}
public @NotNull SELF parse(@NotNull DataFunction<ConfigureSection, PARAM> valueParser) {
public @NotNull SELF parse(@NotNull DataFunction<ConfigureSection, UNIT> valueParser) {
return parse((p, section) -> valueParser.handle(section));
}
public @NotNull SELF parse(@NotNull ValueHandler<ConfigureSection, PARAM> valueParser) {
public @NotNull SELF parse(@NotNull ValueHandler<ConfigureSection, UNIT> valueParser) {
this.parser = valueParser;
return self();
}
public @NotNull SELF serialize(@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
public @NotNull SELF serialize(@NotNull ValueHandler<UNIT, ? extends Map<String, Object>> serializer) {
this.serializer = serializer;
return self();
}
public @NotNull SELF serialize(@NotNull DataFunction<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> {
return serializer.handle(value);
});
public @NotNull SELF serialize(@NotNull DataFunction<UNIT, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
public @NotNull SELF serialize(@NotNull ValueConsumer<Map<String, Object>, PARAM> serializer) {
public @NotNull SELF serialize(@NotNull ValueComposer<Map<String, Object>, UNIT> serializer) {
return serialize((h, value) -> {
Map<String, Object> map = new LinkedHashMap<>();
serializer.accept(h, map, value);
@@ -62,7 +60,7 @@ public abstract class AbstractSectionBuilder<
});
}
protected ValueAdapter<PARAM> buildAdapter() {
protected ValueAdapter<UNIT> buildAdapter() {
return new ValueAdapter<>(this.paramType)
.parser((p, type, data) -> {
ConfigureSection section = p.deserialize(ConfigureSection.class, data);
@@ -9,19 +9,19 @@ import cc.carm.lib.configuration.value.ConfigValue;
import org.jetbrains.annotations.NotNull;
public abstract class AbstractSourceBuilder<
V, SOURCE, PARAM, RESULT extends ConfigValue<V>,
SELF extends AbstractSourceBuilder<V, SOURCE, PARAM, RESULT, SELF>
> extends CommonConfigBuilder<V, RESULT, SELF> {
V, SOURCE, UNIT, RESULT extends ConfigValue<V, UNIT>,
SELF extends AbstractSourceBuilder<V, SOURCE, UNIT, RESULT, SELF>
> extends CommonConfigBuilder<V, UNIT, RESULT, SELF> {
protected final @NotNull ValueType<SOURCE> sourceType;
protected final @NotNull ValueType<PARAM> paramType;
protected @NotNull ValueHandler<SOURCE, PARAM> valueParser;
protected @NotNull ValueHandler<PARAM, SOURCE> valueSerializer;
protected final @NotNull ValueType<UNIT> paramType;
protected @NotNull ValueHandler<SOURCE, UNIT> valueParser;
protected @NotNull ValueHandler<UNIT, SOURCE> valueSerializer;
protected AbstractSourceBuilder(@NotNull ValueType<V> type,
@NotNull ValueType<SOURCE> sourceType, @NotNull ValueType<PARAM> paramType,
@NotNull ValueHandler<SOURCE, PARAM> parser,
@NotNull ValueHandler<PARAM, SOURCE> serializer) {
@NotNull ValueType<SOURCE> sourceType, @NotNull ValueType<UNIT> paramType,
@NotNull ValueHandler<SOURCE, UNIT> parser,
@NotNull ValueHandler<UNIT, SOURCE> serializer) {
super(type);
this.sourceType = sourceType;
this.paramType = paramType;
@@ -29,25 +29,25 @@ public abstract class AbstractSourceBuilder<
this.valueSerializer = serializer;
}
public @NotNull SELF parse(@NotNull DataFunction<SOURCE, PARAM> parser) {
public @NotNull SELF parse(@NotNull DataFunction<SOURCE, UNIT> parser) {
return parse((p, source) -> parser.handle(source));
}
public @NotNull SELF parse(@NotNull ValueHandler<SOURCE, PARAM> parser) {
public @NotNull SELF parse(@NotNull ValueHandler<SOURCE, UNIT> parser) {
this.valueParser = parser;
return self();
}
public @NotNull SELF serialize(@NotNull ValueHandler<PARAM, SOURCE> serializer) {
public @NotNull SELF serialize(@NotNull ValueHandler<UNIT, SOURCE> serializer) {
this.valueSerializer = serializer;
return self();
}
public @NotNull SELF serialize(@NotNull DataFunction<PARAM, SOURCE> serializer) {
public @NotNull SELF serialize(@NotNull DataFunction<UNIT, SOURCE> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
protected ValueAdapter<PARAM> buildAdapter() {
protected ValueAdapter<UNIT> buildAdapter() {
return new ValueAdapter<>(this.paramType)
.parser((holder, type, data) -> {
SOURCE source = holder.deserialize(this.sourceType, data);
@@ -8,9 +8,11 @@ import cc.carm.lib.configuration.value.standard.ConfiguredList;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SectionListBuilder<V> extends AbstractSectionBuilder<List<V>, V, ConfiguredList<V>, SectionListBuilder<V>> {
public class SectionListBuilder<V>
extends AbstractSectionBuilder<List<V>, V, ConfiguredList<V>, SectionListBuilder<V>> {
protected @NotNull Supplier<? extends List<V>> constructor;
@@ -32,6 +34,14 @@ public class SectionListBuilder<V> extends AbstractSectionBuilder<List<V>, V, Co
return defaults(new ArrayList<>(values));
}
public final @NotNull SectionListBuilder<V> defaults(@NotNull Consumer<List<V>> constructor) {
return defaults(() -> {
List<V> list = new ArrayList<>();
constructor.accept(list);
return list;
});
}
public SectionListBuilder<V> constructor(@NotNull Supplier<? extends List<V>> constructor) {
this.constructor = constructor;
return this;
@@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SourceListBuilder<SOURCE, V>
@@ -34,6 +35,23 @@ public class SourceListBuilder<SOURCE, V>
return defaults(new ArrayList<>(values));
}
public final @NotNull SourceListBuilder<SOURCE, V> defaults(@NotNull Consumer<List<V>> constructor) {
return defaults(() -> {
List<V> list = new ArrayList<>();
constructor.accept(list);
return list;
});
}
public SourceListBuilder<SOURCE, V> constructor(@NotNull Supplier<? extends List<V>> constructor) {
this.constructor = constructor;
return this;
}
public <LIST extends List<V>> SourceListBuilder<SOURCE, V> construct(@NotNull LIST list) {
return constructor(() -> list);
}
@Override
protected @NotNull SourceListBuilder<SOURCE, V> self() {
return this;
@@ -3,10 +3,7 @@ package cc.carm.lib.configuration.builder.map;
import cc.carm.lib.configuration.adapter.ValueType;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
import java.util.function.Supplier;
public class ConfigMapCreator<K, V> {
@@ -45,4 +42,8 @@ public class ConfigMapCreator<K, V> {
return constructor(TreeMap::new);
}
public @NotNull ConfigMapBuilder<TreeMap<K, V>, K, V> asTreeMap(@NotNull Comparator<? super K> comparator) {
return constructor(() -> new TreeMap<>(comparator));
}
}
@@ -14,10 +14,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
public class SectionMapBuilder<MAP extends Map<K, V>, K, V>
extends AbstractSectionBuilder<
Map<K, V>, V, ConfiguredMap<K, V>,
SectionMapBuilder<MAP, K, V>
> {
extends AbstractSectionBuilder<Map<K, V>, V, ConfiguredMap<K, V>, SectionMapBuilder<MAP, K, V>> {
protected final @NotNull ValueType<K> keyType;
@@ -75,6 +72,10 @@ public class SectionMapBuilder<MAP extends Map<K, V>, K, V>
});
}
public @NotNull SectionMapBuilder<MAP, K, V> defaults(@NotNull K key, @NotNull V value) {
return defaults(map -> map.put(key, value));
}
public @NotNull ValueAdapter<K> buildKeyAdapter() {
return new ValueAdapter<>(this.keyType)
.parser((holder, type, data) -> {
@@ -13,10 +13,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
public class SourceMapBuilder<MAP extends Map<K, V>, SOURCE, K, V>
extends AbstractSourceBuilder<
Map<K, V>, SOURCE, V, ConfiguredMap<K, V>,
SourceMapBuilder<MAP, SOURCE, K, V>
> {
extends AbstractSourceBuilder<Map<K, V>, SOURCE, V, ConfiguredMap<K, V>, SourceMapBuilder<MAP, SOURCE, K, V>> {
protected final @NotNull ValueType<K> keyType;
@@ -53,6 +50,10 @@ public class SourceMapBuilder<MAP extends Map<K, V>, SOURCE, K, V>
});
}
public @NotNull SourceMapBuilder<MAP, SOURCE, K, V> defaults(@NotNull K key, @NotNull V value) {
return defaults(map -> map.put(key, value));
}
public @NotNull SourceMapBuilder<MAP, SOURCE, K, V> parseKey(@NotNull DataFunction<String, K> keyParser) {
return parseKey((holder, data) -> keyParser.handle(data));
}
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.builder.value;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.impl.AbstractSectionBuilder;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import org.jetbrains.annotations.NotNull;
@@ -6,7 +6,8 @@ import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import org.jetbrains.annotations.NotNull;
public class SourceValueBuilder<S, V> extends AbstractSourceBuilder<V, S, V, ConfiguredValue<V>, SourceValueBuilder<S, V>> {
public class SourceValueBuilder<S, V>
extends AbstractSourceBuilder<V, S, V, ConfiguredValue<V>, SourceValueBuilder<S, V>> {
public SourceValueBuilder(@NotNull ValueType<S> sourceType, @NotNull ValueType<V> valueType,
@@ -0,0 +1,22 @@
package cc.carm.lib.configuration.function;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ConfigExceptionHandler {
void handle(@NotNull String path, @NotNull Throwable throwable);
static @NotNull ConfigExceptionHandler silence() {
return (path, throwable) -> {
};
}
static @NotNull ConfigExceptionHandler print() {
return (path, throwable) -> {
System.err.println("Error occurred at path: " + path);
throwable.printStackTrace();
};
}
}
@@ -0,0 +1,17 @@
package cc.carm.lib.configuration.function;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface DataValidator<T> {
void validate(@Nullable T value) throws Exception;
default DataValidator<T> compose(DataValidator<? super T> other) {
return value -> {
validate(value);
other.validate(value);
};
}
}
@@ -0,0 +1,29 @@
package cc.carm.lib.configuration.function;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ValueComposer<T, U> {
/**
* Accept the value and the data, and then compose the value.
*
* @param holder The configuration holder
* @param type The value type, e.g. {@link java.util.List}, {@link java.util.Map}, etc.
* @param data The unit data
* @throws Exception If an error occurs
*/
void accept(@NotNull ConfigurationHolder<?> holder, @NotNull T type, @NotNull U data) throws Exception;
default ValueComposer<T, U> andThen(ValueComposer<? super T, ? super U> after) {
return (holder, unit, data) -> {
accept(holder, unit, data);
after.accept(holder, unit, data);
};
}
}
@@ -1,21 +0,0 @@
package cc.carm.lib.configuration.function;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ValueConsumer<U, T> {
void accept(@NotNull ConfigurationHolder<?> holder, @NotNull U unit, @NotNull T data) throws Exception;
default ValueConsumer<U, T> andThen(ValueConsumer<? super U, ? super T> after) {
return (holder, unit, data) -> {
accept(holder, unit, data);
after.accept(holder, unit, data);
};
}
}
@@ -0,0 +1,7 @@
package cc.carm.lib.configuration.function;
public interface ValueSupplier {
}
@@ -0,0 +1,47 @@
package cc.carm.lib.configuration.function;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface ValueValidator<T> {
void validate(@NotNull ConfigurationHolder<?> holder, @Nullable T value) throws Exception;
default ValueValidator<T> and(ValueValidator<? super T> other) {
return (holder, value) -> {
validate(holder, value);
other.validate(holder, value);
};
}
static <V> ValueValidator<V> none() {
return (holder, data) -> {
};
}
static <V> ValueValidator<V> nonnull() {
return nonnull("Value cannot be null");
}
static <V> ValueValidator<V> nonnull(String message) {
return (holder, data) -> {
if (data == null) throw new IllegalArgumentException(message);
};
}
static <V extends Number> ValueValidator<V> range(V min, V max) {
return range(min, max, "Value must be in range [" + min + ", " + max + "]");
}
static <V extends Number> ValueValidator<V> range(V min, V max, String message) {
return (holder, data) -> {
if (data.doubleValue() < min.doubleValue() || data.doubleValue() > max.doubleValue()) {
throw new IllegalArgumentException(message);
}
};
}
}
@@ -2,7 +2,9 @@ package cc.carm.lib.configuration.source;
import cc.carm.lib.configuration.adapter.*;
import cc.carm.lib.configuration.adapter.strandard.StandardAdapters;
import cc.carm.lib.configuration.function.ConfigExceptionHandler;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.function.ValueValidator;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.loader.PathGenerator;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
@@ -33,15 +35,17 @@ public abstract class ConfigurationFactory<
SELF
> {
protected ValueAdapterRegistry adapters = new ValueAdapterRegistry();
protected ConfigurationOptionHolder options = new ConfigurationOptionHolder();
protected @NotNull ValueAdapterRegistry adapters = new ValueAdapterRegistry();
protected @NotNull ConfigurationOptionHolder options = new ConfigurationOptionHolder();
protected @NotNull Map<String, ConfigurationMetaHolder> metadata = new HashMap<>();
protected ConfigurationInitializer initializer = new ConfigurationInitializer();
protected @NotNull ConfigurationInitializer initializer = new ConfigurationInitializer();
protected @NotNull ConfigExceptionHandler exceptionHandler = ConfigExceptionHandler.print();
public ConfigurationFactory() {
protected ConfigurationFactory() {
this.adapters.register(StandardAdapters.PRIMITIVES);
this.adapters.register(StandardAdapters.SECTIONS);
this.adapters.register(StandardAdapters.ENUMS);
this.adapters.register(StandardAdapters.UUID);
}
protected abstract SELF self();
@@ -146,6 +150,11 @@ public abstract class ConfigurationFactory<
return self();
}
public SELF exceptionally(@NotNull ConfigExceptionHandler handler) {
this.exceptionHandler = handler;
return self();
}
/**
* Supply the base path generator for this configuration holder
*
@@ -153,9 +162,7 @@ public abstract class ConfigurationFactory<
* @return this
*/
public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> {
loader.pathGenerator(generator);
});
return initializer(loader -> loader.pathGenerator(generator));
}
/**
@@ -174,6 +181,19 @@ public abstract class ConfigurationFactory<
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
}
/**
* Register a new annotation for {@link ValueValidator} to the configuration loader
*
* @param annotation The {@link Annotation}
* @param builder The {@link Function} to build the {@link ValueValidator} from the annotation
* @param <A> The annotation type
* @return this
*/
public <A extends Annotation> SELF validAnnotation(@NotNull Class<A> annotation,
@NotNull Function<A, ValueValidator<Object>> builder) {
return initializer(loader -> loader.registerValidAnnotation(annotation, builder));
}
/**
* Build the configuration holder.
*
@@ -3,12 +3,15 @@ package cc.carm.lib.configuration.source;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.ConfigExceptionHandler;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -18,6 +21,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -27,14 +32,25 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
protected final @NotNull ConfigurationInitializer initializer;
protected @NotNull ConfigExceptionHandler exceptionHandler;
public ConfigurationHolder(@NotNull ValueAdapterRegistry adapters,
@NotNull ConfigurationOptionHolder options,
@NotNull Map<String, ConfigurationMetaHolder> metadata,
@NotNull ConfigurationInitializer initializer) {
this(adapters, options, metadata, initializer, ConfigExceptionHandler.print());
}
public ConfigurationHolder(@NotNull ValueAdapterRegistry adapters,
@NotNull ConfigurationOptionHolder options,
@NotNull Map<String, ConfigurationMetaHolder> metadata,
@NotNull ConfigurationInitializer initializer,
@NotNull ConfigExceptionHandler exceptionHandler) {
this.initializer = initializer;
this.adapters = adapters;
this.options = options;
this.metadata = metadata;
this.exceptionHandler = exceptionHandler;
}
public abstract @NotNull SOURCE config();
@@ -51,7 +67,7 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options;
}
public <O> O option(@NotNull ConfigurationOption<O> option) {
public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
@@ -66,14 +82,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) {
return extractMetadata(type, Objects::nonNull);
}
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type, @NotNull Predicate<M> filter) {
Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type);
if (data != null) metas.put(entry.getKey(), data);
if (filter.test(data)) metas.put(entry.getKey(), data);
}
return Collections.unmodifiableMap(metas);
}
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?, ?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() {
return this.adapters;
}
@@ -101,7 +129,7 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
try {
initializer.initialize(this, configClass);
} catch (Exception e) {
e.printStackTrace();
throwing(configClass.getName(), e);
}
}
@@ -109,12 +137,20 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
try {
initializer.initialize(this, config);
} catch (Exception e) {
e.printStackTrace();
throwing(config.getClass().getName(), e);
}
}
public void initialize(@NotNull ValueManifest<?> value) {
public void initialize(@NotNull ValueManifest<?, ?> value) {
value.holder(this);
}
public void throwing(@NotNull String path, @NotNull Throwable e) {
this.exceptionHandler.handle(path, e);
}
public void exceptionally(@NotNull ConfigExceptionHandler handler) {
this.exceptionHandler = handler;
}
}
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface ConfigInitializeHandler<T> {
public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() {
return (provider, path, value) -> {
static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value, instance) -> {
};
}
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path, @NotNull T value) throws Exception;
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path,
@NotNull T value, @Nullable V instance) throws Exception;
default ConfigInitializeHandler<T> andThen(ConfigInitializeHandler<T> after) {
return (provider, path, value) -> {
whenInitialize(provider, path, value);
after.whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value, instance) -> {
whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value, instance);
};
}
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) {
return (provider, path, value) -> {
before.whenInitialize(provider, path, value);
whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value, instance);
};
}
@@ -1,6 +1,7 @@
package cc.carm.lib.configuration.source.loader;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.function.ValueValidator;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
@@ -21,18 +22,18 @@ import java.util.function.Function;
public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer;
protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?, ?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
}
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
@NotNull ConfigInitializeHandler<Field, ConfigValue<?, ?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer;
this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer;
}
@@ -44,34 +45,34 @@ public class ConfigurationInitializer {
return pathGenerator;
}
public ConfigInitializeHandler<Field> fieldInitializer() {
return fieldInitializer;
public ConfigInitializeHandler<Field, ConfigValue<?, ?>> fieldInitializer() {
return valueInitializer;
}
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = fieldInitializer;
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?, ?>> fieldInitializer) {
this.valueInitializer = fieldInitializer;
}
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() {
public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return classInitializer;
}
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = classInitializer;
}
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(fieldInitializer);
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?, ?>> fieldInitializer) {
this.valueInitializer = this.valueInitializer.andThen(fieldInitializer);
}
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = this.classInitializer.andThen(classInitializer);
}
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> {
appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -81,7 +82,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> {
appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -95,6 +96,14 @@ public class ConfigurationInitializer {
registerFieldAnnotation(annotation, metadata, extractor);
}
public <A extends Annotation> void registerValidAnnotation(@NotNull Class<A> annotation,
@NotNull Function<A, ValueValidator<Object>> builder) {
appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
instance.validate((h, t) -> builder.apply(data).validate(h, t));
});
}
public @Nullable String getFieldPath(@NotNull ConfigurationHolder<?> holder, @Nullable String parentPath, @NotNull Field field) {
return pathGenerator.getFieldPath(holder, parentPath, field);
@@ -123,9 +132,9 @@ public class ConfigurationInitializer {
@Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(holder, parentPath, root.getClass(), configField);
try {
this.classInitializer.whenInitialize(holder, path, root.getClass());
this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) {
e.printStackTrace();
holder.throwing(path, e);
}
Arrays.stream(root.getClass().getDeclaredFields()).forEach(field -> initializeField(holder, root, field, path));
}
@@ -140,9 +149,9 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField);
try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz);
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) {
e.printStackTrace();
holder.throwing(path, e);
}
for (Field field : clazz.getDeclaredFields()) {
@@ -163,21 +172,21 @@ public class ConfigurationInitializer {
field.setAccessible(true);
Object object = field.get(source);
//
if (object instanceof ConfigValue<?>) {
if (object instanceof ConfigValue<?, ?>) {
// 目标是 ConfigValue 实例,进行具体的初始化注入
ConfigValue<?> value = (ConfigValue<?>) object;
ConfigValue<?, ?> value = (ConfigValue<?, ?>) object;
String path = getFieldPath(holder, parent, field);
if (path == null) return;
value.initialize(holder, path);
holder.metadata(path).set(StandardMeta.UNIT, true); // Mark the minimal config value unit.
try {
this.fieldInitializer.whenInitialize(holder, path, field);
} catch (Exception e) {
e.printStackTrace();
}
holder.metadata(path).set(StandardMeta.VALUE, value); // Mark the minimal config value unit.
if (holder.option(StandardOptions.SET_DEFAULTS)) {
value.setDefault(); // Set default value.
}
try {
this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) {
holder.throwing(path, e);
}
if (holder.option(StandardOptions.PRELOAD)) {
value.get(); // Preload the value by calling #get method.
}
@@ -1,10 +1,12 @@
package cc.carm.lib.configuration.source.meta;
import cc.carm.lib.configuration.value.ConfigValue;
public interface StandardMeta {
/**
* To mark the {@link cc.carm.lib.configuration.value.ConfigValue} as a minimal unit path.
* To mark the {@link ConfigValue} instance of specific path.
*/
ConfigurationMetadata<Boolean> UNIT = ConfigurationMetadata.of(false);
ConfigurationMetadata<ConfigValue<?, ?>> VALUE = ConfigurationMetadata.of();
}
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/**
* Whether to pre parse the config values.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* Whether to pre parse the config values,
* may good to set true if you want to keep the config format.
* <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
*/
ConfigurationOption<Boolean> PRELOAD = of(false);
ConfigurationOption<Boolean> PRELOAD = of(true);
}
@@ -7,6 +7,8 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -29,6 +31,60 @@ public interface ConfigureSection {
@Contract(pure = true)
@Nullable ConfigureSection parent();
/**
* Get the current section's path from {@link #parent()} of this section.
*
* @return The current path of this section, if {@link #isRoot()}, return empty string.
*/
@NotNull String path();
/**
* Get the full path of this section.
*
* @return The full path of this section, if {@link #isRoot()}, return empty string.
*/
default @NotNull String fullPath() {
if (parent() == null) return "";
return (parent().isRoot() ? "" : parent().fullPath() + pathSeparator()) + path();
}
/**
* Get the path separator for the section.
*
* @return The path separator
*/
default char pathSeparator() {
return '.';
}
/**
* Gets if this section is a root section.
*
* @return True if this section is a root section, false otherwise.
*/
@Contract(pure = true)
default boolean isRoot() {
return parent() == null;
}
/**
* Gets if this section is empty.
*
* @return True if this section is empty, false otherwise.
*/
default boolean isEmpty() {
return getValues(false).isEmpty();
}
/**
* Gets the number of keys in this section.
*
* @return Number of keys in this section.
*/
default int size(boolean deep) {
return getKeys(deep).size();
}
/**
* Gets a set containing all keys in this section.
* <p>
@@ -47,6 +103,17 @@ public interface ConfigureSection {
return getValues(deep).keySet();
}
/**
* Gets a set containing all primary keys in this section.
*
* @return Set of keys contained within this Section.
*/
@NotNull
@UnmodifiableView
default Set<String> keys() {
return getKeys(false);
}
/**
* Gets a set containing all values in this section.
* <p>
@@ -63,6 +130,47 @@ public interface ConfigureSection {
@UnmodifiableView
Map<String, Object> getValues(boolean deep);
/**
* Gets a set containing all key-values in this section.
*
* @return Map of data values contained within this Section.
* @see #getValues(boolean)
*/
@NotNull
@UnmodifiableView
default Map<String, Object> values() {
return getValues(false);
}
/**
* Get this section as a map.
* <p>
* In this map, child {@link ConfigureSection}s will also be represented as {@link Map}s.
*
* @return Map of data values contained within this Section.
*/
@NotNull
@UnmodifiableView
Map<String, Object> asMap();
/**
* Create a stream of all values in this section.
*
* @return Stream of all values in this section.
*/
default Stream<Map.Entry<String, Object>> stream() {
return values().entrySet().stream();
}
/**
* Iterates over all key-values in this section (include child sections)
*
* @param action The action to apply to each key.
*/
default void forEach(@NotNull BiConsumer<String, Object> action) {
values().forEach(action);
}
/**
* Sets the value at the given path.
* <p>
@@ -85,6 +193,19 @@ public interface ConfigureSection {
*/
void remove(@NotNull String path);
/**
* Check if the given path is present.
* <p>
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to check.
* @return True if the value is present, false otherwise.
*/
default boolean contains(@NotNull String path) {
return getKeys(true).contains(path);
}
/**
* Check if the value of given path is present.
* <p>
@@ -94,7 +215,9 @@ public interface ConfigureSection {
* @param path The path to check.
* @return True if the value is present, false otherwise.
*/
boolean contains(@NotNull String path);
default boolean containsValue(@NotNull String path) {
return get(path) != null;
}
/**
* Predicate the value of given path is specific type.
@@ -124,7 +247,10 @@ public interface ConfigureSection {
* @param path The path to get the {@link List}.
* @return The list if the path exists and is a list, otherwise null.
*/
@Nullable List<?> getList(@NotNull String path);
default @Nullable List<?> getList(@NotNull String path) {
Object val = get(path);
return (val instanceof List<?>) ? (List<?>) val : null;
}
/**
* Predicate the value of given path is a {@link ConfigureSection}.
@@ -143,7 +269,123 @@ public interface ConfigureSection {
* @return The section if the path exists and is a section, otherwise null.
*/
@Nullable
ConfigureSection getSection(@NotNull String path);
default ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
* <p> This section will not be saved until {@link #set(String, Object)} is called.
* <p> If you want to create and use a section and set it to this section, use {@link #computeSection(String)}.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
@NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data);
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return createSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return createSection(path, data.get());
}
/**
* Creates a new empty {@link ConfigureSection}.
* <p> The {@link #parent()} of the new section will be this section.
*
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path) {
return createSection(path, new LinkedHashMap<>());
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path) {
return computeSection(path, new LinkedHashMap<>());
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Map<?, ?> data) {
ConfigureSection current = getSection(path);
if (current == null) {
current = createSection(path, data);
set(path, current);
}
return current;
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return computeSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return computeSection(path, data.get());
}
/**
* Get the origin value of the path.
@@ -318,7 +560,7 @@ public interface ConfigureSection {
* Predicate the value of given path is a {@link Integer}.
*
* @param path The path to check.
* @return True if the value is present and is a int, false otherwise.
* @return True if the value is present and is an int, false otherwise.
*/
default boolean isInt(@NotNull String path) {
return isType(path, Integer.class);
@@ -328,7 +570,7 @@ public interface ConfigureSection {
* Get the value as a {@link Integer} from the specified path.
*
* @param path The path to get the int.
* @return The int if the path exists and is a int, otherwise 0.
* @return The int if the path exists and is an int, otherwise 0.
*/
default @Nullable Integer getInt(@NotNull String path) {
return getInt(path, 0);
@@ -339,7 +581,7 @@ public interface ConfigureSection {
*
* @param path The path to get the int.
* @param def The default value to return if the path does not exist.
* @return The int if the path exists and is a int, otherwise the default value.
* @return The int if the path exists and is an int, otherwise the default value.
*/
@Contract("_, !null -> !null")
default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
@@ -638,8 +880,14 @@ public interface ConfigureSection {
return stream(path).map(parser);
}
default String childPath(String path) {
int index = path.indexOf(pathSeparator());
return (index == -1) ? path : path.substring(index + 1);
}
static <T, C extends Collection<T>> @NotNull C parseCollection(
@Nullable List<?> data, @NotNull Supplier<C> constructor,
@Nullable List<?> data,
@NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser
) {
C values = constructor.get();
@@ -2,13 +2,10 @@ package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ConfigureSource represents the source of configuration,
@@ -17,6 +14,7 @@ import java.util.Map;
* @param <SECTION> The type of the root section.
* @param <ORIGINAL> The original configuration object.
* @param <SELF> The type of the source itself, for further implement support.
* @see ConfigureSection
*/
public abstract class ConfigureSource<
SECTION extends ConfigureSection, ORIGINAL,
@@ -94,36 +92,41 @@ public abstract class ConfigureSource<
return null;
}
@Override
public @NotNull String path() {
return "";
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return section().getValues(deep);
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return section().getKeys(deep);
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> asMap() {
return section().asMap();
}
@Override
public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return section().createSection(path, data);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
section().set(path, value);
}
@Override
public boolean contains(@NotNull String path) {
return section().contains(path);
}
@Override
public void remove(@NotNull String path) {
section().remove(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return section().getList(path);
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
return section().getSection(path);
}
@Override
public @Nullable Object get(@NotNull String path) {
return section().get(path);
@@ -33,9 +33,9 @@ import java.util.Optional;
* @see ValueManifest Base class providing metadata and default value handling
* @see ConfigurationHolder Responsible for configuration source persistence
*/
public abstract class ConfigValue<T> extends ValueManifest<T> {
public abstract class ConfigValue<T, U> extends ValueManifest<T, U> {
protected ConfigValue(@NotNull ValueManifest<T> manifest) {
protected ConfigValue(@NotNull ValueManifest<T, U> manifest) {
super(manifest);
}
@@ -1,47 +1,59 @@
package cc.carm.lib.configuration.value;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.ValueValidator;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class ValueManifest<T> {
public class ValueManifest<TYPE, UNIT> {
protected final @NotNull ValueType<T> type;
protected final @NotNull ValueType<TYPE> type;
protected final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> initializer;
protected @Nullable ConfigurationHolder<?> holder;
protected @Nullable String path; // Section path
protected @NotNull Supplier<@Nullable T> defaultSupplier;
protected @NotNull ValueValidator<UNIT> validator;
protected @NotNull Supplier<@Nullable TYPE> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null);
public ValueManifest(@NotNull ValueType<TYPE> type) {
this(type, () -> null, ValueValidator.none(), EMPTY_INITIALIZER, null, null);
}
public ValueManifest(@NotNull T defaultValue) {
public ValueManifest(@NotNull TYPE defaultValue) {
this(ValueType.of(defaultValue), () -> defaultValue);
}
public ValueManifest(@NotNull ValueType<T> type, @NotNull Supplier<@Nullable T> defaultSupplier) {
this(type, defaultSupplier, EMPTY_INITIALIZER, null, null);
public ValueManifest(@NotNull ValueType<TYPE> type, @NotNull Supplier<@Nullable TYPE> defaultSupplier) {
this(type, defaultSupplier, ValueValidator.none(), EMPTY_INITIALIZER, null, null);
}
public ValueManifest(@NotNull ValueType<T> type, @NotNull Supplier<@Nullable T> defaultSupplier,
public ValueManifest(@NotNull ValueType<TYPE> type,
@NotNull Supplier<@Nullable TYPE> defaultSupplier,
@NotNull ValueValidator<UNIT> validator) {
this(type, defaultSupplier, validator, EMPTY_INITIALIZER, null, null);
}
public ValueManifest(@NotNull ValueType<TYPE> type, @NotNull Supplier<@Nullable TYPE> defaultSupplier,
@NotNull ValueValidator<UNIT> validator,
@NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> initializer) {
this(type, defaultSupplier, initializer, null, null);
this(type, defaultSupplier, validator, initializer, null, null);
}
public ValueManifest(@NotNull ValueType<T> type, @NotNull Supplier<@Nullable T> defaultSupplier,
public ValueManifest(@NotNull ValueType<TYPE> type,
@NotNull Supplier<@Nullable TYPE> defaultSupplier,
@NotNull ValueValidator<UNIT> validator,
@NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> initializer,
@Nullable ConfigurationHolder<?> holder, @Nullable String path) {
this.type = type;
this.validator = validator;
this.initializer = initializer;
this.defaultSupplier = defaultSupplier;
this.holder = holder;
@@ -49,8 +61,8 @@ public class ValueManifest<T> {
initialize();
}
protected ValueManifest(@NotNull ValueManifest<T> manifest) {
this(manifest.type, manifest.defaultSupplier, manifest.initializer, manifest.holder, manifest.path);
protected ValueManifest(@NotNull ValueManifest<TYPE, UNIT> manifest) {
this(manifest.type, manifest.defaultSupplier, manifest.validator, manifest.initializer, manifest.holder, manifest.path);
}
public void initialize(@NotNull ConfigurationHolder<?> holder, @NotNull String path) {
@@ -63,7 +75,7 @@ public class ValueManifest<T> {
if (holder != null && path != null) this.initializer.accept(holder, path);
}
public @NotNull ValueType<T> type() {
public @NotNull ValueType<TYPE> type() {
return this.type;
}
@@ -75,50 +87,77 @@ public class ValueManifest<T> {
this.path = path;
}
public @Nullable T defaults() {
public @Nullable TYPE defaults() {
return this.defaultSupplier.get();
}
public void defaults(@Nullable T defaultValue) {
public void defaults(@Nullable TYPE defaultValue) {
defaults(() -> defaultValue);
}
public void defaults(@NotNull Supplier<@Nullable T> defaultValue) {
public void defaults(@NotNull Supplier<@Nullable TYPE> defaultValue) {
this.defaultSupplier = defaultValue;
}
public boolean hasDefaults() {
return this.defaultSupplier.get() != null;
return defaults() != null;
}
public @NotNull ValueValidator<UNIT> validator() {
return this.validator;
}
public void validator(@NotNull ValueValidator<UNIT> validator) {
this.validator = validator;
}
public void validate(@NotNull ValueValidator<UNIT> validator) {
validator(this.validator.and(validator));
}
protected UNIT withValidated(@Nullable UNIT value) throws Exception {
validator.validate(holder(), value);
return value;
}
public @NotNull String path() {
if (path != null) return path;
else throw new IllegalStateException("No section path provided.");
else throw new IllegalStateException("No section path provided for Value(" + type() + ").");
}
public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.holder;
throw new IllegalStateException("Value does not have a provider.");
throw new IllegalStateException("Value(" + type() + ") does not have a provider.");
}
public @NotNull ConfigureSource<?, ?, ?> config() {
return holder().config();
}
public ConfigurationMetaHolder metadata() {
public @NotNull ConfigurationMetaHolder metadata() {
return holder().metadata(path());
}
protected Object getData() {
@ApiStatus.Internal
protected @Nullable Object getData() {
return config().get(path());
}
@ApiStatus.Internal
protected void setData(@Nullable Object value) {
config().set(path(), value);
}
protected void throwing(@NotNull Throwable throwable) {
throwing(path, throwable);
}
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
protected void throwing(@NotNull String path, @NotNull Throwable throwable) {
if (holder == null) throwable.printStackTrace();
else holder.throwing(path, throwable);
}
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
};
}
@@ -9,12 +9,12 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class CachedConfigValue<T> extends ConfigValue<T> {
public abstract class CachedConfigValue<T, U> extends ConfigValue<T, U> {
protected @Nullable T cachedValue;
protected long parsedTime = -1;
protected CachedConfigValue(@NotNull ValueManifest<T> manifest) {
protected CachedConfigValue(@NotNull ValueManifest<T, U> manifest) {
super(manifest);
}
@@ -5,6 +5,7 @@ import cc.carm.lib.configuration.adapter.ValueParser;
import cc.carm.lib.configuration.adapter.ValueSerializer;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.list.ConfigListBuilder;
import cc.carm.lib.configuration.builder.list.SourceListBuilder;
import cc.carm.lib.configuration.value.ValueManifest;
import cc.carm.lib.configuration.value.impl.CachedConfigValue;
import org.jetbrains.annotations.NotNull;
@@ -15,7 +16,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements List<V> {
public class ConfiguredList<V> extends CachedConfigValue<List<V>, V> implements List<V> {
public static <T> @NotNull ConfigListBuilder<T> builderOf(@NotNull Class<T> type) {
return builderOf(ValueType.of(type));
@@ -25,10 +26,26 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
return new ConfigListBuilder<>(type);
}
public static <T> @NotNull SourceListBuilder<T, T> with(@NotNull Class<T> registeredType) {
return with(ValueType.of(registeredType));
}
public static <T> @NotNull SourceListBuilder<T, T> with(@NotNull ValueType<T> registeredType) {
return new ConfigListBuilder<>(registeredType).from(registeredType);
}
@SafeVarargs
public static <T> @NotNull ConfiguredList<T> of(@NotNull T value, @NotNull T... values) {
List<T> list = new ArrayList<>();
list.add(value);
Collections.addAll(list, values);
return with(ValueType.of(value)).defaults(list).build();
}
protected final @NotNull Supplier<? extends List<V>> constructor;
protected final @NotNull ValueAdapter<V> paramAdapter;
public ConfiguredList(@NotNull ValueManifest<List<V>> manifest,
public ConfiguredList(@NotNull ValueManifest<List<V>, V> manifest,
@NotNull Supplier<? extends List<V>> constructor,
@NotNull ValueAdapter<V> paramAdapter) {
super(manifest);
@@ -70,40 +87,46 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
if (!cacheExpired()) return getCachedOrDefault(createList());
// Data that is outdated and needs to be parsed again.
List<V> list = createList();
List<?> data = config().contains(path()) ? config().getList(path()) : null;
if (data == null) return getDefaultFirst(list);
try {
List<?> data = config().contains(path()) ? config().getList(path()) : null;
if (data == null) return getDefaultFirst(list);
ValueParser<V> parser = parser();
if (parser == null) return getDefaultFirst(list);
ValueParser<V> parser = parser();
if (parser == null) return getDefaultFirst(list);
for (Object dataVal : data) {
if (dataVal == null) continue;
try {
list.add(parser.parse(holder(), paramType(), dataVal));
} catch (Exception e) {
e.printStackTrace();
int i = 0;
for (Object dataVal : data) {
if (dataVal == null) continue;
try {
list.add(withValidated(parser.parse(holder(), paramType(), dataVal)));
} catch (Exception e) {
throwing(path + "[" + i + "]", e);
}
}
} catch (Exception ex) {
throwing(ex);
}
return updateCache(list);
}
@Override
public void set(@Nullable List<V> value) {
updateCache(value);
if (value == null) {
public void set(@Nullable List<V> list) {
updateCache(list);
if (list == null) {
setData(null);
return;
}
ValueSerializer<V> serializer = serializer();
if (serializer == null) return;
List<Object> data = new ArrayList<>();
for (V val : value) {
for (V val : list) {
if (val == null) continue;
try {
data.add(serializer.serialize(holder(), paramType(), val));
data.add(serializer.serialize(holder(), paramType(), withValidated(val)));
} catch (Exception ex) {
ex.printStackTrace();
throwing(ex);
}
}
setData(data);
@@ -17,7 +17,11 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>> implements Map<K, V> {
public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>, V> implements Map<K, V> {
public static <V> ConfigMapCreator<String, V> builderOf(@NotNull Class<V> valueType) {
return builderOf(String.class, valueType);
}
public static <K, V> ConfigMapCreator<K, V> builderOf(@NotNull ValueType<K> keyType, @NotNull ValueType<V> valueType) {
return new ConfigMapCreator<>(keyType, valueType);
@@ -38,7 +42,7 @@ public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>> implements
protected final @NotNull ValueAdapter<K> keyAdapter;
protected final @NotNull ValueAdapter<V> valueAdapter;
public ConfiguredMap(@NotNull ValueManifest<Map<K, V>> manifest,
public ConfiguredMap(@NotNull ValueManifest<Map<K, V>, V> manifest,
@NotNull Supplier<? extends Map<K, V>> constructor,
@NotNull ValueAdapter<K> keyAdapter, @NotNull ValueAdapter<V> valueAdapter) {
super(manifest);
@@ -73,26 +77,31 @@ public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>> implements
// If the value is expired, we need to update it
Map<K, V> map = createMap();
ConfigureSection section = config().getSection(path());
if (section == null) return getDefaultFirst(map);
try {
ConfigureSection section = config().getSection(path());
if (section == null) return getDefaultFirst(map);
Set<String> keys = section.getKeys(false);
if (keys.isEmpty()) return getDefaultFirst(map);
Set<String> keys = section.getKeys(false);
if (keys.isEmpty()) return getDefaultFirst(map);
ValueParser<K> keyParser = parserFor(keyAdapter);
ValueParser<V> valueParser = parserFor(valueAdapter);
if (keyParser == null || valueParser == null) return getDefaultFirst(map);
ValueParser<K> keyParser = parserFor(keyAdapter);
if (keyParser == null) return getDefaultFirst(map);
ValueParser<V> valueParser = parserFor(valueAdapter);
if (valueParser == null) return getDefaultFirst(map);
for (String dataKey : keys) {
Object dataVal = section.get(dataKey);
if (dataVal == null) continue;
try {
K key = keyParser.parse(holder(), keyType(), dataKey);
V value = valueParser.parse(holder(), valueType(), dataVal);
map.put(key, value);
} catch (Exception e) {
e.printStackTrace();
for (String dataKey : keys) {
Object dataVal = section.get(dataKey);
if (dataVal == null) continue;
try {
K key = keyParser.parse(holder(), keyType(), dataKey);
V value = valueParser.parse(holder(), valueType(), dataVal);
map.put(key, withValidated(value));
} catch (Exception e) {
throwing(path + "." + dataKey, e);
}
}
} catch (Exception ex) {
throwing(ex);
}
return updateCache(map);
@@ -115,23 +124,28 @@ public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>> implements
return;
}
ValueSerializer<K> keySerializer = serializerFor(keyAdapter);
ValueSerializer<V> valueSerializer = serializerFor(valueAdapter);
if (keySerializer == null || valueSerializer == null) return;
try {
ValueSerializer<K> keySerializer = serializerFor(keyAdapter);
if (keySerializer == null) return;
ValueSerializer<V> valueSerializer = serializerFor(valueAdapter);
if (valueSerializer == null) return;
Map<Object, Object> data = new LinkedHashMap<>();
Map<Object, Object> data = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : value.entrySet()) {
try {
data.put(
keySerializer.serialize(holder(), keyType(), entry.getKey()),
valueSerializer.serialize(holder(), valueType(), entry.getValue())
);
} catch (Exception e) {
e.printStackTrace();
for (Map.Entry<K, V> entry : value.entrySet()) {
try {
data.put(
keySerializer.serialize(holder(), keyType(), entry.getKey()),
valueSerializer.serialize(holder(), valueType(), withValidated(entry.getValue()))
);
} catch (Exception e) {
throwing(path + "." + entry.getKey(), e);
}
}
setData(data);
} catch (Exception ex) {
throwing(ex);
}
setData(data);
}
public <T> @NotNull T handle(Function<Map<K, V>, T> function) {
@@ -5,6 +5,8 @@ import cc.carm.lib.configuration.adapter.ValueParser;
import cc.carm.lib.configuration.adapter.ValueSerializer;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.value.ConfigValueBuilder;
import cc.carm.lib.configuration.builder.value.SourceValueBuilder;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.value.ValueManifest;
import cc.carm.lib.configuration.value.impl.CachedConfigValue;
import org.jetbrains.annotations.NotNull;
@@ -12,16 +14,54 @@ import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
public class ConfiguredValue<V> extends CachedConfigValue<V> {
public class ConfiguredValue<V> extends CachedConfigValue<V, V> {
/**
* Create a new value builder.
*
* @param type The type of the value.
* @param <V> The type of the value.
* @return a {@link ConfigValueBuilder} with the specified type.
*/
public static <V> ConfigValueBuilder<V> builderOf(@NotNull Class<V> type) {
return new ConfigValueBuilder<>(ValueType.of(type));
}
/**
* Create a new value builder.
*
* @param type The type of the value.
* @param <V> The type of the value.
* @return a {@link ConfigValueBuilder} with the specified type.
*/
public static <V> ConfigValueBuilder<V> builderOf(@NotNull ValueType<V> type) {
return new ConfigValueBuilder<>(type);
}
/**
* Create a new value builder with the specified {@link ConfigurationHolder#registeredValues()} type.
*
* @param registeredType The type of the value.
* @param <V> The type of the value.
* @return a {@link SourceValueBuilder} with the specified registered type.
* @see ValueAdapter
*/
public static <V> SourceValueBuilder<V, V> with(@NotNull Class<V> registeredType) {
return with(ValueType.of(registeredType));
}
/**
* Create a new value builder with the specified {@link ConfigurationHolder#registeredValues()} type.
*
* @param registeredType The type of the value.
* @param <V> The type of the value.
* @return a {@link SourceValueBuilder} with the specified registered type.
* @see ValueAdapter
*/
public static <V> SourceValueBuilder<V, V> with(@NotNull ValueType<V> registeredType) {
return new ConfigValueBuilder<>(registeredType).from(registeredType);
}
public static <V> ConfiguredValue<V> of(@NotNull V defaults) {
return of(ValueType.of(defaults), () -> defaults);
}
@@ -51,7 +91,7 @@ public class ConfiguredValue<V> extends CachedConfigValue<V> {
);
}
public static <V> ConfiguredValue<V> of(@NotNull ValueManifest<V> manifest,
public static <V> ConfiguredValue<V> of(@NotNull ValueManifest<V, V> manifest,
@Nullable ValueParser<V> parser,
@Nullable ValueSerializer<V> serializer) {
ValueAdapter<V> adapter = new ValueAdapter<>(manifest.type());
@@ -60,13 +100,13 @@ public class ConfiguredValue<V> extends CachedConfigValue<V> {
return of(manifest, adapter);
}
public static <V> ConfiguredValue<V> of(@NotNull ValueManifest<V> manifest, @NotNull ValueAdapter<V> adapter) {
public static <V> ConfiguredValue<V> of(@NotNull ValueManifest<V, V> manifest, @NotNull ValueAdapter<V> adapter) {
return new ConfiguredValue<>(manifest, adapter);
}
protected final @NotNull ValueAdapter<V> adapter;
public ConfiguredValue(@NotNull ValueManifest<V> manifest, @NotNull ValueAdapter<V> adapter) {
public ConfiguredValue(@NotNull ValueManifest<V, V> manifest, @NotNull ValueAdapter<V> adapter) {
super(manifest);
this.adapter = adapter;
}
@@ -97,18 +137,19 @@ public class ConfiguredValue<V> extends CachedConfigValue<V> {
if (!cacheExpired()) return getCachedOrDefault();
// Data that is outdated and needs to be parsed again.
Object data = getData();
if (data == null) return defaults();
ValueParser<V> parser = parser();
if (parser == null) return defaults(); // No parser, return default value.
try {
Object data = getData();
if (data == null) return defaults();
ValueParser<V> parser = parser();
if (parser == null) return defaults(); // No parser, return default value.
// If there are no errors, update the cache and return.
return updateCache(parser.parse(holder(), type(), data));
V parsed = parser.parse(holder(), type(), data);
return updateCache(withValidated(parsed));
} catch (Exception e) {
// There was a parsing error, prompted and returned the default value.
e.printStackTrace();
// There was a validate or parsing error, prompted and returned the default value.
throwing(e);
return defaults();
}
@@ -121,20 +162,20 @@ public class ConfiguredValue<V> extends CachedConfigValue<V> {
* @param value The value to be set
*/
@Override
public void set(V value) {
public void set(@Nullable V value) {
updateCache(value); // Update cache
if (value == null) {
setData(null);
return;
}
ValueSerializer<V> serializer = serializer();
if (serializer == null) return; // No serializer, do nothing.
try {
setData(serializer.serialize(holder(), type(), value));
ValueSerializer<V> serializer = serializer();
if (serializer == null) return; // No serializer, do nothing.
setData(serializer.serialize(holder(), type(), withValidated(value)));
} catch (Exception e) {
e.printStackTrace();
throwing(e);
}
}
+20 -5
View File
@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<artifactId>configured-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.2</version>
<version>4.1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
@@ -13,22 +13,37 @@
<maven.compiler.target>${project.jdk.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-demo</artifactId>
<artifactId>configured-demo</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<artifactId>configured-feature-commentable</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>configured-feature-versioned</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>configured-feature-validators</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
@@ -3,11 +3,10 @@ package cc.carm.lib.configuration.demo;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration {
public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver")
@HeaderComments({
@@ -16,18 +15,19 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver",
})
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
ConfiguredValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver"
);
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
ConfiguredValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
ConfiguredValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
ConfiguredValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
ConfiguredValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static String buildJDBC() {
ConfiguredValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
static String buildJDBC() {
return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get());
}
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST);
System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after);
TEST.INSTANCE.STATUS.set(after);
System.out.println("> Test Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
}
@@ -1,14 +1,9 @@
package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.annotation.*;
import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredList;
import cc.carm.lib.configuration.value.standard.ConfiguredMap;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@@ -24,24 +19,27 @@ import java.util.UUID;
"------------------------------------------------",
"此处内容将显示在配置文件的最下方",
"可用于显示版权信息等",
"感谢您使用 https://github.com/CarmJos/EasyConfiguration !"
"感谢您使用 https://github.com/CarmJos/configured !"
})
public interface DemoConfiguration extends Configuration {
@ConfigPath(root = true)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 1.0D);
@ConfigVersion(2)
ConfiguredValue<Double> VERSION = ConfiguredValue.of(Double.class, 2.0D);
@ConfigPath(root = true)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
ConfigValue<Long> TEST_NUMBER = ConfiguredValue.of(1000000L);
ConfiguredValue<Long> TEST_NUMBER = ConfiguredValue.with(Long.class).defaults(123456789L).build();
@HeaderComments({"枚举类型测试"})
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
ConfiguredValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。
// 注意: 若对应类也有注解,则优先使用类的注解。
Class<?> DATABASE = DatabaseConfiguration.class;
@HeaderComments({"空值测试"})
@InlineComment("空值Inline注释")
ConfiguredMap<String, String> EMPTY = ConfiguredMap.builderOf(String.class, String.class)
.asLinkedMap().fromString()
.build();
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@@ -73,7 +71,7 @@ public interface DemoConfiguration extends Configuration {
class SUB implements Configuration {
@ConfigPath(value = "uuid-value", root = true)
public static final ConfigValue<UUID> UUID_CONFIG_VALUE = ConfiguredValue
public static final ConfiguredValue<UUID> UUID_CONFIG_VALUE = ConfiguredValue
.builderOf(UUID.class).fromString()
.parse((holder, data) -> UUID.fromString(data))
.build();
@@ -2,12 +2,13 @@ package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.annotation.ValueRange;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test")
@HeaderComments("service")
public class InstanceConfig implements Configuration {
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(1.0D);
@ValueRange(min = 0, max = 100, message = "The value must be between 0 and 100")
public final ConfiguredValue<Double> STATUS = ConfiguredValue.of(1.0D);
}
@@ -6,7 +6,6 @@ import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import java.util.UUID;
@@ -20,7 +19,7 @@ public class RegistryConfig implements Configuration {
@FooterComments({"12313213212"})
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。
public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection()
public final ConfiguredValue<UserRecord> OWNER = ConfiguredValue.builderOf(UserRecord.class).fromSection()
.defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build();
@@ -36,6 +36,7 @@ public class UserRecord extends AbstractRecord {
}
public static UserRecord deserialize(ConfigureSection section) {
System.out.println("> Deserializing -> " + section.fullPath());
String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid");
+4 -4
View File
@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.2</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -16,13 +16,13 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<artifactId>configured-feature-commentable</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
@@ -49,6 +49,17 @@ public interface CommentableMeta {
FooterComments.class, FOOTER,
a -> Arrays.asList(a.value())
);
initializer.registerAnnotation(InlineComment.class, INLINE, a -> {
Map<String, String> map = new HashMap<>();
if (a.regex().length == 0) { // for current path
map.put(null, a.value());
return map;
}
for (String regex : a.regex()) { // for specified path
map.put(regex, a.value());
}
return map;
});
initializer.registerAnnotation(InlineComments.class, INLINE, a -> {
Map<String, String> map = new HashMap<>();
for (InlineComment comment : a.value()) {
+4 -4
View File
@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.2</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -16,13 +16,13 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-file</artifactId>
<artifactId>configured-feature-file</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
+4 -4
View File
@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.2</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -16,13 +16,13 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-section</artifactId>
<artifactId>configured-feature-section</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
@@ -0,0 +1,189 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implements ConfigureSection {
protected final @NotNull Map<String, Object> data;
protected final @Nullable R parent;
protected final @NotNull String path;
protected AbstractMapSection(@Nullable R parent, @NotNull String path) {
this.parent = parent;
this.path = path;
this.data = new LinkedHashMap<>();
}
public void migrate(Map<?, ?> data) {
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, createSection(key, (Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>();
int index = 0;
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) {
list.add(createSection(key + "[" + index + "]", (Map<?, ?>) obj));
} else {
list.add(obj);
}
index++;
}
this.data.put(key, list);
} else {
this.data.put(key, entry.getValue());
}
}
}
public abstract @NotNull R self();
@Override
public abstract @NotNull R createSection(@NotNull String path, @NotNull Map<?, ?> data);
@Override
public @NotNull String path() {
return this.path;
}
@Override
public boolean contains(@NotNull String path) {
R section = getSectionFor(path);
if (section == this) {
return this.data().containsKey(path);
} else {
return section.contains(childPath(path));
}
}
public @NotNull Map<String, Object> data() {
return this.data;
}
@Override
public boolean isEmpty() {
return this.data.isEmpty();
}
@Override
public int size(boolean deep) {
return deep ? getKeys(true).size() : this.data.size();
}
@Override
@UnmodifiableView
public @NotNull Map<String, Object> asMap() {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : this.data.entrySet()) {
if (entry.getValue() instanceof AbstractMapSection<?>) {
output.put(entry.getKey(), ((AbstractMapSection<?>) entry.getValue()).asMap());
} else if (entry.getValue() instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof AbstractMapSection<?>) {
list.add(((AbstractMapSection<?>) obj).asMap());
} else {
list.add(obj);
}
}
output.put(entry.getKey(), list);
} else {
output.put(entry.getKey(), entry.getValue());
}
}
return output;
}
public @Nullable R parent() {
return this.parent;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mappingValues(this, null, true, String.valueOf(pathSeparator())) : data());
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return Collections.unmodifiableSet(deep ? mappingKeys(this, null, true, String.valueOf(pathSeparator())) : data().keySet());
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createSection(path, (Map<?, ?>) value);
R section = getSectionFor(path);
if (section == this) {
// Even this value is null, we still need to put it in the map
// to ensure that the path is marked as existing.
this.data.put(path, value);
} else {
section.set(childPath(path), value);
}
}
@Override
public void remove(@NotNull String path) {
R section = getSectionFor(path);
if (section != this) {
section.remove(childPath(path));
} else {
this.data.remove(path);
}
}
@Override
public @Nullable Object get(@NotNull String path) {
R section = getSectionFor(path);
return section == this ? data.get(path) : section.get(childPath(path));
}
@SuppressWarnings("unchecked")
private R getSectionFor(String path) {
int index = path.indexOf(pathSeparator());
if (index == -1) return self();
String root = path.substring(0, index);
return (R) computeSection(root);
}
/**
* Map the values of the children of the section to the output map.
*
* @param section The section to map the values from
* @param parent The parent path
* @param deep If the mapping should be deep
*/
protected static Map<String, Object> mappingValues(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
output.putAll(mappingValues((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
}
}
return output;
}
protected static Set<String> mappingKeys(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Set<String> keys = new LinkedHashSet<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
keys.add(path);
if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
keys.addAll(mappingKeys((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
}
}
return keys;
}
}
@@ -0,0 +1,383 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class ImmutableSection implements ConfigureSection {
public static ImmutableSection of(@NotNull ConfigureSection section) {
if (section instanceof ImmutableSection) {
return (ImmutableSection) section;
} else return new ImmutableSection(null, section);
}
protected final @Nullable ImmutableSection parent;
protected final @NotNull ConfigureSection raw;
private ImmutableSection(@Nullable ImmutableSection parent, @NotNull ConfigureSection raw) {
this.parent = parent;
this.raw = raw;
}
private @NotNull ConfigureSection raw() {
return raw;
}
@Override
public @Nullable ImmutableSection parent() {
return this.parent;
}
@Override
public @NotNull String path() {
return raw().path();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
return raw().getValues(deep);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
throw new IllegalStateException("This section is not modifiable!");
}
@Override
public void remove(@NotNull String path) {
throw new IllegalStateException("This section is not modifiable!");
}
@Override
public @NotNull ImmutableSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new ImmutableSection(this, raw().createSection(path, data));
}
@Override
public @Nullable Object get(@NotNull String path) {
Object value = raw().get(path);
if (value instanceof ConfigureSection && !(value instanceof ImmutableSection)) {
return new ImmutableSection(this, (ConfigureSection) value);
}
return value;
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
ConfigureSection get = raw().getSection(path);
if (get != null && !(get instanceof ImmutableSection)) {
return new ImmutableSection(this, get);
}
return get;
}
@Override
public char pathSeparator() {
return raw().pathSeparator();
}
@Override
public boolean isRoot() {
return raw().isRoot();
}
@Override
public boolean isEmpty() {
return raw().isEmpty();
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return raw().getKeys(deep);
}
@Override
public @NotNull @UnmodifiableView Set<String> keys() {
return raw().keys();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> values() {
return raw().values();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> asMap() {
return raw().asMap();
}
@Override
public Stream<Map.Entry<String, Object>> stream() {
return raw().stream();
}
@Override
public void forEach(@NotNull BiConsumer<String, Object> action) {
raw().forEach(action);
}
@Override
public boolean contains(@NotNull String path) {
return raw().contains(path);
}
@Override
public boolean containsValue(@NotNull String path) {
return raw().containsValue(path);
}
@Override
public <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) {
return raw().isType(path, typeClass);
}
@Override
public boolean isList(@NotNull String path) {
return raw().isList(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return raw().getList(path);
}
@Override
public boolean isSection(@NotNull String path) {
return raw().isSection(path);
}
@Override
public <T> @Nullable T get(@NotNull String path, @NotNull Class<T> type) {
return raw().get(path, type);
}
@Override
public <T> @Nullable T get(@NotNull String path, @NotNull DataFunction<@Nullable Object, T> parser) {
return raw().get(path, parser);
}
@Override
public <T> @Nullable T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> clazz) {
return raw().get(path, defaults, clazz);
}
@Override
public <T> @Nullable T get(@NotNull String path, @Nullable T defaultValue, @NotNull DataFunction<Object, T> parser) {
return raw().get(path, defaultValue, parser);
}
@Override
public boolean isBoolean(@NotNull String path) {
return raw().isBoolean(path);
}
@Override
public boolean getBoolean(@NotNull String path) {
return raw().getBoolean(path);
}
@Override
public @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
return raw().getBoolean(path, def);
}
@Override
public @Nullable Boolean isByte(@NotNull String path) {
return raw().isByte(path);
}
@Override
public @Nullable Byte getByte(@NotNull String path) {
return raw().getByte(path);
}
@Override
public @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
return raw().getByte(path, def);
}
@Override
public boolean isShort(@NotNull String path) {
return raw().isShort(path);
}
@Override
public @Nullable Short getShort(@NotNull String path) {
return raw().getShort(path);
}
@Override
public @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
return raw().getShort(path, def);
}
@Override
public boolean isInt(@NotNull String path) {
return raw().isInt(path);
}
@Override
public @Nullable Integer getInt(@NotNull String path) {
return raw().getInt(path);
}
@Override
public @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
return raw().getInt(path, def);
}
@Override
public boolean isLong(@NotNull String path) {
return raw().isLong(path);
}
@Override
public @Nullable Long getLong(@NotNull String path) {
return raw().getLong(path);
}
@Override
public @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
return raw().getLong(path, def);
}
@Override
public boolean isFloat(@NotNull String path) {
return raw().isFloat(path);
}
@Override
public @Nullable Float getFloat(@NotNull String path) {
return raw().getFloat(path);
}
@Override
public @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
return raw().getFloat(path, def);
}
@Override
public boolean isDouble(@NotNull String path) {
return raw().isDouble(path);
}
@Override
public @Nullable Double getDouble(@NotNull String path) {
return raw().getDouble(path);
}
@Override
public @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
return raw().getDouble(path, def);
}
@Override
public boolean isChar(@NotNull String path) {
return raw().isChar(path);
}
@Override
public @Nullable Character getChar(@NotNull String path) {
return raw().getChar(path);
}
@Override
public @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
return raw().getChar(path, def);
}
@Override
public boolean isString(@NotNull String path) {
return raw().isString(path);
}
@Override
public @Nullable String getString(@NotNull String path) {
return raw().getString(path);
}
@Override
public @Nullable String getString(@NotNull String path, @Nullable String def) {
return raw().getString(path, def);
}
@Override
public @NotNull <V> List<V> getList(@NotNull String path, @NotNull DataFunction<Object, V> parser) {
return raw().getList(path, parser);
}
@Override
public @NotNull List<String> getStringList(@NotNull String path) {
return raw().getStringList(path);
}
@Override
public @NotNull List<Integer> getIntegerList(@NotNull String path) {
return raw().getIntegerList(path);
}
@Override
public @NotNull List<Long> getLongList(@NotNull String path) {
return raw().getLongList(path);
}
@Override
public @NotNull List<Double> getDoubleList(@NotNull String path) {
return raw().getDoubleList(path);
}
@Override
public @NotNull List<Float> getFloatList(@NotNull String path) {
return raw().getFloatList(path);
}
@Override
public @NotNull List<Byte> getByteList(@NotNull String path) {
return raw().getByteList(path);
}
@Override
public @NotNull List<Character> getCharList(@NotNull String path) {
return raw().getCharList(path);
}
@Override
public <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path, @NotNull Supplier<C> constructor, @NotNull DataFunction<Object, T> parser) {
return raw().getCollection(path, constructor, parser);
}
@Override
public @NotNull Stream<?> stream(@NotNull String path) {
return raw().stream(path);
}
@Override
public @NotNull <T> Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) {
return raw().stream(path, parser);
}
@Override
public String childPath(String path) {
return raw().childPath(path);
}
@Override
public int hashCode() {
return raw.hashCode();
}
@Override
public boolean equals(Object obj) {
return Objects.equals(raw, obj);
}
}
@@ -3,161 +3,54 @@ package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MemorySection implements ConfigureSection {
public class MemorySection extends AbstractMapSection<MemorySection> {
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) {
return new MemorySection(source, new LinkedHashMap<>(), null);
public static MemorySection of() {
return of(new LinkedHashMap<>());
}
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@Nullable Map<?, ?> data) {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null);
public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) {
return of(() -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source;
protected final @NotNull Map<String, Object> data;
protected final @Nullable MemorySection parent;
public MemorySection(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@NotNull Map<?, ?> data, @Nullable MemorySection parent) {
this.source = source;
this.parent = parent;
this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, createChild((Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) {
list.add(createChild((Map<?, ?>) obj));
} else {
list.add(obj);
}
}
this.data.put(key, list);
} else {
this.data.put(key, entry.getValue());
}
}
public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) {
return of(data.get());
}
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this);
public static MemorySection of(@NotNull Map<?, ?> data) {
return of(data, null, "");
}
protected @NotNull MemorySection createChild() {
return createChild(new LinkedHashMap<>());
public static MemorySection of(@Nullable MemorySection parent, @NotNull String path) {
return of(new LinkedHashMap<>(), parent, path);
}
public @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() {
return this.source;
public static MemorySection of(@NotNull Map<?, ?> data, @Nullable MemorySection parent, @NotNull String path) {
return new MemorySection(data, parent, path);
}
public @NotNull Map<String, Object> data() {
return this.data;
}
public @Nullable MemorySection parent() {
return this.parent;
}
public char pathSeparator() {
return source.pathSeparator();
public MemorySection(@NotNull Map<?, ?> raw, @Nullable MemorySection parent, @NotNull String path) {
super(parent, path);
migrate(raw);
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mapChildrenValues(this, null, true) : data());
public @NotNull MemorySection self() {
return this;
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createChild((Map<?, ?>) value);
MemorySection section = getSectionFor(path);
if (section == this) {
// Even this value is null, we still need to put it in the map
// to ensure that the path is marked as existing.
this.data.put(path, value);
} else {
section.set(childPath(path), value);
}
public @NotNull MemorySection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new MemorySection(data, this, path);
}
@Override
public void remove(@NotNull String path) {
MemorySection section = getSectionFor(path);
if (section != this) {
section.remove(childPath(path));
} else {
this.data.remove(path);
}
}
@Override
public boolean contains(@NotNull String path) {
return get(path) != null;
}
@Override
public @Nullable Object get(@NotNull String path) {
MemorySection section = getSectionFor(path);
return section == this ? data.get(path) : section.get(childPath(path));
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
Object val = get(path);
return (val instanceof List<?>) ? (List<?>) val : null;
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
private MemorySection getSectionFor(String path) {
int index = path.indexOf(pathSeparator());
if (index == -1) return this;
String root = path.substring(0, index);
Object section = this.data.get(root);
if (section == null) {
section = createChild();
this.data.put(root, section);
}
return (MemorySection) section;
}
private String childPath(String path) {
int index = path.indexOf(pathSeparator());
return (index == -1) ? path : path.substring(index + 1);
}
/**
* Map the values of the children of the section to the output map.
*
* @param section The section to map the values from
* @param parent The parent path
* @param deep If the mapping should be deep
*/
protected Map<String, Object> mapChildrenValues(@NotNull MemorySection section,
@Nullable String parent, boolean deep) {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator()) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof MemorySection) {
output.putAll(mapChildrenValues((MemorySection) entry.getValue(), path, true));
}
}
return output;
}
}
@@ -0,0 +1,151 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.*;
public class ShadedSection implements ConfigureSection {
public static ShadedSection create(@NotNull ConfigureSection template, @Nullable ConfigureSection source) {
return new ShadedSection(null, template, source);
}
protected final @Nullable ShadedSection parent;
protected @NotNull ConfigureSection template;
protected @Nullable ConfigureSection source;
public ShadedSection(@Nullable ShadedSection parent,
@NotNull ConfigureSection template, @Nullable ConfigureSection source) {
this.parent = parent;
this.template = template;
this.source = source;
}
@Override
public @Nullable ConfigureSection parent() {
return this.parent;
}
@Override
public @NotNull String path() {
return this.template.path();
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
if (source == null) return template.getValues(deep);
// 本函数为,当 getValues 时,递归合并 source 和 template
return merge(template, source).getValues(deep);
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> asMap() {
if (source == null) return template.asMap();
return merge(template, source).asMap();
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
Set<String> keys = new HashSet<>(template.getKeys(deep));
if (source != null) {
keys.addAll(source.getKeys(deep));
}
return Collections.unmodifiableSet(keys);
}
private ConfigureSection merge(ConfigureSection templateSection, ConfigureSection valueSection) {
MemorySection merged = MemorySection.of();
Set<String> existingKey = new HashSet<>();
for (Map.Entry<String, Object> entry : valueSection.getValues(false).entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof ConfigureSection) {
ConfigureSection subSectionFromValue = (ConfigureSection) value;
ConfigureSection subSectionFromTemplate = (ConfigureSection) templateSection.get(key);
if (subSectionFromTemplate == null) {
merged.set(key, value);
} else {
merged.set(key, merge(subSectionFromTemplate, subSectionFromValue));
}
} else {
merged.set(key, value);
}
existingKey.add(key);
}
for (Map.Entry<String, Object> entry : templateSection.getValues(false).entrySet()) {
if (existingKey.contains(entry.getKey())) continue;
merged.set(entry.getKey(), entry.getValue());
}
return merged;
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof ConfigureSection) {
ConfigureSection targetSection = (ConfigureSection) value;
for (Map.Entry<String, Object> entry : targetSection.getValues(true).entrySet()) {
set(path + pathSeparator() + entry.getKey(), entry.getValue());
}
return;
} else if (Objects.equals(get(path), value)) {
remove(path);
return;
}
Optional.ofNullable(source).ifPresent(s -> s.set(path, value));
}
@Override
public void remove(@NotNull String path) {
Optional.ofNullable(source).ifPresent(s -> s.remove(path));
}
@Override
public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
if (source == null) {
return new ShadedSection(this, template, MemorySection.of(data));
} else {
ConfigureSection section = source.computeSection(path, data);
return new ShadedSection(this, template, section);
}
}
@Override
public @Nullable Object get(@NotNull String path) {
if (source == null) {
return getFromTemplate(path);
}
Object value = source.get(path);
if (value == null) {
return getFromTemplate(path);
}
if (value instanceof ConfigureSection) {
ConfigureSection templateSection = (ConfigureSection) template.get(path);
if (templateSection == null) {
return value;
} else {
return new ShadedSection(this, templateSection, (ConfigureSection) value);
}
}
return value;
}
public @Nullable Object getFromTemplate(@NotNull String path) {
Object value = template.get(path);
if (value instanceof ConfigureSection) {
return new ShadedSection(this, (ConfigureSection) value, null);
} else {
return value;
}
}
public @Nullable ConfigureSection getSource() {
return source;
}
}
@@ -0,0 +1,48 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
public class SourcedSection extends AbstractMapSection<SourcedSection> {
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source) {
return new SourcedSection(source, new LinkedHashMap<>(), null, "");
}
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@Nullable Map<?, ?> raw) {
return new SourcedSection(source, raw == null ? new LinkedHashMap<>() : raw, null, "");
}
protected final @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source;
public SourcedSection(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@NotNull Map<?, ?> raw, @Nullable SourcedSection parent, @NotNull String path) {
super(parent, path);
this.source = source;
migrate(raw);
}
public @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source() {
return source;
}
@Override
public char pathSeparator() {
return source().pathSeparator();
}
@Override
public @NotNull SourcedSection self() {
return this;
}
@Override
public @NotNull SourcedSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new SourcedSection(source(), data, this, path);
}
}
@@ -0,0 +1,77 @@
package test.section;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.configuration.source.section.ShadedSection;
import org.junit.Test;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Lyzen
* @date 21/2/2025 下午2:18
*/
public class ShadeTest {
@Test
public void test() {
ConfigureSection template = MemorySection.of(data -> {
data.put("name", "GentleMan");
data.put("age", 12);
data.put("gender", "male");
Map<String, Object> address = new LinkedHashMap<>();
address.put("Hotel", "Nanjing Road 101");
address.put("Store", "Beijing Road 404");
Map<String, Object> mapInside = new LinkedHashMap<>();
mapInside.put("InsideKeyExample", "InsideValueExample");
address.put("Inside", mapInside);
data.put("addresses", address);
data.put("cards", Arrays.asList("00000", "11111", "22222"));
});
ConfigureSection source = MemorySection.of(data -> {
data.put("age", 25);
Map<String, Object> address = new LinkedHashMap<>();
address.put("NewOne", "Guangdong Road 505");
data.put("addresses", address);
Map<String, Object> mapInside = new LinkedHashMap<>();
mapInside.put("AnotherInsideKey", "AnotherInsideValue");
address.put("Inside", mapInside);
data.put("cards", Arrays.asList("33333", "55555")); // 应当直接覆盖原先的List
});
ShadedSection root = new ShadedSection(null, template, source);
System.out.println("age: " + root.get("age"));
System.out.println("addresses: ");
for (Map.Entry<String, Object> entry : root.getSection("addresses").getValues(false).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
if (entry.getValue() instanceof ConfigureSection) {
for (Map.Entry<String, Object> inner : ((ConfigureSection) entry.getValue()).getValues(false).entrySet()) {
System.out.println(" " + inner.getKey() + ": " + inner.getValue());
}
}
}
System.out.println("cards: " + root.getList("cards"));
System.out.println("\n----------------------\n");
System.out.println("Deep Search Test");
System.out.println("addresses: ");
for (Map.Entry<String, Object> entry : root.getSection("addresses").getValues(true).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
System.out.println("\n----------------------\n");
root.set("addresses", MemorySection.of(map -> {
map.put("Hotel", "Nanjing Road 101");
map.put("Store", "Beijing Road banned");
}));
for (Map.Entry<String, Object> entry : source.getValues(true).entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
}
+5 -5
View File
@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.2</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -16,20 +16,20 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-text</artifactId>
<artifactId>configured-feature-text</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-yaml</artifactId>
<artifactId>configured-gson</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
@@ -22,7 +22,7 @@ import java.util.function.Consumer;
*/
public class ConfiguredText<MSG, RECEIVER> extends ConfiguredValue<TextContents> {
public static <M, R> Builder<M, R, ?> builder() {
public static <M, R> Builder<M, R, ?> create() {
return new StardardBuilder<>();
}
@@ -38,7 +38,7 @@ public class ConfiguredText<MSG, RECEIVER> extends ConfiguredValue<TextContents>
protected final @NotNull String[] params; // The parameters of the message.
public ConfiguredText(@NotNull ValueManifest<TextContents> manifest,
public ConfiguredText(@NotNull ValueManifest<TextContents, TextContents> manifest,
@NotNull BiFunction<RECEIVER, String, String> parser,
@NotNull BiFunction<RECEIVER, String, MSG> compiler,
@NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher,
@@ -121,7 +121,7 @@ public class ConfiguredText<MSG, RECEIVER> extends ConfiguredValue<TextContents>
}
public abstract static class Builder<MSG, RECEIVER, SELF extends Builder<MSG, RECEIVER, SELF>>
extends AbstractConfigBuilder<TextContents, ConfiguredText<MSG, RECEIVER>, ConfigurationHolder<?>, SELF> {
extends AbstractConfigBuilder<TextContents, TextContents, ConfiguredText<MSG, RECEIVER>, ConfigurationHolder<?>, SELF> {
protected @NotNull TextContents.Builder defaultBuilder = TextContents.builder();
protected @NotNull String[] params = new String[0];
@@ -16,11 +16,12 @@ import java.util.stream.IntStream;
public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEIVER, SELF>> {
/**
* Used to match the message insertion
* Used to match the message insertion.
* <p>
* format:
* <br>- to insert parsed line {prefix}#content-id#{offset-above,offset-down}
* <br>- to insert original line {prefix}@content-id@{offset-above,offset-down}
* <br> original lines will not be parsed
* <br> example:
* <ul>
* <li>{- }#content-id#{1,1}</li>
@@ -30,6 +31,21 @@ public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEI
public static final @NotNull Pattern INSERT_PATTERN = Pattern.compile(
"^(?:\\{(?<prefix>.*)})?(?<type>[#@])(?<id>.*)[#@](?:\\{(?<above>-?\\d+)(?:,(?<down>-?\\d+))?})?$"
);
/**
* Used to match the message which can be inserted
* <p>
* format:
* <br>- ?[id]Message content
* <br> example:
* <ul>
* <li>?[click]Click to use this item!</li>
* </ul>
*/
public static final @NotNull Pattern ENABLE_PATTERN = Pattern.compile(
"^\\?\\[(?<id>.+)](?<content>.*)$"
);
public static final @NotNull UnaryOperator<String> DEFAULT_PARAM_BUILDER = s -> "%(" + s + ")";
protected BiFunction<RECEIVER, String, String> parser = (receiver, value) -> value;
@@ -45,7 +61,7 @@ public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEI
/**
* Used to store the insertion of the message
*/
protected @NotNull Map<String, Function<RECEIVER, List<String>>> insertion = new HashMap<>();
protected @NotNull Map<String, @Nullable Function<RECEIVER, List<String>>> insertion = new HashMap<>();
protected boolean disableInsertion = false;
public abstract SELF self();
@@ -139,6 +155,17 @@ public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEI
return self();
}
/**
* Insert the specific contents by the id.
*
* @param id the id of the insertion text
* @return the current {@link ContentHandler} instance
*/
public SELF insert(@NotNull String id) {
this.insertion.put(id, null);
return self();
}
/**
* Insert the specific contents by the id.
*
@@ -205,37 +232,50 @@ public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEI
}
for (String line : contents.lines()) {
Matcher matcher = INSERT_PATTERN.matcher(line);
if (!matcher.matches()) {
lineConsumer.accept(parse(receiver, line));
Matcher insertMatcher = INSERT_PATTERN.matcher(line);
if (insertMatcher.matches()) {
doInsert(insertMatcher, receiver, lineConsumer);
continue;
}
String id = matcher.group("id");
List<String> values = Optional.ofNullable(this.insertion.get(id))
.map(f -> f.apply(receiver))
.orElse(null);
if (values == null || values.isEmpty()) continue;
String prefix = matcher.group("prefix");
String type = matcher.group("type");
boolean original = type.equals("@");
int offsetAbove = Optional.ofNullable(matcher.group("above"))
.map(Integer::parseInt).orElse(0);
int offsetDown = Optional.ofNullable(matcher.group("down"))
.map(Integer::parseInt).orElse(offsetAbove); // If offsetDown is not set, use offsetAbove
IntStream.range(0, Math.max(0, offsetAbove)).mapToObj(i -> "").forEach(lineConsumer);
String prefixContent = Optional.ofNullable(prefix).map(p -> parse(receiver, p)).orElse("");
if (original) {
values.stream().map(value -> prefixContent + value).forEach(lineConsumer);
} else {
values.stream().map(value -> prefixContent + parse(receiver, value)).forEach(lineConsumer);
Matcher enableMatcher = ENABLE_PATTERN.matcher(line);
if (enableMatcher.matches()) {
if (this.insertion.containsKey(enableMatcher.group("id"))) {
lineConsumer.accept(parse(receiver, enableMatcher.group("content")));
}
continue;
}
IntStream.range(0, Math.max(0, offsetDown)).mapToObj(i -> "").forEach(lineConsumer);
lineConsumer.accept(parse(receiver, line));
}
}
private void doInsert(Matcher matcher, @Nullable RECEIVER receiver,
@NotNull Consumer<String> lineConsumer) {
String id = matcher.group("id");
List<String> values = Optional.ofNullable(this.insertion.get(id))
.map(f -> f.apply(receiver))
.orElse(null);
if (values == null || values.isEmpty()) return; // No values to insert
String prefix = matcher.group("prefix");
String type = matcher.group("type");
boolean original = type.equals("@");
int offsetAbove = Optional.ofNullable(matcher.group("above"))
.map(Integer::parseInt).orElse(0);
int offsetDown = Optional.ofNullable(matcher.group("down"))
.map(Integer::parseInt).orElse(offsetAbove); // If offsetDown is not set, use offsetAbove
IntStream.range(0, Math.max(0, offsetAbove)).mapToObj(i -> "").forEach(lineConsumer);
String prefixContent = Optional.ofNullable(prefix).map(p -> parse(receiver, p)).orElse("");
if (original) {
values.stream().map(value -> prefixContent + value).forEach(lineConsumer);
} else {
values.stream().map(value -> prefixContent + parse(receiver, value)).forEach(lineConsumer);
}
IntStream.range(0, Math.max(0, offsetDown)).mapToObj(i -> "").forEach(lineConsumer);
}
public static String setPlaceholders(@NotNull String messages,
@NotNull Map<String, Object> placeholders) {
if (messages.isEmpty()) return messages;
@@ -2,10 +2,12 @@ package cc.carm.lib.configuration.value.text.tests;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.value.text.tests.conf.AppMessages;
import org.junit.Test;
import java.io.File;
public class ConfigTest {
@@ -19,7 +21,7 @@ public class ConfigTest {
@Test
public void test() {
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/messages.yml").build();
ConfigurationHolder<?> holder = JSONConfigFactory.from(new File("target/messages.json")).build();
holder.initialize(AppMessages.class);
@@ -20,7 +20,9 @@ public class ParseTest {
lines.add("#guidance#");
lines.add("{- }#websites#{0,1}");
lines.add("Thanks for your reading!");
lines.add("?[click]");
lines.add("?[click]Click to see more!");
lines.add("?[hidden]This entry should be hidden!");
Map<String, List<String>> optional = new HashMap<>();
optional.put("guidance", Arrays.asList("To get more information for %(name), see:"));
@@ -35,6 +37,7 @@ public class ParseTest {
msg.placeholder("name", "Carm")
.insert("guidance")
.insert("click")
.insert("websites", "Baidu", "Bilibili", "Google");
System.out.println("----------------------------");
@@ -17,7 +17,8 @@ public class ConfiguredMsg extends ConfiguredText<String, PrintStream> {
return builder().defaults(text).build();
}
public ConfiguredMsg(@NotNull ValueManifest<TextContents> manifest, @NotNull String[] params) {
public ConfiguredMsg(@NotNull ValueManifest<TextContents, TextContents> manifest,
@NotNull String[] params) {
super(
manifest,
(p, s) -> s,
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
<maven.compiler.source>${project.jdk.version}</maven.compiler.source>
<maven.compiler.target>${project.jdk.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>configured-feature-validators</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,16 @@
package cc.carm.lib.configuration.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValuePattern {
String value();
String message() default "Invalid value format!";
}
@@ -0,0 +1,18 @@
package cc.carm.lib.configuration.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValueRange {
long min() default Long.MIN_VALUE;
long max() default Long.MAX_VALUE;
String message() default "Value out of range.";
}
@@ -0,0 +1,44 @@
package cc.carm.lib.configuration.validators;
import cc.carm.lib.configuration.annotation.ValuePattern;
import cc.carm.lib.configuration.annotation.ValueRange;
import cc.carm.lib.configuration.function.ValueValidator;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Pattern;
public class Validators {
private Validators() {
throw new UnsupportedOperationException("API Register.");
}
public static void activate(ConfigurationHolder<?> holder) {
holder.initializer().registerValidAnnotation(ValueRange.class, r -> (ho, value) -> {
if (!(value instanceof Number)) {
throw new IllegalArgumentException("Value is not a number: " + value);
}
Number number = (Number) value;
if (number.doubleValue() < r.min() || number.doubleValue() > r.max()) {
throw new IllegalArgumentException(r.message());
}
});
holder.initializer().registerValidAnnotation(ValuePattern.class, r -> new ValueValidator<Object>() {
private final Pattern pattern = Pattern.compile(r.value());
@Override
public void validate(@NotNull ConfigurationHolder<?> holder, @Nullable Object value) throws Exception {
if (value == null) return;
if (!pattern.matcher(value.toString()).matches()) {
throw new IllegalArgumentException(r.message());
}
}
});
}
}
+4 -4
View File
@@ -5,8 +5,8 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.2</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -16,13 +16,13 @@
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<artifactId>configured-feature-versioned</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
*
* @return the version of the configuration field
*/
@Range(from = 0, to = Integer.MAX_VALUE)
@Range(from = 0, to = 65535)
int value() default 0;
}
@@ -1,13 +0,0 @@
package cc.carm.lib.configuration.option;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface VersionedOptions {
/**
* Whether to set newer defaults when a {@link cc.carm.lib.configuration.value.ConfigValue}'s marked version
* is newer than the current in storage.
*/
ConfigurationOption<Boolean> RESET_NEWER_DEFAULTS = ConfigurationOption.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.commentable;
package cc.carm.lib.configuration.versioned;
import cc.carm.lib.configuration.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
}
static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value);
initializer.appendFieldInitializer((holder, path, field, value) -> {
ConfigVersion annotation = field.getAnnotation(ConfigVersion.class);
if (annotation == null || value == null) return;
int currentVersion = annotation.value();
int savedVersion = holder.metadata(path).get(VERSION, 0);
if (currentVersion == savedVersion) return;
if (currentVersion > savedVersion) { // This values updated.
value.setDefault(true); // Mark as default value, force write.
}
holder.metadata(path).set(VERSION, currentVersion);
});
}
}
+19 -24
View File
@@ -13,29 +13,30 @@
</properties>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<artifactId>configured-parent</artifactId>
<packaging>pom</packaging>
<version>4.0.2</version>
<version>4.1.1</version>
<modules>
<module>core</module>
<module>features/section</module>
<module>features/file</module>
<module>features/commentable</module>
<module>features/versioned</module>
<module>features/validators</module>
<module>features/text</module>
<module>providers/yaml</module>
<module>providers/gson</module>
<!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>-->
<!-- <module>providers/mongodb</module>-->
<module>providers/hocon</module>
<module>providers/sql</module>
<module>providers/mongodb</module>
<module>demo</module>
</modules>
<name>EasyConfiguration</name>
<description>轻松(做)配置,简单便捷的通用配置文件加载、读取与更新工具,可自定义配置格式。</description>
<url>https://github.com/CarmJos/EasyConfiguration</url>
<name>configured</name>
<description>A simple, easy-to-use and universal solution for managing configuration files.</description>
<url>https://github.com/CarmJos/configured</url>
<developers>
<developer>
@@ -48,9 +49,9 @@
</developers>
<scm>
<connection>scm:git:git@github.com:CarmJos/Easy.EasyConfiguration</connection>
<developerConnection>scm:git:git@github.com:CarmJos/EasyConfiguration.git</developerConnection>
<url>https://github.com/CarmJos/EasyConfiguration</url>
<connection>scm:git:git@github.com:CarmJos/Easy.configured</connection>
<developerConnection>scm:git:git@github.com:CarmJos/configured.git</developerConnection>
<url>https://github.com/CarmJos/configured</url>
<tag>HEAD</tag>
</scm>
@@ -63,12 +64,12 @@
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/CarmJos/EasyConfiguration/issues</url>
<url>https://github.com/CarmJos/configured/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub Actions</system>
<url>https://github.com/CarmJos/EasyConfiguration/actions/workflows/maven.yml</url>
<url>https://github.com/CarmJos/configured/actions/workflows/maven.yml</url>
</ciManagement>
<repositories>
@@ -89,20 +90,14 @@
<url>https://repo1.maven.org/maven2/</url>
</repository>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/*</url>
</repository>
</repositories>
<distributionManagement>
<downloadUrl>https://github.com/CarmJos/EasyConfiguration/releases</downloadUrl>
<downloadUrl>https://github.com/CarmJos/configured/releases</downloadUrl>
<site>
<id>javadoc</id>
<name>EasyConfiguration JavaDoc (on GitHub Pages)</name>
<url>https://CarmJos.github.io/EasyConfiguration</url>
<name>configured JavaDoc (on GitHub Pages)</name>
<url>https://CarmJos.github.io/configured</url>
</site>
</distributionManagement>
@@ -202,7 +197,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>3.14.0</version>
<configuration>
<source>${project.jdk.version}</source>
<target>${project.jdk.version}</target>
@@ -298,7 +293,7 @@
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/EasyConfiguration</url>
<url>https://maven.pkg.github.com/CarmJos/configured</url>
</repository>
</distributionManagement>
</profile>
+6 -6
View File
@@ -1,4 +1,4 @@
# EasyConfiguration-JSON
# configured-JSON
JSON file-based implementation, compatible with all Java environments.
@@ -22,9 +22,9 @@ JSON file-based implementation, compatible with all Java environments.
<repository>
<!-- Using GitHub dependencies for real-time updates, configuration required (recommended). -->
<id>EasyConfiguration</id>
<id>configured</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/EasyConfiguration</url>
<url>https://maven.pkg.github.com/CarmJos/configured</url>
</repository>
</repositories>
@@ -37,7 +37,7 @@ JSON file-based implementation, compatible with all Java environments.
<dependencies>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId>
<artifactId>configured-json</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -54,14 +54,14 @@ repositories {
mavenCentral()
// Using GitHub dependencies for real-time updates, configuration required (recommended).
maven { url 'https://maven.pkg.github.com/CarmJos/EasyConfiguration' }
maven { url 'https://maven.pkg.github.com/CarmJos/configured' }
}
```
```groovy
dependencies {
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]"
api "cc.carm.lib:configured-json:[LATEST RELEASE]"
}
```
+7 -7
View File
@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<artifactId>configured-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.2</version>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,35 +15,35 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-gson</artifactId>
<artifactId>configured-gson</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId>
<artifactId>configured-feature-file</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-section</artifactId>
<artifactId>configured-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<artifactId>configured-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
@@ -2,7 +2,7 @@ package cc.carm.lib.configuration.source.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
@@ -14,17 +14,17 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class JSONSource extends FileConfigSource<MemorySection, Map<String, Object>, JSONSource> {
public class JSONSource extends FileConfigSource<SourcedSection, Map<String, Object>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
MemorySection.class,
(JsonSerializer<MemorySection>) (src, t, c) -> c.serialize(src.data())
SourcedSection.class,
(JsonSerializer<SourcedSection>) (src, t, c) -> c.serialize(src.data())
).create();
protected final @NotNull Gson gson;
protected @Nullable MemorySection rootSection;
protected @Nullable SourcedSection rootSection;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath) {
@@ -58,7 +58,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
}
@Override
public @NotNull MemorySection section() {
public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized");
}
@@ -70,7 +70,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override
protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class));
this.rootSection = MemorySection.root(this, data);
this.rootSection = SourcedSection.root(this, data);
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
}
@@ -1,10 +1,8 @@
package config;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import org.junit.Test;
import java.io.File;
@@ -18,12 +16,6 @@ public class JSONConfigTest {
@Test
public void onTest() {
ConfigValue<Boolean> EXAMPLE = ConfiguredValue.of(false);
EXAMPLE.initialize(this.holder, "example");
System.out.println("Example: " + EXAMPLE.get());
ConfigurationTest.testDemo(this.holder);
ConfigurationTest.testInner(this.holder);
+6 -6
View File
@@ -1,4 +1,4 @@
# EasyConfiguration-HOCON
# configured-HOCON
HOCON file-based implementation, compatible with all Java environments.
@@ -20,9 +20,9 @@ HOCON file-based implementation, compatible with all Java environments.
<repository>
<!-- Using GitHub dependencies for real-time updates, configuration required (recommended). -->
<id>EasyConfiguration</id>
<id>configured</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/EasyConfiguration</url>
<url>https://maven.pkg.github.com/CarmJos/configured</url>
</repository>
</repositories>
@@ -35,7 +35,7 @@ HOCON file-based implementation, compatible with all Java environments.
<dependencies>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-hocon</artifactId>
<artifactId>configured-hocon</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -52,14 +52,14 @@ repositories {
mavenCentral()
// Using GitHub dependencies for real-time updates, configuration required (recommended).
maven { url 'https://maven.pkg.github.com/CarmJos/EasyConfiguration' }
maven { url 'https://maven.pkg.github.com/CarmJos/configured' }
}
```
```groovy
dependencies {
api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]"
api "cc.carm.lib:configured-hocon:[LATEST RELEASE]"
}
```
+9 -9
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version>
<artifactId>configured-parent</artifactId>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -15,38 +15,38 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-hocon</artifactId>
<artifactId>configured-hocon</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<artifactId>configured-feature-commentable</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId>
<artifactId>configured-feature-file</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-section</artifactId>
<artifactId>configured-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<artifactId>configured-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
@@ -1,36 +0,0 @@
package cc.carm.lib.configuration;
import cc.carm.lib.configuration.hocon.HOCONFileConfigProvider;
import java.io.File;
import java.io.IOException;
public class EasyConfiguration {
private EasyConfiguration() {
}
public static HOCONFileConfigProvider from(File file, String source) {
HOCONFileConfigProvider provider = new HOCONFileConfigProvider(file);
try {
provider.initializeFile(source);
provider.initializeConfig();
} catch (IOException e) {
e.printStackTrace();
}
return provider;
}
public static HOCONFileConfigProvider from(File file) {
return from(file, file.getName());
}
public static HOCONFileConfigProvider from(String fileName) {
return from(fileName, fileName);
}
public static HOCONFileConfigProvider from(String fileName, String source) {
return from(new File(fileName), source);
}
}
@@ -1,116 +0,0 @@
package cc.carm.lib.configuration.hocon;
import cc.carm.lib.configuration.hocon.util.HOCONUtils;
import com.typesafe.config.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class HOCONConfigWrapper implements ConfigurationWrapper<Map<String, Object>> {
private static final char SEPARATOR = '.';
protected final Map<String, Object> data;
public HOCONConfigWrapper(ConfigObject config) {
this.data = new LinkedHashMap<>();
config.forEach((key, value) -> {
Config cfg = config.toConfig();
ConfigValue cv = cfg.getValue(key);
if (cv.valueType() == ConfigValueType.OBJECT) {
HOCONConfigWrapper.this.data.put(key, new HOCONConfigWrapper((ConfigObject) cv));
} else {
HOCONConfigWrapper.this.data.put(key, value.unwrapped());
}
});
}
@Override
public @NotNull Map<String, Object> getSource() {
return this.data;
}
@Override
public @NotNull Set<String> getKeys(boolean deep) {
return this.getValues(deep).keySet();
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return HOCONUtils.getKeysFromObject(this, deep, "").stream().collect(
LinkedHashMap::new,
(map, key) -> map.put(key, get(key)),
LinkedHashMap::putAll
);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) {
//noinspection unchecked
value = new HOCONConfigWrapper(ConfigFactory.parseMap((Map<String, ?>) value).root());
}
HOCONConfigWrapper section = HOCONUtils.getObjectOn(this, path, SEPARATOR);
String simplePath = HOCONUtils.getSimplePath(path, SEPARATOR);
if (value == null) {
section.data.remove(simplePath);
} else {
section.setDirect(simplePath, value);
}
}
/**
* 只能设置当前路径下的内容
* 避免环回
*
* @param path 路径
*/
public void setDirect(@NotNull String path, @Nullable Object value) {
this.data.put(path, value);
}
@Override
public boolean contains(@NotNull String path) {
return this.get(path) != null;
}
@Override
public @Nullable Object get(@NotNull String path) {
HOCONConfigWrapper section = HOCONUtils.getObjectOn(this, path, SEPARATOR);
return section.getDirect(HOCONUtils.getSimplePath(path, SEPARATOR));
}
/**
* 只能获取当前路径下的内容
* 避免环回
*
* @param path 路径
*/
public Object getDirect(@NotNull String path) {
return this.data.get(path);
}
@Override
public boolean isList(@NotNull String path) {
return this.get(path) instanceof List<?>;
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
Object val = this.get(path);
return (val instanceof List<?>) ? (List<?>) val : null;
}
@Override
public boolean isConfigurationSection(@NotNull String path) {
return this.get(path) instanceof HOCONConfigWrapper;
}
@Override
public @Nullable ConfigurationWrapper<Map<String, Object>> getConfigurationSection(@NotNull String path) {
Object val = get(path);
return (val instanceof HOCONConfigWrapper) ? (HOCONConfigWrapper) val : null;
}
}
@@ -1,105 +0,0 @@
package cc.carm.lib.configuration.hocon;
import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import cc.carm.lib.configuration.hocon.exception.HOCONGetValueException;
import cc.carm.lib.configuration.hocon.util.HOCONUtils;
import com.typesafe.config.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Set;
public class HOCONFileConfigProvider extends FileConfigProvider<HOCONConfigWrapper> {
protected final @NotNull ConfigurationComments comments = new ConfigurationComments();
protected HOCONConfigWrapper configuration;
protected ConfigInitializer<HOCONFileConfigProvider> initializer;
public HOCONFileConfigProvider(@NotNull File file) {
super(file);
this.initializer = new ConfigInitializer<>(this);
}
public void initializeConfig() {
try {
this.configuration = new HOCONConfigWrapper(ConfigFactory.parseFile(this.file, ConfigParseOptions.defaults()
.setSyntax(ConfigSyntax.CONF)
.setAllowMissing(false)).root());
} catch (ConfigException e) {
e.printStackTrace();
}
}
@Override
public @NotNull HOCONConfigWrapper getConfiguration() {
return this.configuration;
}
@Override
public void save() throws IOException {
Files.write(this.file.toPath(), HOCONUtils.renderWithComment(configuration, comments::getHeaderComment).getBytes(StandardCharsets.UTF_8));
}
@Override
protected void onReload() throws ConfigException {
ConfigObject conf = ConfigFactory.parseFile(this.file, ConfigParseOptions.defaults()
.setSyntax(ConfigSyntax.CONF)
.setAllowMissing(false)).root();
this.configuration = new HOCONConfigWrapper(conf);
}
@Override
public @NotNull ConfigurationComments getComments() {
return this.comments;
}
@Override
public @NotNull ConfigInitializer<HOCONFileConfigProvider> getInitializer() {
return this.initializer;
}
public String serializeValue(@NotNull String key, @NotNull Object value) {
// 带有 key=value 的新空对象
return ConfigFactory.empty()
.withValue(key, ConfigValueFactory.fromAnyRef(value))
.root().render();
}
public @NotNull Set<String> getKeys() {
return getKeys(null, true);
}
@Contract("null,_->!null")
public @Nullable Set<String> getKeys(@Nullable String sectionKey, boolean deep) {
if (sectionKey == null) { // 当前路径
return HOCONUtils.getKeysFromObject(this.configuration, deep, "");
}
HOCONConfigWrapper section;
try {
// 获取目标字段所在路径
section = (HOCONConfigWrapper) this.configuration.get(sectionKey);
} catch (ClassCastException e) {
// 值和类型不匹配
throw new HOCONGetValueException(e);
}
if (section == null) {
return null;
}
return HOCONUtils.getKeysFromObject(section, deep, "");
}
public @Nullable Object getValue(@NotNull String key) {
return this.configuration.get(key);
}
public @Nullable List<String> getHeaderComments(@Nullable String key) {
return this.comments.getHeaderComment(key);
}
}
@@ -1,19 +0,0 @@
package cc.carm.lib.configuration.hocon.exception;
public class HOCONGetValueException extends RuntimeException {
public HOCONGetValueException() {
super();
}
public HOCONGetValueException(String message) {
super(message);
}
public HOCONGetValueException(String message, Throwable cause) {
super(message, cause);
}
public HOCONGetValueException(Throwable cause) {
super(cause);
}
}
@@ -1,110 +0,0 @@
package cc.carm.lib.configuration.hocon.util;
import cc.carm.lib.configuration.hocon.HOCONConfigWrapper;
import cc.carm.lib.configuration.hocon.exception.HOCONGetValueException;
import com.typesafe.config.*;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
public class HOCONUtils {
private HOCONUtils() {
}
public static String getSimplePath(String path, char separator) {
int index = path.lastIndexOf(separator);
return (index == -1) ? path : path.substring(index + 1);
}
public static HOCONConfigWrapper getObjectOn(@NotNull HOCONConfigWrapper parent, @NotNull String path, char separator) {
String currentPath = path;
HOCONConfigWrapper currentObject = parent;
int index;
while ((index = currentPath.indexOf(separator)) != -1) {
HOCONConfigWrapper previousObject = currentObject;
String pathName = currentPath.substring(0, index);
try {
currentObject = (HOCONConfigWrapper) previousObject.getDirect(pathName);
} catch (ClassCastException e) {
throw new HOCONGetValueException(e);
}
if (currentObject == null) {
currentObject = new HOCONConfigWrapper(ConfigFactory.empty().root());
previousObject.setDirect(pathName, currentObject);
}
currentPath = currentPath.substring(index + 1);
}
return currentObject;
}
/**
* 在 Object 中获取所有键
* 思路:在第一次执行时 prefix 应该是 ""
* 后续找到了更深层的键,将会变为 "deep."
* 下一次键名就是 "deep.key"
*
* @param parent Object
* @param deep 是否更深层获取
* @param prefix 当前 Object 键名前缀
* @return Object 中的所有键
*/
public static Set<String> getKeysFromObject(HOCONConfigWrapper parent, boolean deep, String prefix) {
return parent.getSource().entrySet().stream().collect(
LinkedHashSet::new,
(set, entry) -> {
Object value = entry.getValue();
if (value instanceof HOCONConfigWrapper && deep) {
set.addAll(HOCONUtils.getKeysFromObject((HOCONConfigWrapper) value, true, prefix + entry.getKey() + "."));
} else {
set.add(prefix + entry.getKey());
}
},
LinkedHashSet::addAll
);
}
/**
* 将 Object 保存为字符串
* 并使用注释器打上注释
*
* @param object Object
* @param commenter 注释器
* @return 保存的字符串
*/
public static @NotNull String renderWithComment(@NotNull HOCONConfigWrapper object, @NotNull Function<String, List<String>> commenter) {
return HOCONUtils.makeConfigWithComment(object, "", commenter).root().render(
ConfigRenderOptions.defaults()
.setJson(false)
.setOriginComments(false)
);
}
public static @NotNull Config makeConfigWithComment(@NotNull HOCONConfigWrapper object, @NotNull String prefix, @NotNull Function<String, List<String>> commenter) {
Config config = ConfigFactory.empty();
for (Map.Entry<String, Object> entry : object.getSource().entrySet()) {
String key = entry.getKey();
String fullKey = prefix + key;
Object value = entry.getValue();
ConfigValue result;
if (value instanceof Iterable) {
result = ConfigValueFactory.fromIterable((Iterable<?>) value);
} else if (value instanceof HOCONConfigWrapper) {
result = makeConfigWithComment((HOCONConfigWrapper) value, fullKey + ".", commenter).root();
} else {
result = ConfigValueFactory.fromAnyRef(value);
}
result = result.withOrigin(
ConfigOriginFactory.newSimple()
.withComments(commenter.apply(fullKey))
);
config = config.withValue(key, result);
}
return config;
}
}
@@ -0,0 +1,48 @@
package cc.carm.lib.configuration.source.hocon;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigFactory;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class HOCONConfigFactory extends FileConfigFactory<HOCONSource, ConfigurationHolder<HOCONSource>, HOCONConfigFactory> {
public static HOCONConfigFactory from(@NotNull String path) {
return new HOCONConfigFactory(new File(path));
}
public static HOCONConfigFactory from(@NotNull File file) {
return new HOCONConfigFactory(file);
}
public static HOCONConfigFactory from(@NotNull File parent, @NotNull String configName) {
return new HOCONConfigFactory(new File(parent, configName));
}
public HOCONConfigFactory(@NotNull File file) {
super(file);
}
@Override
protected HOCONConfigFactory self() {
return this;
}
@Override
public @NotNull ConfigurationHolder<HOCONSource> build() {
File configFile = this.file;
String sourcePath = this.resourcePath;
Commentable.registerMeta(this.initializer); // Register commentable meta types
return new ConfigurationHolder<HOCONSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull HOCONSource source = new HOCONSource(this, configFile, sourcePath);
@Override
public @NotNull HOCONSource config() {
return this.source;
}
};
}
}
@@ -1,69 +0,0 @@
package cc.carm.lib.configuration.source.hocon;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import com.typesafe.config.Config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.util.List;
import java.util.Map;
public class HOCONSection implements ConfigureSection {
protected final @NotNull HOCONSource source;
protected final @Nullable HOCONSection parent;
protected final @NotNull Config data;
public HOCONSection(@NotNull HOCONSource source, @Nullable HOCONSection parent,
@NotNull Config data) {
this.source = source;
this.parent = parent;
this.data = data;
}
public @NotNull Config data() {
return this.data;
}
@Override
public @NotNull HOCONSource source() {
return this.source;
}
@Override
public @Nullable HOCONSection parent() {
return this.parent;
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
return data().root().unwrapped();
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
}
@Override
public boolean contains(@NotNull String path) {
return data().hasPath(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return data().getAnyRefList(path);
}
@Override
public @Nullable HOCONSection getSection(@NotNull String path) {
return data().getConfig(path) == null ? null : new HOCONSection(source, this, data().getConfig(path));
}
@Override
public @Nullable Object get(@NotNull String path) {
return data().getAnyRef(path);
}
}
@@ -1,44 +1,104 @@
package cc.carm.lib.configuration.source.hocon;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import com.typesafe.config.Config;
import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.typesafe.config.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.io.File;
import java.util.*;
public class HOCONSource extends ConfigureSource<HOCONSection, Config, HOCONSource> {
public class HOCONSource
extends FileConfigSource<SourcedSection, Map<String, Object>, HOCONSource> {
protected @Nullable SourcedSection rootSection;
protected HOCONSource(
@NotNull ConfigurationHolder<? extends HOCONSource> holder,
@NotNull File file, @Nullable String resourcePath
) {
super(holder, 0, file, resourcePath);
private HOCONSection rootSection;
this.initialize();
}
protected HOCONSource(@NotNull ConfigurationHolder<? extends HOCONSource> holder, long lastUpdateMillis) {
super(holder, lastUpdateMillis);
public void initialize() {
try {
this.initializeFile();
this.onReload();
} catch (Exception e) {
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
}
@Override
protected HOCONSource self() {
protected @NotNull HOCONSource self() {
return this;
}
@Override
public @NotNull Config original() {
return section().data();
public @NotNull Map<String, Object> original() {
return this.section().data();
}
@Override
public @NotNull HOCONSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized");
public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
}
public @NotNull String saveToString() {
// identity: 创建新的空 typesafe config
// accumulator: 将 Section 中的信息为 typesafe config 添加并返回
// combiner: 合并两个配置文件
Config config = this.getValues(true).entrySet().stream().reduce(
ConfigFactory.empty(),
(cfg, entry) -> {
String key = entry.getKey(); // 源数据 key
Object value = entry.getValue(); // 源数据 value
ConfigValue result; // 最终转换为 typesafe 的 ConfigValue 类型
if (value == null || value instanceof Boolean || value instanceof String || value instanceof Number) {
result = ConfigValueFactory.fromAnyRef(value); // 原始数据类型
} else if (value instanceof Iterator) {
result = ConfigValueFactory.fromIterable((Iterable<?>) value);
} else if (value instanceof Map) {
//noinspection unchecked
result = ConfigValueFactory.fromMap((Map<String, ?>) value);
} else {
result = ConfigValueFactory.fromAnyRef(String.valueOf(value));
}
List<String> headerComments = HOCONSource.this.getHeaderComments(key); // 获取其注释
result = result.withOrigin(result.origin().withComments(headerComments)); // 赋予其注释
return cfg.withValue(key, result); // 将其添加到根 config 中
},
Config::withFallback
);
return config.root().render(
ConfigRenderOptions.defaults()
.setJson(false)
.setOriginComments(false)
);
}
@Override
public void save() throws Exception {
this.fileWriter(w -> w.write(HOCONSource.this.saveToString()));
}
@Override
protected void onReload() throws Exception {
this.rootSection = this.fileReadString(this::loadFromString);
}
protected @NotNull SourcedSection loadFromString(@NotNull String data) {
ConfigObject config = ConfigFactory.parseString(data).root();
return SourcedSection.root(this, config.unwrapped());
}
public @Nullable List<String> getHeaderComments(@Nullable String key) {
return Commentable.getHeaderComments(holder(), key);
}
}
@@ -1,18 +0,0 @@
package online.flowerinsnow.test.easyconfiguration;
public class HOCONTest {
// @Test
// public void onTest() {
// HOCONFileConfigProvider provider = EasyConfiguration.from(new File("target/hocon.conf"));
//
// ConfigurationTest.testDemo(provider);
// ConfigurationTest.testInner(provider);
//
// try {
// provider.save();
// provider.reload();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
}
@@ -0,0 +1,38 @@
package sample;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.value.standard.ConfiguredList;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import java.util.UUID;
@ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
).build();
@ConfigPath("info") // Custom path
interface INFO extends Configuration {
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
}
}
@@ -0,0 +1,27 @@
package sample;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.hocon.HOCONConfigFactory;
import org.junit.Test;
public class SampleTest {
@Test
public void test() {
// 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = HOCONConfigFactory.from("target/config.conf")
.resourcePath("configs/sample.conf")
.build();
// 2. Initialize the configuration classes or instances.
holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false);
System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. Changes not save so enable value will still be true in the next run.
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
}
}
@@ -0,0 +1,2 @@
version = 1.0
test-save = false
+21 -13
View File
@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<artifactId>configured-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.1.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,22 +15,23 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.mongodb.version>5.3.1</deps.mongodb.version>
<log4j.version>2.24.3</log4j.version>
</properties>
<artifactId>easyconfiguration-mongodb</artifactId>
<artifactId>configured-mongodb</artifactId>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<artifactId>configured-core</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<artifactId>configured-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
@@ -38,14 +39,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
<version>${deps.mongodb.version}</version>
</dependency>
<dependency>
@@ -69,6 +63,20 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>configured-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>configured-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -0,0 +1,75 @@
package cc.carm.lib.configuration.source.mongodb;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class MongoConfigFactory extends ConfigurationFactory<MongoSource, ConfigurationHolder<MongoSource>, MongoConfigFactory> {
public static MongoConfigFactory from(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
return new MongoConfigFactory(collectionSupplier);
}
public static MongoConfigFactory from(@NotNull MongoCollection<Document> collection) {
return from(() -> collection);
}
public static MongoConfigFactory from(@NotNull MongoDatabase database, @NotNull String collectionName) {
return from(() -> database.getCollection(collectionName));
}
protected @NotNull Supplier<MongoCollection<Document>> collectionSupplier;
protected @NotNull String namespace = "config";
public MongoConfigFactory(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
super();
this.collectionSupplier = collectionSupplier;
}
public MongoConfigFactory collection(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
this.collectionSupplier = collectionSupplier;
return this;
}
public MongoConfigFactory collection(@NotNull MongoCollection<Document> collection) {
return collection(() -> collection);
}
public MongoConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return this;
}
public MongoConfigFactory namespace(@NotNull Supplier<String> namespace) {
return namespace(namespace.get());
}
@Override
protected MongoConfigFactory self() {
return this;
}
@Override
public @NotNull ConfigurationHolder<MongoSource> build() {
MongoCollection<Document> collection = this.collectionSupplier.get();
if (collection == null) {
throw new IllegalStateException("Failed to get MongoCollection<Document> from supplier");
}
return new ConfigurationHolder<MongoSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull MongoSource source = new MongoSource(this, System.currentTimeMillis(), collection, namespace);
@Override
public @NotNull MongoSource config() {
return this.source;
}
};
}
}
@@ -1,4 +1,76 @@
package cc.carm.lib.configuration.source.mongodb;
public class MongoSource {
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.ReplaceOptions;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
public class MongoSource extends ConfigureSource<SourcedSection, Map<String, Object>, MongoSource> {
protected final @NotNull MongoCollection<Document> collection;
protected final @NotNull String namespace;
protected SourcedSection rootSection;
protected MongoSource(@NotNull ConfigurationHolder<? extends MongoSource> holder, long lastUpdateMillis,
@NotNull MongoCollection<Document> collection, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.collection = collection;
this.namespace = namespace;
try {
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected @NotNull MongoSource self() {
return this;
}
@Override
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull SourcedSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized");
}
public @NotNull String namespace() {
return this.namespace;
}
public @NotNull MongoCollection<Document> collection() {
return this.collection;
}
@Override
public void save() throws Exception {
Map<String, Object> data = this.rootSection.asMap();
if (data.isEmpty()) return; // Skip saving if empty
if (data.containsKey("_id") && data.size() == 1) return; // Skip saving if only contains _id
ReplaceOptions options = new ReplaceOptions().upsert(true);
Document storage = new Document(data).append("_id", this.namespace);
this.collection.replaceOne(new Document("_id", this.namespace), storage, options);
}
@Override
protected void onReload() throws Exception {
Document storage = this.collection.find(new Document("_id", this.namespace)).first();
if (storage == null) storage = new Document();
else storage.remove("_id"); // Remove _id
this.rootSection = SourcedSection.root(this, storage);
}
}
@@ -0,0 +1,12 @@
package config;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
interface MongoConfig extends Configuration {
ConfiguredValue<String> HOST = ConfiguredValue.of("127.0.0.1");
ConfiguredValue<Integer> PORT = ConfiguredValue.of(27017);
ConfiguredValue<String> USERNAME = ConfiguredValue.of("minecraft");
ConfiguredValue<String> PASSWORD = ConfiguredValue.of("minecraft");
ConfiguredValue<String> DATABASE = ConfiguredValue.of("minecraft");
}

Some files were not shown because too many files have changed in this diff Show More