1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 18:48:20 +08:00

Compare commits

...

66 Commits

Author SHA1 Message Date
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
carm 1c27c2f881 fix: Fixed the missing implements of #remove 2025-02-19 00:57:17 +08:00
carm 476cb79e2f feat: Support standard "pre-load" option 2025-02-18 21:47:58 +08:00
carm bf716b06ae docs: Add more javadocs 2025-02-18 17:33:28 +08:00
carm 00170e6d77 feat: Simplify the ConfigureSection functions to support more different usages. 2025-02-18 17:08:12 +08:00
carm 1bac201427 chore: Add more limitations for inline comments 2025-02-18 14:12:28 +08:00
carm cd77639df5 docs: Add examples 2025-02-17 18:37:16 +08:00
renovate[bot] 5ae756929a fix(deps): update dependency cc.carm.lib:yamlcommentwriter to v1.2.0 2025-02-17 18:28:00 +08:00
carm a9e80aecce fix(text): Fixed wrong type of text 2025-02-17 18:28:00 +08:00
carm 477f0d11bc fix(text): Fixed wrong type of text 2025-02-17 06:43:30 +08:00
renovate[bot] 53ff38a76f fix(deps): update dependency cc.carm.lib:yamlcommentwriter to v1.2.0 2025-02-17 05:24:09 +08:00
carm c32bea3864 fix(builder): Fixed wrong type of serializer 2025-02-17 04:48:15 +08:00
carm 15823bb076 fix(builder): Fixed wrong type of serializer 2025-02-17 04:47:23 +08:00
renovate[bot] f303f56199 fix(deps): update dependency cc.carm.lib:yamlcommentwriter to v1.2.0 2025-02-17 04:27:50 +08:00
carm 22f2674e7c feat(text): Separated the text handler 2025-02-17 04:26:22 +08:00
carm 9eb9c9e13d feat(parser): Supported more insert functions 2025-02-17 03:14:40 +08:00
carm 616314c7f0 feat(parser): Supported more insert functions 2025-02-17 03:13:35 +08:00
carm 05f504a347 feat(parser): Supported parse functions 2025-02-17 02:46:18 +08:00
carm bf6ea97b99 feaT(unit): Add unit meta 2025-02-17 01:40:41 +08:00
carm 9847399e56 feat: Version update to 4.0.1 2025-02-17 00:33:41 +08:00
carm 18515d4a78 refactor(comment): Extract common comment functions 2025-02-17 00:12:29 +08:00
carm 9f1fc5bf90 refactor(comment): Extract common comment functions 2025-02-17 00:11:56 +08:00
carm e7847b2166 feat: Version update to 4.0.1 2025-02-16 23:51:18 +08:00
carm 69cec281be feat: Version update to 4.0.1 2025-02-16 23:47:22 +08:00
carm 28dc9b9e55 feat(text): Implement the ConfiguredText for simply text/msg functions. 2025-02-16 23:46:31 +08:00
LSeng c60ba074d9 fix(comment): Fixed wrong behavior of inline regex comments 2025-02-16 21:59:03 +08:00
carm e88bf301cc ci(javadoc): Fixed javadoc deployment 2025-02-16 02:31:10 +08:00
93 changed files with 4008 additions and 1133 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
run: | run: |
rm -rf docs rm -rf docs
mkdir -vp docs mkdir -vp docs
cp -vrf core/target/apidocs/* docs/ cp -vrf core/target/reports/apidocs/* docs/
cp -vrf .doc/JAVADOC-README.md docs/README.md cp -vrf .doc/JAVADOC-README.md docs/README.md
- name: "Generate the Javadoc sitemap" - name: "Generate the Javadoc sitemap"
+1 -1
View File
@@ -1,3 +1,3 @@
/.idea/ .idea/
**/target/ **/target/
**.iml **.iml
+43 -14
View File
@@ -49,41 +49,58 @@ Check out all code demonstrations [HERE](demo/src/main/java/cc/carm/lib/configur
For more examples, see the [Development Guide](.doc/README.md). For more examples, see the [Development Guide](.doc/README.md).
```java ```java
@HeaderComment("Configurations for sample") @ConfigPath(root = true)
interface SampleConfig extends Configuration { @HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // Inline comment @InlineComment("Enabled?") // Inline comment
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@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() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
).build(); ).build();
@ConfigPath("info") // Custom path
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // Header comment @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // Custom path @ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
} }
} }
``` ```
```java ```java
public class Sample { public class Sample {
public static void main(String[] args) { public static void main(String[] args) {
// 1. Make a configuration provider from a file. // 1. Make a configuration provider from a file.
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml"); 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. // 2. Initialize the configuration classes or instances.
provider.initialize(SampleConfig.class); holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration! // 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false); SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !"); 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() + ")!");
} }
} }
@@ -94,14 +111,20 @@ public class Sample {
enabled: true #Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### Dependencies ### Dependencies
@@ -159,10 +182,10 @@ info:
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. --> <!-- JSON file-based implementation, compatible with all Java environments. -->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -205,8 +228,8 @@ dependencies {
// YAML file-based implementation, compatible with all Java environments. // YAML file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. // JSON file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
} }
``` ```
@@ -220,7 +243,8 @@ dependencies {
EasyConfiguration for MineCraft! EasyConfiguration for MineCraft!
Easily manage configurations on MineCraft-related server platforms. Easily manage configurations on MineCraft-related server platforms.
Currently supports BungeeCord, Bukkit (Spigot) servers, with more platforms to be supported soon. Currently, it supports BungeeCord, Velocity, Bukkit (Spigot) servers,
with more platforms to be supported soon.
## Support and Donation ## Support and Donation
@@ -232,6 +256,11 @@ Many thanks to Jetbrains for kindly providing a license for us to work on this a
[![](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/EasyConfiguration)
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 ## Open Source License
This project's source code is licensed under This project's source code is licensed under
+44 -17
View File
@@ -32,26 +32,35 @@ 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/EasyConfiguration) 。
### 示例代码 ### 示例代码
为快速的展示该项目的适用性,这里有几个实际演示: 为快速的展示该项目的适用性,这里有几个实际演示:
- [数据库配置文件实例](demo/src/main/java/cc/carm/lib/configuration/demo/DatabaseConfiguration.java) - [数据库配置文件实例](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/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 ```java
@HeaderComment("Configurations for sample")
interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行内注释 @ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行后注释
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@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() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
@@ -59,26 +68,34 @@ interface SampleConfig extends Configuration {
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // 头部注释 @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // 自定义配置路径,若不定义,则按照默认规则生成 @ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
} }
} }
``` ```
```java ```java
public class Sample { public class Sample {
public static void main(String[] args) { public static void main(String[] args) {
// 1. 生成一个 “Provider” 用于给配置类提供源配置的文件。 // 1. 生成一个 “holder” 用于给配置类提供源配置的文件。
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml"); ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
// 2. 通过 “Provider” 初始化配置类或配置实例。 .resourcePath("configs/sample.yml")
provider.initialize(SampleConfig.class); .indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. 通过 “holder” 初始化配置类或配置实例。
holder.initialize(SampleConfig.class);
// 3. 现在可以享受快捷方便的配置文件使用方式了~ // 3. 现在可以享受快捷方便的配置文件使用方式了~
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false); SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !"); System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. 在本示例里的更改未保存,因此启用值在下次运行中仍将为 true。
} }
} }
``` ```
@@ -88,14 +105,20 @@ public class Sample {
enabled: true #Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### 依赖方式 ### 依赖方式
@@ -164,7 +187,7 @@ info:
<!--需要注意的是,JSON不支持文件注释。--> <!--需要注意的是,JSON不支持文件注释。-->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -225,7 +248,7 @@ dependencies {
//基于JSON文件的实现版本,可用于全部Java环境。 //基于JSON文件的实现版本,可用于全部Java环境。
//需要注意的是,JSON不支持文件注释。 //需要注意的是,JSON不支持文件注释。
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]"
@@ -255,6 +278,10 @@ EasyConfiguration for MineCraft!
[![](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/EasyConfiguration)
万分感谢来自 [ArtformGames](https://github.com/ArtformGames) 对本项目的大力支持与积极贡献!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## 开源协议 ## 开源协议
本项目源码采用 [GNU LESSER GENERAL PUBLIC LICENSE](https://www.gnu.org/licenses/lgpl-3.0.html) 开源协议。 本项目源码采用 [GNU LESSER GENERAL PUBLIC LICENSE](https://www.gnu.org/licenses/lgpl-3.0.html) 开源协议。
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -157,7 +158,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type; ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType(); Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName()); sb.append(raw.getClass().getName());
sb.append('<'); sb.append('<');
Type[] args = pt.getActualTypeArguments(); Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@@ -166,6 +167,7 @@ public abstract class ValueType<T> {
} }
sb.append(args[i].getTypeName()); sb.append(args[i].getTypeName());
} }
sb.append('>');
return sb.toString(); return sb.toString();
} }
return type.getTypeName(); return type.getTypeName();
@@ -3,8 +3,8 @@ package cc.carm.lib.configuration.builder.impl;
import cc.carm.lib.configuration.adapter.ValueAdapter; import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType; import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.CommonConfigBuilder; import cc.carm.lib.configuration.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.function.DataConsumer;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.function.ValueConsumer;
import cc.carm.lib.configuration.function.ValueHandler; import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigureSection; import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
@@ -34,28 +34,30 @@ public abstract class AbstractSectionBuilder<
this.serializer = serializer; this.serializer = serializer;
} }
public @NotNull SELF parse(DataFunction<ConfigureSection, PARAM> valueParser) { public @NotNull SELF parse(@NotNull DataFunction<ConfigureSection, PARAM> valueParser) {
return parse((p, section) -> valueParser.handle(section)); return parse((p, section) -> valueParser.handle(section));
} }
public @NotNull SELF parse(ValueHandler<ConfigureSection, PARAM> valueParser) { public @NotNull SELF parse(@NotNull ValueHandler<ConfigureSection, PARAM> valueParser) {
this.parser = valueParser; this.parser = valueParser;
return self(); return self();
} }
public @NotNull SELF serialize(DataFunction<PARAM, ? extends Map<String, Object>> serializer) { public @NotNull SELF serialize(@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
public @NotNull SELF serialize(ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
this.serializer = serializer; this.serializer = serializer;
return self(); return self();
} }
public @NotNull SELF serialize(DataConsumer<Map<String, Object>> serializer) { public @NotNull SELF serialize(@NotNull DataFunction<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> { return serialize((p, value) -> {
return serializer.handle(value);
});
}
public @NotNull SELF serialize(@NotNull ValueConsumer<Map<String, Object>, PARAM> serializer) {
return serialize((h, value) -> {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
serializer.accept(map); serializer.accept(h, map, value);
return map; return map;
}); });
} }
@@ -29,7 +29,7 @@ public abstract class AbstractSourceBuilder<
this.valueSerializer = serializer; this.valueSerializer = serializer;
} }
public @NotNull SELF parse(DataFunction<SOURCE, PARAM> parser) { public @NotNull SELF parse(@NotNull DataFunction<SOURCE, PARAM> parser) {
return parse((p, source) -> parser.handle(source)); return parse((p, source) -> parser.handle(source));
} }
@@ -0,0 +1,21 @@
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);
};
}
}
@@ -20,6 +20,13 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* ConfigurationFactory, used to create configuration holder.
*
* @param <SOURCE> The {@link ConfigureSource} type
* @param <HOLDER> The {@link ConfigurationHolder} type.
* @param <SELF> Self builder, for further implement support.
*/
public abstract class ConfigurationFactory< public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>, SOURCE extends ConfigureSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>, HOLDER extends ConfigurationHolder<SOURCE>,
@@ -139,18 +146,39 @@ public abstract class ConfigurationFactory<
return self(); return self();
} }
/**
* Supply the base path generator for this configuration holder
*
* @param generator {@link PathGenerator}
* @return this
*/
public SELF pathGenerator(PathGenerator generator) { public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> { return initializer(loader -> {
loader.pathGenerator(generator); loader.pathGenerator(generator);
}); });
} }
/**
* Register a new annotation for metadata to the configuration loader
*
* @param annotation The {@link Annotation}
* @param metadata The {@link ConfigurationMetadata} type
* @param extractor The {@link Function} to extract the metadata from the annotation
* @param <M> The metadata type
* @param <A> The annotation type
* @return this
*/
public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation, public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<M> metadata, @NotNull ConfigurationMetadata<M> metadata,
@NotNull Function<A, M> extractor) { @NotNull Function<A, M> extractor) {
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor)); return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
} }
/**
* Build the configuration holder.
*
* @return The configuration holder
*/
public abstract @NotNull HOLDER build(); public abstract @NotNull HOLDER build();
} }
@@ -6,8 +6,11 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer; import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; 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.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest; import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -17,6 +20,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> { public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -50,6 +55,10 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options; return options;
} }
public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
public @NotNull Map<String, ConfigurationMetaHolder> metadata() { public @NotNull Map<String, ConfigurationMetaHolder> metadata() {
return this.metadata; return this.metadata;
} }
@@ -61,14 +70,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) { 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<>(); Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) { for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type); 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); return Collections.unmodifiableMap(metas);
} }
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() { public ValueAdapterRegistry adapters() {
return this.adapters; return this.adapters;
} }
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@FunctionalInterface @FunctionalInterface
public interface ConfigInitializeHandler<T> { public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() { static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value) -> { return (provider, path, value, instace) -> {
}; };
} }
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) { default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value); after.whenInitialize(provider, path, value, instance);
}; };
} }
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) { default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value); before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
}; };
} }
@@ -3,6 +3,7 @@ package cc.carm.lib.configuration.source.loader;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.StandardOptions; import cc.carm.lib.configuration.source.option.StandardOptions;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -20,18 +21,18 @@ import java.util.function.Function;
public class ConfigurationInitializer { public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator; protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer; protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer; protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() { public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start()); this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
} }
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator, public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer, @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator; this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer; this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer; this.classInitializer = classInitializer;
} }
@@ -43,34 +44,34 @@ public class ConfigurationInitializer {
return pathGenerator; return pathGenerator;
} }
public ConfigInitializeHandler<Field> fieldInitializer() { public ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer() {
return fieldInitializer; return valueInitializer;
} }
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = fieldInitializer; this.valueInitializer = fieldInitializer;
} }
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() { public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return 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; this.classInitializer = classInitializer;
} }
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(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); this.classInitializer = this.classInitializer.andThen(classInitializer);
} }
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> { appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation); A data = clazz.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -80,7 +81,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> { appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation); A data = field.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -107,13 +108,13 @@ public class ConfigurationInitializer {
public void initialize(@NotNull ConfigurationHolder<?> holder, public void initialize(@NotNull ConfigurationHolder<?> holder,
@NotNull Configuration config) throws Exception { @NotNull Configuration config) throws Exception {
initializeInstance(holder, config, null, null); initializeInstance(holder, config, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save(); if (holder.option(StandardOptions.SET_DEFAULTS)) holder.save();
} }
public void initialize(@NotNull ConfigurationHolder<?> holder, public void initialize(@NotNull ConfigurationHolder<?> holder,
@NotNull Class<? extends Configuration> clazz) throws Exception { @NotNull Class<? extends Configuration> clazz) throws Exception {
initializeStaticClass(holder, clazz, null, null); initializeStaticClass(holder, clazz, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save(); if (holder.option(StandardOptions.SET_DEFAULTS)) holder.save();
} }
@@ -122,7 +123,7 @@ public class ConfigurationInitializer {
@Nullable String parentPath, @Nullable Field configField) { @Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(holder, parentPath, root.getClass(), configField); String path = getClassPath(holder, parentPath, root.getClass(), configField);
try { try {
this.classInitializer.whenInitialize(holder, path, root.getClass()); this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -139,7 +140,7 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField); String path = getClassPath(holder, parentPath, clazz, configField);
try { try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz); this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -148,7 +149,7 @@ public class ConfigurationInitializer {
initializeField(holder, clazz, field, path); initializeField(holder, clazz, field, path);
} }
if (holder.options().get(StandardOptions.LOAD_SUB_CLASSES)) { if (holder.option(StandardOptions.LOAD_SUB_CLASSES)) {
Class<?>[] classes = clazz.getDeclaredClasses(); Class<?>[] classes = clazz.getDeclaredClasses();
for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。 for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
initializeStaticClass(holder, classes[i], path, null); initializeStaticClass(holder, classes[i], path, null);
@@ -168,13 +169,17 @@ public class ConfigurationInitializer {
String path = getFieldPath(holder, parent, field); String path = getFieldPath(holder, parent, field);
if (path == null) return; if (path == null) return;
value.initialize(holder, path); value.initialize(holder, path);
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 { try {
this.fieldInitializer.whenInitialize(holder, path, field); this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
if (holder.options().get(StandardOptions.SET_DEFAULTS)) { if (holder.option(StandardOptions.PRELOAD)) {
value.setDefault(); value.get(); // Preload the value by calling #get method.
} }
} else if (source instanceof Configuration && object instanceof Configuration) { } else if (source instanceof Configuration && object instanceof Configuration) {
// 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。 // 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。
@@ -9,6 +9,12 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
/**
* Path generator for configuration.
* <p>
* Path generator is a utility class that helps to generate the path of the configuration.
* It can be used to generate the path of the field or class.
*/
public class PathGenerator { public class PathGenerator {
public static PathGenerator of() { public static PathGenerator of() {
@@ -80,7 +86,7 @@ public class PathGenerator {
} }
public static char pathSeparator(ConfigurationHolder<?> holder) { public static char pathSeparator(ConfigurationHolder<?> holder) {
return holder.options().get(StandardOptions.PATH_SEPARATOR); return holder.option(StandardOptions.PATH_SEPARATOR);
} }
/** /**
@@ -0,0 +1,12 @@
package cc.carm.lib.configuration.source.meta;
import cc.carm.lib.configuration.value.ConfigValue;
public interface StandardMeta {
/**
* To mark the {@link ConfigValue} instance of specific path.
*/
ConfigurationMetadata<ConfigValue<?>> VALUE = ConfigurationMetadata.of();
}
@@ -6,32 +6,20 @@ import java.util.function.Supplier;
public class ConfigurationOption<V> { public class ConfigurationOption<V> {
@SuppressWarnings("unchecked")
public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) {
return of((Class<T>) defaultValue.getClass(), defaultValue); return new ConfigurationOption<>(defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Class<T> valueClazz, @NotNull T defaultValue) {
return new ConfigurationOption<>(valueClazz, defaultValue);
} }
public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) {
return of(defaultValue.get()); return of(defaultValue.get());
} }
private final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue; private @NotNull V defaultValue;
public ConfigurationOption(@NotNull Class<V> valueClazz, @NotNull V defaultValue) { public ConfigurationOption(@NotNull V defaultValue) {
this.valueClazz = valueClazz;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
@NotNull
public Class<V> valueClass() {
return this.valueClazz;
}
public @NotNull V defaults() { public @NotNull V defaults() {
return defaultValue; return defaultValue;
} }
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true); ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/** /**
* Whether to pre parse the config values. * Whether to pre parse the config values,
* <br> if false, the values will be parsed when calling * may good to set true if you want to keep the config format.
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* <br> if true, the values will be parsed when * <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}. * {@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);
} }
@@ -1,73 +1,451 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView; import org.jetbrains.annotations.UnmodifiableView;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
/**
* Represents a section of a configuration.
*
* @author Carm
* @since 4.0.0
*/
public interface ConfigureSection { public interface ConfigureSection {
@NotNull ConfigureSource<?, ?, ?> source(); /**
* Gets the parent section of this section.
* <p>
* For root sections, this will return null.
*
* @return Parent section, or null if this is a root section.
*/
@Contract(pure = true)
@Nullable ConfigureSection parent(); @Nullable ConfigureSection parent();
default char separator() { /**
return source().holder().options().get(StandardOptions.PATH_SEPARATOR); * 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>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Set of keys contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
default Set<String> getKeys(boolean deep) { default Set<String> getKeys(boolean deep) {
return getValues(deep).keySet(); 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>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Map of data values contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
Map<String, Object> getValues(boolean deep); 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>
* Null values will be kept, if you want to remove a value use {@link #remove(String)}
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to set the value at.
* @param value The value to set.
*/
void set(@NotNull String path, @Nullable Object value); void set(@NotNull String path, @Nullable Object value);
boolean contains(@NotNull String path); /**
* Removes the value at the given path.
* <p>
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to remove the value at.
*/
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>
* 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 containsValue(@NotNull String path) {
return get(path) != null;
}
/**
* Predicate the value of given path is specific type.
*
* @param path The path to check.
* @param typeClass The type's class
* @param <T> The type to check.
* @return True if the value is present and is the correct type, false otherwise.
*/
default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) { default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) {
return typeClass.isInstance(get(path)); return typeClass.isInstance(get(path));
} }
/**
* Predicate the value of given path is a {@link List}.
*
* @param path The path to check.
* @return True if the value is present and is a list, false otherwise.
*/
default boolean isList(@NotNull String path) { default boolean isList(@NotNull String path) {
return isType(path, List.class); return isType(path, List.class);
} }
@Nullable List<?> getList(@NotNull String path); /**
* Get the value as a {@link List} from the specified path.
*
* @param path The path to get the {@link List}.
* @return The list if the path exists and is a list, otherwise null.
*/
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}.
*
* @param path The path to check.
* @return True if the value is present and is a section, false otherwise.
*/
default boolean isSection(@NotNull String path) { default boolean isSection(@NotNull String path) {
return isType(path, ConfigureSection.class); return isType(path, ConfigureSection.class);
} }
/**
* Get the value as a {@link ConfigureSection} from the specified path.
*
* @param path The path to get the section.
* @return The section if the path exists and is a section, otherwise null.
*/
@Nullable @Nullable
ConfigureSection getSection(@NotNull String path); default ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
@Nullable Object get(@NotNull String path); return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> clazz) {
return get(path, null, clazz);
} }
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<Object, T> parser) { /**
* 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.
*
* @param path The path to get the value from.
* @return The value at the path, or null if not found.
*/
@Nullable Object get(@NotNull String path);
/**
* Get the value of the path for specific type,
* if the path does not exist, return null.
*
* @param path The path to get the value from.
* @param type The type class of the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> type) {
return get(path, null, type);
}
/**
* Get the value of the path using a parser function,
* if the path does not exist, return NULL.
*
* @param path The path to get the value from.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or null if not found.
*/
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<@Nullable Object, T> parser) {
return get(path, null, parser); return get(path, null, parser);
} }
/**
* Get the value of the path for specific type,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaults The default value to return if the path does not exist.
* @param clazz The type class of the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, @NotNull Class<T> clazz) { default @Nullable <T> T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> clazz) {
return get(path, defaultValue, DataFunction.castObject(clazz)); return get(path, defaults, DataFunction.castObject(clazz));
} }
/**
* Get the value of the path using a parser function,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaultValue The default value to return if the path does not exist.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
@@ -82,125 +460,290 @@ public interface ConfigureSection {
return defaultValue; return defaultValue;
} }
/**
* Predicate the value of given path is a {@link Boolean}.
*
* @param path The path to check.
* @return True if the value is present and is a boolean, false otherwise.
*/
default boolean isBoolean(@NotNull String path) { default boolean isBoolean(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @return The boolean if the path exists and is a boolean, otherwise false.
*/
default boolean getBoolean(@NotNull String path) { default boolean getBoolean(@NotNull String path) {
return getBoolean(path, false); return getBoolean(path, false);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @param def The default value to return if the path does not exist.
* @return The boolean if the path exists and is a boolean, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) { default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
return get(path, def, DataFunction.booleanValue()); return get(path, def, DataFunction.booleanValue());
} }
/**
* Predicate the value of given path is a {@link Byte}.
*
* @param path The path to check.
* @return True if the value is present and is a byte, false otherwise.
*/
default @Nullable Boolean isByte(@NotNull String path) { default @Nullable Boolean isByte(@NotNull String path) {
return isType(path, Byte.class); return isType(path, Byte.class);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @return The byte if the path exists and is a byte, otherwise 0.
*/
default @Nullable Byte getByte(@NotNull String path) { default @Nullable Byte getByte(@NotNull String path) {
return getByte(path, (byte) 0); return getByte(path, (byte) 0);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @param def The default value to return if the path does not exist.
* @return The byte if the path exists and is a byte, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) { default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
return get(path, def, DataFunction.byteValue()); return get(path, def, DataFunction.byteValue());
} }
/**
* Predicate the value of given path is a {@link Short}.
*
* @param path The path to check.
* @return True if the value is present and is a short, false otherwise.
*/
default boolean isShort(@NotNull String path) { default boolean isShort(@NotNull String path) {
return isType(path, Short.class); return isType(path, Short.class);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @return The short if the path exists and is a short, otherwise 0.
*/
default @Nullable Short getShort(@NotNull String path) { default @Nullable Short getShort(@NotNull String path) {
return getShort(path, (short) 0); return getShort(path, (short) 0);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @param def The default value to return if the path does not exist.
* @return The short if the path exists and is a short, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Short getShort(@NotNull String path, @Nullable Short def) { default @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
return get(path, def, DataFunction.shortValue()); return get(path, def, DataFunction.shortValue());
} }
/**
* 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 an int, false otherwise.
*/
default boolean isInt(@NotNull String path) { default boolean isInt(@NotNull String path) {
return isType(path, Integer.class); return isType(path, Integer.class);
} }
/**
* 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 an int, otherwise 0.
*/
default @Nullable Integer getInt(@NotNull String path) { default @Nullable Integer getInt(@NotNull String path) {
return getInt(path, 0); return getInt(path, 0);
} }
/**
* Get the value as a {@link Integer} from the specified path.
*
* @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 an int, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) { default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
return get(path, def, DataFunction.intValue()); return get(path, def, DataFunction.intValue());
} }
/**
* Predicate the value of given path is a {@link Long}.F
*
* @param path The path to check.
* @return True if the value is present and is a long, false otherwise.
*/
default boolean isLong(@NotNull String path) { default boolean isLong(@NotNull String path) {
return isType(path, Long.class); return isType(path, Long.class);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @return The long if the path exists and is a long, otherwise 0.
*/
default @Nullable Long getLong(@NotNull String path) { default @Nullable Long getLong(@NotNull String path) {
return getLong(path, 0L); return getLong(path, 0L);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @param def The default value to return if the path does not exist.
* @return The long if the path exists and is a long, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Long getLong(@NotNull String path, @Nullable Long def) { default @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
return get(path, def, DataFunction.longValue()); return get(path, def, DataFunction.longValue());
} }
/**
* Predicate the value of given path is a {@link Float}.
*
* @param path The path to check.
* @return True if the value is present and is a float, false otherwise.
*/
default boolean isFloat(@NotNull String path) { default boolean isFloat(@NotNull String path) {
return isType(path, Float.class); return isType(path, Float.class);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @return The float if the path exists and is a float, otherwise 0.
*/
default @Nullable Float getFloat(@NotNull String path) { default @Nullable Float getFloat(@NotNull String path) {
return getFloat(path, 0.0F); return getFloat(path, 0.0F);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @param def The default value to return if the path does not exist.
* @return The float if the path exists and is a float, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) { default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
return get(path, def, DataFunction.floatValue()); return get(path, def, DataFunction.floatValue());
} }
/**
* Predicate the value of given path is a {@link Double}.
*
* @param path The path to check.
* @return True if the value is present and is a double, false otherwise.
*/
default boolean isDouble(@NotNull String path) { default boolean isDouble(@NotNull String path) {
return isType(path, Double.class); return isType(path, Double.class);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @return The double if the path exists and is a double, otherwise 0.
*/
default @Nullable Double getDouble(@NotNull String path) { default @Nullable Double getDouble(@NotNull String path) {
return getDouble(path, 0.0D); return getDouble(path, 0.0D);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @param def The default value to return if the path does not exist.
* @return The double if the path exists and is a double, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) { default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
return get(path, def, DataFunction.doubleValue()); return get(path, def, DataFunction.doubleValue());
} }
/**
* Predicate the value of given path is a {@link Character}.
*
* @param path The path to check.
* @return True if the value is present and is a char, false otherwise.
*/
default boolean isChar(@NotNull String path) { default boolean isChar(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @return The char if the path exists and is a char, otherwise null.
*/
default @Nullable Character getChar(@NotNull String path) { default @Nullable Character getChar(@NotNull String path) {
return getChar(path, null); return getChar(path, null);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @param def The default value to return if the path does not exist.
* @return The char if the path exists and is a char, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Character getChar(@NotNull String path, @Nullable Character def) { default @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
return get(path, def, Character.class); return get(path, def, Character.class);
} }
/**
* Predicate the value of given path is a {@link String}.
*
* @param path The path to check.
* @return True if the value is present and is a string, false otherwise.
*/
default boolean isString(@NotNull String path) { default boolean isString(@NotNull String path) {
return isType(path, String.class); return isType(path, String.class);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @return The string if the path exists and is a string, otherwise null.
*/
default @Nullable String getString(@NotNull String path) { default @Nullable String getString(@NotNull String path) {
return getString(path, null); return getString(path, null);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @param def The default value to return if the path does not exist.
* @return The string if the path exists and is a string, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable String getString(@NotNull String path, @Nullable String def) { default @Nullable String getString(@NotNull String path, @Nullable String def) {
return get(path, def, String.class); return get(path, def, String.class);
@@ -298,23 +841,53 @@ public interface ConfigureSection {
return getList(path, DataFunction.castObject(Character.class)); return getList(path, DataFunction.castObject(Character.class));
} }
/**
* Get the specific type of collection from the section.
*
* @param path The path to get the collection from
* @param constructor The constructor of the collection
* @param parser The function to parse the values
* @param <T> The type of the values
* @param <C> The type of the collection
* @return The collection of values
*/
default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path, default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path,
@NotNull Supplier<C> constructor, @NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
return parseCollection(getList(path), constructor, parser); return parseCollection(getList(path), constructor, parser);
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @return The stream of values
*/
default @NotNull Stream<?> stream(@NotNull String path) { default @NotNull Stream<?> stream(@NotNull String path) {
List<?> values = getList(path); List<?> values = getList(path);
return values == null ? Stream.empty() : values.stream(); return values == null ? Stream.empty() : values.stream();
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @param parser The function to parse the values
* @param <T> The type of the values
* @return The stream of values
*/
default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) { default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) {
return stream(path).map(parser); 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( 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 @NotNull DataFunction<Object, T> parser
) { ) {
C values = constructor.get(); C values = constructor.get();
@@ -1,12 +1,21 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull; import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.*;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/**
* ConfigureSource represents the source of configuration,
* which can be a file, a database, or any other source.
*
* @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< public abstract class ConfigureSource<
SECTION extends ConfigureSection, ORIGINAL, SECTION extends ConfigureSection, ORIGINAL,
SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>> SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>>
@@ -24,28 +33,46 @@ public abstract class ConfigureSource<
return holder; return holder;
} }
public void reload() throws Exception { public void reload() throws Exception {
onReload(); // 调用重写的Reload方法 onReload(); // 调用重写的Reload方法
this.lastUpdateMillis = System.currentTimeMillis(); this.lastUpdateMillis = System.currentTimeMillis();
} }
protected abstract SELF self(); @Contract(pure = true)
@ApiStatus.Internal
protected abstract @NotNull SELF self();
/** /**
* @return Original configuration object * @return The original configuration object.
*/ */
@Contract(pure = true)
public abstract @NotNull ORIGINAL original(); public abstract @NotNull ORIGINAL original();
/** /**
* @return The root {@link ConfigureSection} * @return The root {@link ConfigureSection}, which represents the entire configuration.
*/ */
public abstract @NotNull SECTION section(); public abstract @NotNull SECTION section();
/**
* Save the whole configuration.
*
* @throws Exception If any error occurs while saving.
*/
public abstract void save() throws Exception; public abstract void save() throws Exception;
/**
* Reload the configuration.
* <br>This used for implementation, for external usage, use {@link #reload()}
*
* @throws Exception If any error occurs while reloading.
*/
@ApiStatus.OverrideOnly
protected abstract void onReload() throws Exception; protected abstract void onReload() throws Exception;
public char pathSeparator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public long getLastUpdateMillis() { public long getLastUpdateMillis() {
return this.lastUpdateMillis; return this.lastUpdateMillis;
} }
@@ -54,14 +81,20 @@ public abstract class ConfigureSource<
return getLastUpdateMillis() > parsedTime; return getLastUpdateMillis() > parsedTime;
} }
/**
* Source also represents the root section, so it has no parent
*
* @return null
*/
@Override @Override
@Contract(pure = true, value = "->null")
public @Nullable ConfigureSection parent() { public @Nullable ConfigureSection parent() {
return null; // Source also represents the root section, so it has no parent return null;
} }
@Override @Override
public @NotNull ConfigureSource<?, ?, ?> source() { public @NotNull String path() {
return self(); return "";
} }
@Override @Override
@@ -69,34 +102,29 @@ public abstract class ConfigureSource<
return section().getValues(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 @Override
public void set(@NotNull String path, @Nullable Object value) { public void set(@NotNull String path, @Nullable Object value) {
section().set(path, value); section().set(path, value);
} }
@Override @Override
public boolean contains(@NotNull String path) { public void remove(@NotNull String path) {
return section().contains(path); section().remove(path);
}
@Override
public boolean isList(@NotNull String path) {
return section().isList(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return section().getList(path);
}
@Override
public boolean isSection(@NotNull String path) {
return section().isSection(path);
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
return section().getSection(path);
} }
@Override @Override
@@ -113,7 +113,7 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* @param override Whether to overwrite existing configured value * @param override Whether to overwrite existing configured value
*/ */
public void setDefault(boolean override) { public void setDefault(boolean override) {
if (!override && config().contains(path())) return; if (config().contains(path()) && !override) return; // Skip if the value is already set
set(defaults()); // Set the default value set(defaults()); // Set the default value
} }
@@ -4,6 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -20,7 +21,6 @@ public class ValueManifest<T> {
protected @NotNull Supplier<@Nullable T> defaultSupplier; protected @NotNull Supplier<@Nullable T> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) { public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null); this(type, () -> null, EMPTY_INITIALIZER, null, null);
} }
@@ -93,12 +93,12 @@ public class ValueManifest<T> {
public @NotNull String path() { public @NotNull String path() {
if (path != null) return 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() { public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.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() { public @NotNull ConfigureSource<?, ?, ?> config() {
@@ -109,16 +109,17 @@ public class ValueManifest<T> {
return holder().metadata(path()); return holder().metadata(path());
} }
@ApiStatus.Internal
protected Object getData() { protected Object getData() {
return config().get(path()); return config().get(path());
} }
@ApiStatus.Internal
protected void setData(@Nullable Object value) { protected void setData(@Nullable Object value) {
config().set(path(), value); config().set(path(), value);
} }
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
}; };
} }
+8 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -33,6 +33,13 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -7,7 +7,7 @@ import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue; import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"}) @HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration { public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver") @ConfigPath("driver")
@HeaderComments({ @HeaderComments({
@@ -16,18 +16,19 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver", "- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver", "- MariaDB(推荐): org.mariadb.jdbc.Driver",
}) })
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of( ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver" String.class, "com.mysql.cj.jdbc.Driver"
); );
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1"); ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306); ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft"); ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root"); ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password"); ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
protected static String buildJDBC() { ConfigValue<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()); 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); provider.initialize(TEST);
System.out.println("> Test Inner value before:"); 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; double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after); 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 Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve()); System.out.println(TEST.INSTANCE.STATUS.resolve());
} }
@@ -1,11 +1,7 @@
package cc.carm.lib.configuration.demo.tests.conf; package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; import cc.carm.lib.configuration.annotation.*;
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.demo.tests.model.ItemStack; import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord; import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
@@ -29,7 +25,8 @@ import java.util.UUID;
public interface DemoConfiguration extends Configuration { public interface DemoConfiguration extends Configuration {
@ConfigPath(root = true) @ConfigPath(root = true)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 1.0D); @ConfigVersion(2)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 2.0D);
@ConfigPath(root = true) @ConfigPath(root = true)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"}) @FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
@@ -39,15 +36,15 @@ public interface DemoConfiguration extends Configuration {
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"}) @FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS); ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。 @HeaderComments({"空值测试"})
// 注意: 若对应类也有注解,则优先使用类的注解。 @InlineComment("空值Inline注释")
Class<?> DATABASE = DatabaseConfiguration.class; ConfiguredMap<String, String> EMPTY = ConfiguredMap.builderOf(String.class, String.class)
.asLinkedMap().fromString()
.build();
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。 @HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。 @InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = "info.*") // 通过注解给配置添加注释。
ConfiguredList<UserRecord> ALLOWLISTS = ConfiguredList.builderOf(UserRecord.class).fromSection() ConfiguredList<UserRecord> ALLOWLISTS = ConfiguredList.builderOf(UserRecord.class).fromSection()
.parse(UserRecord::deserialize).serialize(UserRecord::serialize) .parse(UserRecord::deserialize).serialize(UserRecord::serialize)
.defaults(UserRecord.CARM).build(); .defaults(UserRecord.CARM).build();
@@ -81,6 +78,7 @@ public interface DemoConfiguration extends Configuration {
.build(); .build();
@HeaderComments({"内部类的内部类测试", "通过这种方式,您可以轻易实现多层次的配置文件结构"}) @HeaderComments({"内部类的内部类测试", "通过这种方式,您可以轻易实现多层次的配置文件结构"})
@FooterComments({"-------------"})
public interface That extends Configuration { public interface That extends Configuration {
ConfiguredList<UUID> OPERATORS = ConfiguredList ConfiguredList<UUID> OPERATORS = ConfiguredList
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test") @HeaderComments("Inner Test")
public class InstanceConfig implements Configuration { public class InstanceConfig implements Configuration {
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(1.0D); public final ConfigValue<Double> STATUS = ConfiguredValue.of(1.0D);
} }
@@ -18,7 +18,9 @@ public class RegistryConfig implements Configuration {
@ConfigPath("test.user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("test.user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@FooterComments({"12313213212"}) @FooterComments({"12313213212"})
public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection() @InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。
public final ConfigValue<UserRecord> OWNER = ConfiguredValue.builderOf(UserRecord.class).fromSection()
.defaults(new UserRecord("Carm", UUID.randomUUID())) .defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section)) .parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build(); .serialize((holder, data) -> data.serialize()).build();
@@ -36,6 +36,7 @@ public class UserRecord extends AbstractRecord {
} }
public static UserRecord deserialize(ConfigureSection section) { public static UserRecord deserialize(ConfigureSection section) {
System.out.println("> Deserializing -> " + section.fullPath());
String name = section.getString("name"); String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null"); if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid"); String uuidString = section.getString("info.uuid");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -33,7 +33,7 @@ public @interface InlineComment {
* If the regex is not empty, the comment will be added to * If the regex is not empty, the comment will be added to
* all sub paths if the regex matches the value. * all sub paths if the regex matches the value.
* If the regex is empty, the comment will be added to the current path. * If the regex is empty, the comment will be added to the current path.
* <p> e.g. for section, set <b>{"^foo", "*", "bar"}</b> will be set like * <p> e.g. for section, set <b>"foo.*.bar"</b> will be set like
* <blockquote><pre> * <blockquote><pre>
* section: * section:
* foo: * foo:
@@ -44,6 +44,9 @@ public @interface InlineComment {
* bar: "foobar" # Comment Contents * bar: "foobar" # Comment Contents
* </pre></blockquote> * </pre></blockquote>
* *
* <p><i>Some implements may not support this feature
* in list values or other complex structures.</i>
*
* @return The path regexes of this comment * @return The path regexes of this comment
*/ */
@NotNull @NotNull
@@ -9,7 +9,12 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface InlineComments { public @interface InlineComments {
/**
* Multiple inline comments support.
*
* @return inline comment contents.
* @see InlineComment
*/
InlineComment[] value(); InlineComment[] value();
} }
@@ -0,0 +1,66 @@
package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
public class Commentable {
private Commentable() {
throw new UnsupportedOperationException();
}
public static void registerMeta(@NotNull ConfigurationInitializer initializer) {
CommentableMeta.register(initializer);
}
public static @Nullable String getInlineComment(@NotNull ConfigurationHolder<?> holder, @NotNull String path) {
String comment = getInlineComment(holder, path, null);
if (comment != null) return comment;
String sep = String.valueOf(holder.option(StandardOptions.PATH_SEPARATOR));
// If the comment is not found, try to get the comment from the parent section
String[] keys = path.split(Pattern.quote(sep));
if (keys.length == 1) return null;
// Try every possible parent key&child key combination
for (int i = 1; i < keys.length; i++) {
String parentKey = String.join(sep, Arrays.copyOfRange(keys, 0, i));
String childKey = String.join(sep, Arrays.copyOfRange(keys, i, keys.length));
comment = getInlineComment(holder, parentKey, childKey);
if (comment != null) return comment;
}
return null;
}
public static @Nullable String getInlineComment(@NotNull ConfigurationHolder<?> holder, @NotNull String path, @Nullable String sectionKey) {
Map<String, String> pathComment = holder.metadata(path).get(CommentableMeta.INLINE);
if (pathComment == null || pathComment.isEmpty()) return null;
if (sectionKey == null) return pathComment.get(null);
for (Map.Entry<String/*regex*/, String/*content*/> entry : pathComment.entrySet()) {
if (entry.getKey() == null) continue;
if (Objects.equals(entry.getKey(), sectionKey)) return entry.getValue();
Pattern pattern = Pattern.compile(entry.getKey().replace(".", "\\.").replace("*", "(.*)"));
if (pattern.matcher(sectionKey).matches()) return entry.getValue();
}
return null;
}
public static @Nullable List<String> getHeaderComments(@NotNull ConfigurationHolder<?> holder, @Nullable String path) {
return holder.metadata(path).get(CommentableMeta.HEADER);
}
public static @Nullable List<String> getFooterComments(@NotNull ConfigurationHolder<?> holder, @Nullable String path) {
return holder.metadata(path).get(CommentableMeta.FOOTER);
}
}
@@ -49,6 +49,17 @@ public interface CommentableMeta {
FooterComments.class, FOOTER, FooterComments.class, FOOTER,
a -> Arrays.asList(a.value()) 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 -> { initializer.registerAnnotation(InlineComments.class, INLINE, a -> {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
for (InlineComment comment : a.value()) { for (InlineComment comment : a.value()) {
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.option; package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.source.option.ConfigurationOption; import cc.carm.lib.configuration.source.option.ConfigurationOption;
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -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,148 +3,54 @@ package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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) { public static MemorySection of() {
return new MemorySection(source, new LinkedHashMap<>(), null); return of(new LinkedHashMap<>());
} }
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source, public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) {
@Nullable Map<?, ?> data) { return of(() -> {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null); Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
} }
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source; public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) {
protected final @NotNull Map<String, Object> data; return of(data.get());
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());
}
}
} }
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) { public static MemorySection of(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this); return of(data, null, "");
} }
protected @NotNull MemorySection createChild() { public static MemorySection of(@Nullable MemorySection parent, @NotNull String path) {
return createChild(new LinkedHashMap<>()); return of(new LinkedHashMap<>(), parent, path);
}
public static MemorySection of(@NotNull Map<?, ?> data, @Nullable MemorySection parent, @NotNull String path) {
return new MemorySection(data, parent, path);
}
public MemorySection(@NotNull Map<?, ?> raw, @Nullable MemorySection parent, @NotNull String path) {
super(parent, path);
migrate(raw);
} }
@Override @Override
public @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() { public @NotNull MemorySection self() {
return this.source; return this;
}
public @NotNull Map<String, Object> data() {
return this.data;
}
public @Nullable MemorySection parent() {
return this.parent;
} }
@Override @Override
public @NotNull Map<String, Object> getValues(boolean deep) { public @NotNull MemorySection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return Collections.unmodifiableMap(deep ? mapChildrenValues(this, null, true) : data()); return new MemorySection(data, this, path);
} }
@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);
}
}
@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(separator());
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(separator());
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 + separator()) + 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());
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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>easyconfiguration-parent</artifactId>
<version>4.0.11</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>easyconfiguration-feature-text</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</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,196 @@
package cc.carm.lib.configuration.value.text;
import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.AbstractConfigBuilder;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.value.ValueManifest;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import cc.carm.lib.configuration.value.text.data.TextContents;
import cc.carm.lib.configuration.value.text.function.TextDispatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
* @param <MSG> The type of the message
* @param <RECEIVER> The type of the receiver
*/
public class ConfiguredText<MSG, RECEIVER> extends ConfiguredValue<TextContents> {
public static <M, R> Builder<M, R, ?> builder() {
return new StardardBuilder<>();
}
public static final ValueType<TextContents> TEXT_TYPE = ValueType.of(TextContents.class);
public static final ValueAdapter<TextContents> TEXT_ADAPTER = new ValueAdapter<>(TEXT_TYPE,
(h, t, d) -> d.serialize(),
(h, t, d) -> TextContents.deserialize(d)
);
protected final @NotNull BiFunction<RECEIVER, String, String> parser;
protected final @NotNull BiFunction<RECEIVER, String, MSG> compiler;
protected final @NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher;
protected final @NotNull String[] params; // The parameters of the message.
public ConfiguredText(@NotNull ValueManifest<TextContents> manifest,
@NotNull BiFunction<RECEIVER, String, String> parser,
@NotNull BiFunction<RECEIVER, String, MSG> compiler,
@NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher,
@NotNull String[] params) {
super(manifest, TEXT_ADAPTER);
this.parser = parser;
this.compiler = compiler;
this.dispatcher = dispatcher;
this.params = params;
}
public TextDispatcher<MSG, RECEIVER, ?> prepare(@NotNull Object... values) {
return new PreparedText<MSG, RECEIVER>(resolve(), this.params)
.parser(this.parser).compiler(this.compiler)
.dispatcher(this.dispatcher).placeholders(values);
}
/**
* Parse the message for the receiver.
*
* @param receiver The receiver of the message.
* @param values The values to replace the {@link #params}.
* @return The parsed message.
*/
public List<String> parse(@Nullable RECEIVER receiver, @NotNull Object... values) {
return prepare(values).parse(receiver);
}
/**
* Parse the message for the receiver and send it.
*
* @param receiver The receiver of the message.
* @param values The values to replace the {@link #params}.
* @return The parsed message.
*/
public String parseLine(@Nullable RECEIVER receiver, @NotNull Object... values) {
return prepare(values).parseLine(receiver);
}
/**
* Compile the message for the receiver.
*
* @param receiver The receiver of the message.
* @param values The values to replace the {@link #params}.
* @return The compiled message.
*/
public List<MSG> compile(@Nullable RECEIVER receiver, @NotNull Object... values) {
return prepare(values).compile(receiver);
}
/**
* Compile the message for the receiver and send it.
*
* @param receiver The receiver of the message.
* @param values The values to replace the {@link #params}.
* @return The compiled message.
*/
public MSG compileLine(@Nullable RECEIVER receiver, @NotNull Object... values) {
return prepare(values).compileLine(receiver);
}
/**
* Send the message to the receiver.
*
* @param receiver The receiver of the message.
* @param values The values to replace the {@link #params}.
*/
public void sendTo(@NotNull RECEIVER receiver, @NotNull Object... values) {
prepare(values).to(receiver);
}
/**
* Send the message to the multiple receivers.
*
* @param receivers The receivers of the message.
* @param values The values to replace the {@link #params}.
*/
public void sendToAll(@NotNull Iterable<? extends RECEIVER> receivers, @NotNull Object... values) {
prepare(values).to(receivers);
}
public abstract static class Builder<MSG, RECEIVER, SELF extends Builder<MSG, RECEIVER, SELF>>
extends AbstractConfigBuilder<TextContents, ConfiguredText<MSG, RECEIVER>, ConfigurationHolder<?>, SELF> {
protected @NotNull TextContents.Builder defaultBuilder = TextContents.builder();
protected @NotNull String[] params = new String[0];
protected @NotNull BiFunction<RECEIVER, String, String> parser = (r, s) -> s;
protected @NotNull BiFunction<RECEIVER, String, MSG> compiler = (r, s) -> {
throw new IllegalStateException("Compiler not supplied.");
};
protected @NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher = (r, l) -> {
throw new IllegalStateException("Dispatcher not supplied.");
};
protected Builder() {
super(ConfigurationHolder.class, TEXT_TYPE);
defaults(() -> defaultBuilder.build()); // Set the default value from the default builder.
}
public @NotNull SELF parser(@NotNull BiFunction<RECEIVER, String, String> parser) {
this.parser = parser;
return self();
}
public @NotNull SELF compiler(@NotNull BiFunction<RECEIVER, String, MSG> compiler) {
this.compiler = compiler;
return self();
}
public @NotNull SELF dispatcher(@NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher) {
this.dispatcher = dispatcher;
return self();
}
public @NotNull SELF params(@NotNull String... params) {
this.params = params;
return self();
}
public @NotNull SELF defaults(@NotNull Consumer<TextContents.Builder> consumer) {
consumer.accept(this.defaultBuilder);
return self();
}
public @NotNull SELF defaults(@NotNull String... contents) {
return defaults(builder -> builder.set(contents));
}
public @NotNull SELF defaults(@NotNull Iterable<String> contents) {
return defaults(builder -> builder.set(contents));
}
public @NotNull SELF optional(@NotNull String id, @NotNull String... lines) {
return defaults(builder -> builder.optional(id, lines));
}
public @NotNull SELF optional(@NotNull String id, @NotNull Iterable<String> lines) {
return defaults(builder -> builder.optional(id, lines));
}
@Override
public @NotNull ConfiguredText<MSG, RECEIVER> build() {
return new ConfiguredText<>(buildManifest(), this.parser, this.compiler, this.dispatcher, this.params);
}
}
public static class StardardBuilder<MSG, RECEIVER> extends Builder<MSG, RECEIVER, StardardBuilder<MSG, RECEIVER>> {
@Override
protected @NotNull StardardBuilder<MSG, RECEIVER> self() {
return this;
}
}
}
@@ -0,0 +1,32 @@
package cc.carm.lib.configuration.value.text;
import cc.carm.lib.configuration.value.text.data.TextContents;
import cc.carm.lib.configuration.value.text.function.TextDispatcher;
import org.jetbrains.annotations.NotNull;
public class PreparedText<MSG, RECEIVER> extends TextDispatcher<MSG, RECEIVER, PreparedText<MSG, RECEIVER>> {
protected final @NotNull TextContents texts;
public PreparedText(@NotNull TextContents texts, @NotNull String... params) {
this.params = params;
this.texts = texts;
}
@Override
public TextContents texts() {
return this.texts;
}
@Override
public PreparedText<MSG, RECEIVER> self() {
return this;
}
public PreparedText<MSG, RECEIVER> insert(@NotNull String key,
@NotNull ConfiguredText<?, RECEIVER> message,
@NotNull Object... values) {
return insert(key, receiver -> message.parse(receiver, values));
}
}
@@ -0,0 +1,160 @@
package cc.carm.lib.configuration.value.text.data;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class TextContents {
public static Builder builder() {
return new Builder();
}
public static TextContents of(@NotNull List<String> lines, @NotNull Map<String, List<String>> optional) {
return new TextContents(lines, optional);
}
protected final @NotNull List<String> lines;
protected final @NotNull Map<String, List<String>> optional;
public TextContents(@NotNull List<String> lines, @NotNull Map<String, List<String>> optional) {
this.lines = lines;
this.optional = optional;
}
public boolean isEmpty() {
return lines.isEmpty() || lines.stream().allMatch(String::isEmpty);
}
public @NotNull List<String> lines() {
return lines;
}
public @NotNull Map<String, List<String>> optionalLines() {
return optional;
}
public @Nullable Object serialize() {
if (optional.isEmpty()) {
if (lines.isEmpty()) return null;
else if (lines.size() == 1) return lines.get(0);
else return lines;
} else {
Map<String, Object> map = new LinkedHashMap<>();
map.put("contents", lines);
map.put("optional", optional);
return map;
}
}
public static @Nullable TextContents deserialize(@NotNull Object data) {
Builder builder = builder();
if (data instanceof String) {
return builder.set((String) data).build();
} else if (data instanceof List<?>) {
((List<?>) data).stream().map(Object::toString).forEach(builder::add);
return builder.build();
} else if (data instanceof ConfigureSection) {
ConfigureSection section = (ConfigureSection) data;
builder.set(section.getStringList("contents"));
ConfigureSection optionalSection = section.getSection("optional");
if (optionalSection != null) {
for (String key : optionalSection.getKeys(false)) {
builder.optional(key, optionalSection.getStringList(key));
}
}
return builder.build();
}
return null;
}
public static class Builder {
protected List<String> lines = new ArrayList<>();
protected Map<String, List<String>> optional = new HashMap<>();
/**
* Add lines to the contents
*
* @param lines lines to add
* @return this builder
*/
public Builder add(@NotNull String... lines) {
this.lines.addAll(Arrays.asList(lines));
return this;
}
/**
* Add lines to the contents
*
* @param lines lines to add
* @return this builder
*/
public Builder add(@NotNull Iterable<String> lines) {
lines.forEach(this.lines::add);
return this;
}
/**
* Set the lines of the contents
*
* @param lines lines to set
* @return this builder
*/
public Builder set(@NotNull String... lines) {
this.lines = Arrays.asList(lines);
return this;
}
/**
* Set the lines of the contents
*
* @param lines lines to set
* @return this builder
*/
public Builder set(@NotNull Iterable<String> lines) {
this.lines = new ArrayList<>();
lines.forEach(this.lines::add);
return this;
}
/**
* Add optional lines to the contents
*
* @param key key of the optional lines
* @param lines lines to add
* @return this builder
*/
public Builder optional(@NotNull String key, @NotNull String... lines) {
optional.put(key, Arrays.asList(lines));
return this;
}
/**
* Add optional lines to the contents
*
* @param key key of the optional lines
* @param lines lines to add
* @return this builder
*/
public Builder optional(@NotNull String key, @NotNull Iterable<String> lines) {
List<String> list = new ArrayList<>();
lines.forEach(list::add);
optional.put(key, list);
return this;
}
/**
* @return The built TextContents
*/
public TextContents build() {
return of(lines, optional);
}
}
}
@@ -0,0 +1,299 @@
package cc.carm.lib.configuration.value.text.function;
import cc.carm.lib.configuration.value.text.data.TextContents;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public abstract class ContentHandler<RECEIVER, SELF extends ContentHandler<RECEIVER, SELF>> {
/**
* 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>
* <li>@content-id@{1,1}</li>
* </ul>
*/
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;
protected String lineSeparator = System.lineSeparator();
/**
* Used to store the placeholders of the message
*/
protected @NotNull Map<String, Object> placeholders = new HashMap<>();
protected @NotNull UnaryOperator<String> paramFormatter = DEFAULT_PARAM_BUILDER;
protected @NotNull String[] params;
/**
* Used to store the insertion of the message
*/
protected @NotNull Map<String, @Nullable Function<RECEIVER, List<String>>> insertion = new HashMap<>();
protected boolean disableInsertion = false;
public abstract SELF self();
/**
* Disable the insertion of the text.
* <br> If the insertion is disabled, the text will be parsed directly.
*
* @return the current {@link ContentHandler} instance
*/
public SELF disableInsertion() {
this.disableInsertion = true;
return self();
}
/**
* Enable the insertion of the text.
*
* @return the current {@link ContentHandler} instance
*/
public SELF enableInsertion() {
this.disableInsertion = false;
return self();
}
/**
* Set the line separator for the text.
*
* @param lineSeparator the line separator, default is {@link System#lineSeparator()}
* @return the current {@link ContentHandler} instance
*/
public SELF lineSeparator(@NotNull String lineSeparator) {
this.lineSeparator = lineSeparator;
return self();
}
/**
* Set all the placeholders for the text.
* <br> Will override the previous placeholders modifications.
*
* @param placeholders the placeholders
* @return the current {@link ContentHandler} instance
*/
public SELF placeholders(@NotNull Map<String, Object> placeholders) {
this.placeholders = placeholders;
return self();
}
/**
* Set the placeholders for the text.
*
* @param consumer the placeholders
* @return the current {@link ContentHandler} instance
*/
public SELF placeholders(@NotNull Consumer<Map<String, Object>> consumer) {
consumer.accept(this.placeholders);
return self();
}
/**
* Set the placeholders for the text.
*
* @param values The values to replace the {@link #params(String...)}.
* @return the current {@link ContentHandler} instance
*/
public SELF placeholders(@Nullable Object... values) {
return placeholders(map -> map.putAll(buildParams(this.paramFormatter, this.params, values)));
}
/**
* Set the placeholder for the text.
*
* @param key the key of the placeholder
* @param value the value of the placeholder
* @return the current {@link ContentHandler} instance
*/
public SELF placeholder(@NotNull String key, @Nullable Object value) {
this.placeholders.put(paramFormatter.apply(key), value);
return self();
}
/**
* Set the params for the text,
* used for {@link #placeholders(Object...)} to build the placeholders.
*
* @param params the params
* @return the current {@link ContentHandler} instance
*/
public SELF params(@NotNull String... params) {
this.params = params;
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.
*
* @param id the id of the insertion text
* @param linesSupplier to supply the lines to insert
* @return the current {@link ContentHandler} instance
*/
public SELF insert(@NotNull String id, @NotNull Function<RECEIVER, List<String>> linesSupplier) {
this.insertion.put(id, linesSupplier);
return self();
}
/**
* Insert the specific contents by the id.
*
* @param id the id of the insertion text
* @param lines the lines to insert
* @return the current {@link ContentHandler} instance
*/
public SELF insert(@NotNull String id, @NotNull String... lines) {
return insert(id, Arrays.asList(lines));
}
/**
* Insert the specific contents by the id.
*
* @param id the id of the insertion text
* @param lines the lines to insert
* @return the current {@link ContentHandler} instance
*/
public SELF insert(@NotNull String id, @NotNull List<String> lines) {
return insert(id, receiver -> lines);
}
/**
* Set the parser for the text.
*
* @param parser The parser
* @return The current {@link ContentHandler} instance
*/
public SELF parser(@NotNull BiFunction<RECEIVER, String, String> parser) {
this.parser = parser;
return self();
}
/**
* Parse the supplied single text for the receiver.
*
* @param receiver the receiver
* @param text the text to parse
* @return the parsed text
*/
protected @Nullable String parse(@Nullable RECEIVER receiver, @NotNull String text) {
return this.parser.apply(receiver, setPlaceholders(text, this.placeholders));
}
public void handle(@NotNull TextContents contents, @Nullable RECEIVER receiver,
@NotNull Consumer<String> lineConsumer) {
if (contents.isEmpty()) return; // Nothing to parse
if (this.disableInsertion) {
contents.lines().forEach(line -> lineConsumer.accept(parse(receiver, line)));
return; // Simple parsed
}
for (String line : contents.lines()) {
Matcher insertMatcher = INSERT_PATTERN.matcher(line);
if (insertMatcher.matches()) {
doInsert(insertMatcher, receiver, lineConsumer);
continue;
}
Matcher enableMatcher = ENABLE_PATTERN.matcher(line);
if (enableMatcher.matches()) {
if (this.insertion.containsKey(enableMatcher.group("id"))) {
lineConsumer.accept(parse(receiver, enableMatcher.group("content")));
}
continue;
}
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;
String parsed = messages;
for (Map.Entry<String, Object> entry : placeholders.entrySet()) {
parsed = parsed.replace(entry.getKey(), entry.getValue() == null ? "" : entry.getValue().toString());
}
return parsed;
}
public static Map<String, Object> buildParams(@NotNull UnaryOperator<String> paramBuilder,
@Nullable String[] params, @Nullable Object[] values) {
Map<String, Object> map = new HashMap<>();
if (params == null || params.length == 0) return map;
for (int i = 0; i < params.length; i++) {
map.put(paramBuilder.apply(params[i]), (values != null && values.length > i) ? values[i] : "?");
}
return map;
}
}
@@ -0,0 +1,45 @@
package cc.carm.lib.configuration.value.text.function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.BiFunction;
public abstract class TextCompiler<MSG, RECEIVER, SELF extends TextCompiler<MSG, RECEIVER, SELF>> extends TextParser<RECEIVER, SELF> {
protected BiFunction<RECEIVER, String, MSG> compiler = (receiver, value) -> null;
/**
* Set the text compiler.
*
* @param compiler The text compiler.
* @return The current {@link TextCompiler} instance.
*/
public SELF compiler(@NotNull BiFunction<RECEIVER, String, MSG> compiler) {
this.compiler = compiler;
return self();
}
/**
* Compile the text for specific receiver.
*
* @param receiver The receiver.
* @return The compiled text.
* @see #parse(Object, BiFunction)
*/
public @NotNull List<MSG> compile(@Nullable RECEIVER receiver) {
return parse(receiver, this.compiler);
}
/**
* Compile the singleton text for specific receiver.
*
* @param receiver The receiver.
* @return The compiled text.
* @see #parseLine(Object, BiFunction)
*/
public @Nullable MSG compileLine(@Nullable RECEIVER receiver) {
return parseLine(receiver, this.compiler);
}
}
@@ -0,0 +1,61 @@
package cc.carm.lib.configuration.value.text.function;
import cc.carm.lib.configuration.value.text.data.TextContents;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public abstract class TextDispatcher<MSG, RECEIVER, SELF extends TextDispatcher<MSG, RECEIVER, SELF>> extends TextCompiler<MSG, RECEIVER, SELF> {
protected @NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher = (receiver, msg) -> {
};
/**
* Set the dispatcher to send the message to the receiver
*
* @param dispatcher the dispatcher
* @return {@link TextDispatcher}
*/
public SELF dispatcher(@NotNull BiConsumer<RECEIVER, List<MSG>> dispatcher) {
this.dispatcher = dispatcher;
return self();
}
/**
* Dispatch the message to the receiver
*
* @param receivers the receivers
*/
@SafeVarargs
public final void to(@NotNull RECEIVER... receivers) {
if (receivers.length == 0) return;
to(Arrays.asList(receivers));
}
/**
* Dispatch the message to the receiver
*
* @param receivers the receivers
*/
public void to(@NotNull Iterable<? extends RECEIVER> receivers) {
for (RECEIVER receiver : receivers) {
List<MSG> msg = compile(receiver);
if (msg.isEmpty()) return;
dispatcher.accept(receiver, msg);
}
}
/**
* Dispatch the message to the receiver
*
* @param receivers the receivers
*/
public void to(@NotNull Supplier<Iterable<? extends RECEIVER>> receivers) {
to(receivers.get());
}
}
@@ -0,0 +1,74 @@
package cc.carm.lib.configuration.value.text.function;
import cc.carm.lib.configuration.value.text.data.TextContents;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public abstract class TextParser<RECEIVER, SELF extends TextParser<RECEIVER, SELF>>
extends ContentHandler<RECEIVER, SELF> {
public abstract TextContents texts();
/**
* Parse the texts for the receiver.
*
* @param receiver the receiver
* @return the parsed line
*/
public List<String> parse(@Nullable RECEIVER receiver) {
List<String> result = new ArrayList<>();
handle(receiver, result::add);
return result;
}
/**
* Parse the texts for the receiver.
*
* @param receiver the receiver
* @param compiler the compiler
* @param <V> the type of the message
* @return the parsed line
*/
public <V> List<V> parse(@Nullable RECEIVER receiver, @NotNull BiFunction<RECEIVER, String, V> compiler) {
List<V> result = new ArrayList<>();
handle(receiver, s -> result.add(compiler.apply(receiver, s)));
return result;
}
/**
* Parse the texts as a single line for the receiver.
*
* @param receiver the receiver
* @return the parsed line
*/
public @Nullable String parseLine(@Nullable RECEIVER receiver) {
if (texts().isEmpty()) return null;
StringBuilder builder = new StringBuilder();
handle(receiver, s -> builder.append(s).append(this.lineSeparator));
// Remove the last line separator, if it exists
if (builder.length() > 0) builder.delete(builder.length() - this.lineSeparator.length(), builder.length());
return builder.toString();
}
/**
* Parse the texts as a single line for the receiver.
*
* @param receiver the receiver
* @param <V> the type of the message
* @return the parsed line
*/
public <V> @Nullable V parseLine(@Nullable RECEIVER receiver, @NotNull BiFunction<RECEIVER, String, V> compiler) {
return Optional.ofNullable(parseLine(receiver)).map(s -> compiler.apply(receiver, s)).orElse(null);
}
public void handle(@Nullable RECEIVER receiver, @NotNull Consumer<String> lineConsumer) {
handle(texts(), receiver, lineConsumer);
}
}
@@ -0,0 +1,49 @@
package cc.carm.lib.configuration.value.text.tests;
import cc.carm.lib.configuration.source.ConfigurationHolder;
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 {
public static final String[] WEBSITES = new String[]{
"https://carm.cc",
"https://www.baidu.com",
"https://www.google.com"
};
@Test
public void test() {
ConfigurationHolder<?> holder = JSONConfigFactory.from(new File("target/messages.json")).build();
holder.initialize(AppMessages.class);
System.out.println("--------------------------");
AppMessages.WELCOME.prepare()
.placeholders("Carm")
.insert("guidance")
.insert("websites", WEBSITES)
.to(System.out);
System.out.println("--------------------------");
AppMessages.NOT_AVAILABLE.sendTo(System.out);
System.out.println("--------------------------");
AppMessages.NO_PERMISSION.sendTo(System.out);
System.out.println("--------------------------");
}
}
@@ -0,0 +1,51 @@
package cc.carm.lib.configuration.value.text.tests;
import cc.carm.lib.configuration.value.text.PreparedText;
import cc.carm.lib.configuration.value.text.data.TextContents;
import org.junit.Test;
import java.io.PrintStream;
import java.util.*;
public class ParseTest {
@Test
public void test() {
List<String> lines = new ArrayList<>();
lines.add("Hello, %(name)");
lines.add("#more-creating#{1}");
lines.add("This is a test message");
lines.add("#guidance#");
lines.add("{- }#websites#{0,1}");
lines.add("Thanks for your reading!");
lines.add("?[click]");
lines.add("?[click]Click to see more!");
Map<String, List<String>> optional = new HashMap<>();
optional.put("guidance", Arrays.asList("To get more information for %(name), see:"));
optional.put("websites", Arrays.asList("https://www.baidu.com", "https://www.google.com"));
TextContents textContents = new TextContents(lines, optional);
PreparedText<String, PrintStream> msg = new PreparedText<String, PrintStream>(textContents)
.dispatcher((p, s) -> s.forEach(p::println))
.parser((p, s) -> s)
.compiler((p, s) -> s);
msg.placeholder("name", "Carm")
.insert("guidance")
.insert("click")
.insert("websites", "Baidu", "Bilibili", "Google");
System.out.println("----------------------------");
msg.to(System.out);
System.out.println("----------------------------");
System.out.println(msg.compileLine(System.out));
System.out.println("----------------------------");
}
}
@@ -0,0 +1,28 @@
package cc.carm.lib.configuration.value.text.tests.conf;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
@ConfigPath(root = true)
public interface AppMessages extends Configuration {
ConfiguredMsg WELCOME = ConfiguredMsg.builder()
.defaults(
"Hello, %(name)",
"#more-creating#{1}",
"This is a test message",
"#guidance#",
"{- }#websites#{0,1}",
"Thanks for your reading!")
.optional("guidance", "To get more information for %(name), see:")
.params("name").build();
ConfiguredMsg NO_PERMISSION = ConfiguredMsg.builder()
.defaults("Sorry! But you don't have permissions to do this.")
.build();
ConfiguredMsg NOT_AVAILABLE = ConfiguredMsg.builder()
.defaults("Error! Service is not available now.", "Please contact your system manager.")
.build();
}
@@ -0,0 +1,47 @@
package cc.carm.lib.configuration.value.text.tests.conf;
import cc.carm.lib.configuration.value.ValueManifest;
import cc.carm.lib.configuration.value.text.ConfiguredText;
import cc.carm.lib.configuration.value.text.data.TextContents;
import org.jetbrains.annotations.NotNull;
import java.io.PrintStream;
public class ConfiguredMsg extends ConfiguredText<String, PrintStream> {
public static @NotNull MsgBuilder builder() {
return new MsgBuilder();
}
public static @NotNull ConfiguredMsg of(@NotNull String... text) {
return builder().defaults(text).build();
}
public ConfiguredMsg(@NotNull ValueManifest<TextContents> manifest, @NotNull String[] params) {
super(
manifest,
(p, s) -> s,
(p, s) -> s,
(p, s) -> s.forEach(p::println),
params
);
}
public void print(@NotNull Object... values) {
sendTo(System.out, values);
}
public static class MsgBuilder extends Builder<String, PrintStream, MsgBuilder> {
@Override
protected @NotNull MsgBuilder self() {
return this;
}
@Override
public @NotNull ConfiguredMsg build() {
return new ConfiguredMsg(buildManifest(), params);
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation; package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
* *
* @return the version of the configuration field * @return the version of the configuration field
*/ */
@Range(from = 0, to = Integer.MAX_VALUE) @Range(from = 0, to = 65535)
int value() default 0; 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.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
} }
static void register(@NotNull ConfigurationInitializer initializer) { 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);
});
} }
} }
+7 -12
View File
@@ -15,25 +15,26 @@
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>4.0.0</version> <version>4.0.11</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>features/section</module> <module>features/section</module>
<module>features/file</module> <module>features/file</module>
<module>features/commentable</module> <module>features/commentable</module>
<module>features/versioned</module> <module>features/versioned</module>
<module>features/text</module>
<module>providers/yaml</module> <module>providers/yaml</module>
<module>providers/gson</module> <module>providers/gson</module>
<!-- <module>providers/hocon</module>--> <module>providers/hocon</module>
<!-- <module>providers/sql</module>--> <module>providers/sql</module>
<!-- <module>providers/mongodb</module>--> <module>providers/mongodb</module>
<module>demo</module> <module>demo</module>
</modules> </modules>
<name>EasyConfiguration</name> <name>EasyConfiguration</name>
<description>轻松(做)配置,简单便捷的通用配置文件加载、读取与更新工具,可自定义配置格式。</description> <description>A simple, easy-to-use and universal solution for managing configuration files.</description>
<url>https://github.com/CarmJos/EasyConfiguration</url> <url>https://github.com/CarmJos/EasyConfiguration</url>
<developers> <developers>
@@ -88,12 +89,6 @@
<url>https://repo1.maven.org/maven2/</url> <url>https://repo1.maven.org/maven2/</url>
</repository> </repository>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/*</url>
</repository>
</repositories> </repositories>
<distributionManagement> <distributionManagement>
@@ -201,7 +196,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version> <version>3.14.0</version>
<configuration> <configuration>
<source>${project.jdk.version}</source> <source>${project.jdk.version}</source>
<target>${project.jdk.version}</target> <target>${project.jdk.version}</target>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -2,7 +2,7 @@ package cc.carm.lib.configuration.source.json;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; 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.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
@@ -14,17 +14,17 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; 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() public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting() .serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter( .registerTypeAdapter(
MemorySection.class, SourcedSection.class,
(JsonSerializer<MemorySection>) (src, t, c) -> c.serialize(src.data()) (JsonSerializer<SourcedSection>) (src, t, c) -> c.serialize(src.data())
).create(); ).create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected @Nullable MemorySection rootSection; protected @Nullable SourcedSection rootSection;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath) { @NotNull File file, @Nullable String resourcePath) {
@@ -48,7 +48,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
protected JSONSource self() { protected @NotNull JSONSource self() {
return this; return this;
} }
@@ -58,7 +58,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized"); return Objects.requireNonNull(this.rootSection, "Root section is not initialized");
} }
@@ -70,7 +70,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class)); 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(); // 更新时间 this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
} }
+2 -2
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -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; 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.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import com.typesafe.config.Config; import cc.carm.lib.configuration.source.section.SourcedSection;
import com.typesafe.config.*;
import org.jetbrains.annotations.NotNull; 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) { public void initialize() {
super(holder, lastUpdateMillis); try {
this.initializeFile();
this.onReload();
} catch (Exception e) {
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
} }
@Override @Override
protected HOCONSource self() { protected @NotNull HOCONSource self() {
return this; return this;
} }
@Override @Override
public @NotNull Config original() { public @NotNull Map<String, Object> original() {
return section().data(); return this.section().data();
} }
@Override @Override
public @NotNull HOCONSection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized"); 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 @Override
public void save() throws Exception { public void save() throws Exception {
this.fileWriter(w -> w.write(HOCONSource.this.saveToString()));
} }
@Override @Override
protected void onReload() throws Exception { 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
+18 -10
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.mongodb.version>5.3.1</deps.mongodb.version>
<log4j.version>2.24.3</log4j.version> <log4j.version>2.24.3</log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-mongodb</artifactId> <artifactId>easyconfiguration-mongodb</artifactId>
@@ -30,7 +31,7 @@
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId> <artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -38,14 +39,7 @@
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>5.3.1</version> <version>${deps.mongodb.version}</version>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -69,6 +63,20 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <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; 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");
}
@@ -0,0 +1,57 @@
package config;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.mongodb.MongoConfigFactory;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import org.bson.UuidRepresentation;
import org.junit.Test;
import java.io.File;
public class MongoTest {
boolean local = false;
@Test
public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/mongo.json")).build();
gsonHolder.initialize(MongoConfig.class);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(
"mongodb://" + MongoConfig.HOST.resolve() + ":" + MongoConfig.PORT.resolve()
))
.credential(MongoCredential.createCredential(
MongoConfig.USERNAME.resolve(), MongoConfig.DATABASE.resolve(),
MongoConfig.PASSWORD.resolve().toCharArray()
))
.uuidRepresentation(UuidRepresentation.STANDARD)
.build();
MongoClient mongoClient = MongoClients.create(settings);
MongoDatabase mongoDatabase = mongoClient.getDatabase(MongoConfig.DATABASE.resolve());
ConfigurationHolder<?> mongoHolder = MongoConfigFactory
.from(mongoDatabase, "configs")
.namespace("my_plugin")
.build();
// Test the configuration
ConfigurationTest.testDemo(mongoHolder);
ConfigurationTest.testInner(mongoHolder);
ConfigurationTest.save(mongoHolder);
}
}
+4 -4
View File
@@ -10,10 +10,10 @@ CREATE TABLE IF NOT EXISTS conf
`path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath) `path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath)
`type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...) `type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...)
`value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式) `value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式)
`inline_comment` TEXT, # 配置项的用法,本质是行内注释 `inline_comment` TEXT comment 'usage', # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT, # 配置项的描述,本质是顶部注释 `header_comment` MEDIUMTEXT comment 'description', # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT, # 配置项的描述,本质是部注释 `footer_comment` MEDIUMTEXT comment 'example', # 配置项的参考,本质是部注释
`version` INT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本 `version` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间 `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`namespace`, `path`) PRIMARY KEY (`namespace`, `path`)
+20 -5
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -15,7 +15,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<log4j.version>2.24.3</log4j.version> <deps.mysql-driver.version>8.0.33</deps.mysql-driver.version>
<deps.log4j.version>2.24.3</deps.log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-sql</artifactId> <artifactId>easyconfiguration-sql</artifactId>
@@ -77,21 +78,35 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${deps.mysql-driver.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -0,0 +1,178 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.builder.TableCreateBuilder;
import cc.carm.lib.easysql.api.enums.IndexType;
import cc.carm.lib.easysql.api.function.SQLHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.HashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SQLConfigFactory extends ConfigurationFactory<SQLSource, ConfigurationHolder<SQLSource>, SQLConfigFactory> {
public static SQLConfigFactory from(@NotNull Supplier<@NotNull SQLManager> managerSupplier) {
return new SQLConfigFactory(managerSupplier);
}
public static SQLConfigFactory from(@NotNull SQLManager manager) {
return from(() -> manager);
}
protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().create();
protected static final @NotNull BiConsumer<String, TableCreateBuilder> DEFAULT_TABLE_SCHEMA = (tableName, builder) -> {
builder.addColumn("namespace", "VARCHAR(32) NOT NULL");
builder.addColumn("path", "VARCHAR(96) NOT NULL");
builder.addColumn("value", "TEXT");
builder.addColumn("inline_comment", "TEXT");
builder.addColumn("header_comments", "MEDIUMTEXT");
builder.addColumn("footer_comments", "MEDIUMTEXT");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0");
builder.addColumn(
"create_time",
"TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
);
builder.addColumn(
"update_time",
"TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
);
builder.setIndex(
IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(),
"namespace", "path"
);
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
};
protected @NotNull Supplier<SQLManager> managerSupplier;
protected Supplier<Gson> gsonSupplier = () -> DEFAULT_GSON;
protected HashMap<Integer, SQLValueResolver<?>> resolvers = new HashMap<>(SQLValueResolver.STANDARD_RESOLVERS);
protected SQLTable tableName = SQLTable.of("config", (table) -> DEFAULT_TABLE_SCHEMA.accept("config", table));
protected String namespace = "default";
public SQLConfigFactory(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
}
@Override
protected SQLConfigFactory self() {
return this;
}
public SQLConfigFactory manager(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
return self();
}
public SQLConfigFactory manager(@NotNull SQLManager manager) {
return manager(() -> manager);
}
public SQLConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public SQLConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
public SQLConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public SQLConfigFactory resolver(@Range(from = 0, to = 255) int type, @NotNull SQLValueResolver<?> resolver) {
this.resolvers.put(type, resolver);
return self();
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser) {
return resolver(id, ValueType.of(clazz), parser);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser) {
return resolver(id, SQLValueResolver.of(type, parser));
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<T, String> serializer) {
return resolver(id, ValueType.of(clazz), parser, serializer);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<T, String> serializer) {
return resolver(id, SQLValueResolver.of(type, parser, serializer));
}
public SQLConfigFactory table(@NotNull SQLTable table) {
this.tableName = table;
return self();
}
public SQLConfigFactory table(@NotNull String tableName, @NotNull SQLHandler<TableCreateBuilder> builder) {
return table(SQLTable.of(tableName, builder));
}
public SQLConfigFactory tableName(@NotNull String tableName) {
return table(tableName, table -> DEFAULT_TABLE_SCHEMA.accept(tableName, table));
}
public SQLConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return self();
}
@Override
public @NotNull ConfigurationHolder<SQLSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
SQLManager manager = this.managerSupplier.get();
if (manager == null) throw new NullPointerException("No SQLManager instance provided.");
Commentable.registerMeta(this.initializer);
VersionedMetaTypes.register(this.initializer);
return new ConfigurationHolder<SQLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final SQLSource source = new SQLSource(
this, System.currentTimeMillis(),
gson, manager, resolvers, tableName, namespace
);
@Override
public @NotNull SQLSource config() {
return source;
}
};
}
}
@@ -1,6 +1,12 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface SQLOptions { public interface SQLOptions {
/**
* Whether to purge the configuration's in-database data when saving.
*/
ConfigurationOption<Boolean> PURGE = ConfigurationOption.of( true);
} }
@@ -1,136 +1,170 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLQuery;
import cc.carm.lib.easysql.api.SQLTable; import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.enums.IndexType;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSource> { public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Object>, SQLSource> {
protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected final @NotNull SQLManager sqlManager; protected final @NotNull SQLManager sqlManager;
protected final @NotNull String namespace; protected final @NotNull String namespace;
protected final @NotNull SQLTable table; protected final @NotNull SQLTable table;
protected final @NotNull Set<String> updated = new HashSet<>(); protected final @NotNull Map<Integer, SQLValueResolver<?>> resolvers;
protected MemorySection rootSection; protected SourcedSection rootSection;
public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis, public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis,
@NotNull Gson gson, @NotNull SQLManager sqlManager, @NotNull String tableName, @NotNull String namespace) { @NotNull Gson gson, @NotNull SQLManager sqlManager,
@NotNull Map<Integer, SQLValueResolver<?>> resolvers,
@NotNull SQLTable table, @NotNull String namespace) {
super(holder, lastUpdateMillis); super(holder, lastUpdateMillis);
this.gson = gson; this.gson = gson;
this.sqlManager = sqlManager; this.sqlManager = sqlManager;
this.resolvers = resolvers;
this.namespace = namespace; this.namespace = namespace;
this.table = SQLTable.of(tableName, builder -> { this.table = table;
builder.addColumn("namespace", "VARCHAR(32) NOT NULL"); try {
builder.addColumn("path", "VARCHAR(96) NOT NULL"); this.table.create(this.sqlManager);
onReload();
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0"); } catch (Exception e) {
builder.addColumn("value", "TEXT"); e.printStackTrace();
}
builder.addColumn("inline_comment", "TEXT");
builder.addColumn("header_comments", "MEDIUMTEXT");
builder.addColumn("footer_comments", "MEDIUMTEXT");
builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0");
builder.addColumn("create_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
builder.addColumn("update_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
builder.setIndex(
IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(),
"namespace", "path"
);
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
});
} }
@Override @Override
protected SQLSource self() { protected @NotNull SQLSource self() {
return this; return this;
} }
public @NotNull Gson gson() {
return gson;
}
@Override @Override
public @NotNull Map<?, ?> original() { public @NotNull Map<String, Object> original() {
return section().data(); return section().data();
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
} }
public int purge() throws Exception {
return this.table.createDelete().addCondition("namespace", namespace).build().execute();
}
@Override @Override
public void save() throws Exception { public void save() throws Exception {
if (this.updated.isEmpty()) return; // Nothing to save LocalDateTime time = LocalDateTime.now(); // Update time
List<Object[]> dataValues = new ArrayList<>();
Date date = new Date(); // Update time SourcedSection section = section();
List<Object[]> values = new ArrayList<>(); Map<String, ConfigValue<?>> values = holder().registeredValues();
for (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {
@NotNull String path = entry.getKey();
@NotNull ConfigValue<?> config = entry.getValue();
@Nullable Object value = section.get(path);
for (String path : this.updated) { if (value instanceof SourcedSection) {
Object value = get(path); value = ((SourcedSection) value).asMap();
} else if (value instanceof List<?>) {
// if (value instanceof SQLConfigWrapper) { List<Object> list = new ArrayList<>();
// value = getSourceMap(((SQLConfigWrapper) value).getSource()); for (Object obj : (List<?>) value) {
// } if (obj instanceof SourcedSection) {
// list.add(((SourcedSection) obj).asMap());
// SQLValueResolver<?> type = SQLValueTypes.get(value.getClass()); } else {
// if (type != null) { list.add(obj);
// values.add(new Object[]{ }
// this.namespace, path, date, }
// type.getID(), type.serializeObject(value), value = list;
// getComments().getInlineComment(path),
// GSON.toJson(getComments().getHeaderComment(path))
// });
// }
} }
this.updated.clear(); if (value == null) continue;
try {
int typeID = typeIdOf(value);
String data = serialize(typeID, value);
if (data == null) continue;
int version = holder().metadata(path).get(VersionedMetaTypes.VERSION, 0);
dataValues.add(new Object[]{
namespace, path, time, version, typeID, data,
Commentable.getInlineComment(holder(), path),
gson.toJson(Commentable.getHeaderComments(holder(), path)),
gson.toJson(Commentable.getFooterComments(holder(), path))
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (holder.option(SQLOptions.PURGE)) {
purge();
}
this.table.createReplaceBatch() this.table.createReplaceBatch()
.setColumnNames("namespace", "path", "update_time", "type", "value", "inline_comment", "header_comments") .setColumnNames(
.setAllParams(values) "namespace", "path", "update_time", "version", "type", "value",
.execute(); "inline_comment", "header_comments", "footer_comments"
).setAllParams(dataValues).execute();
} }
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
LinkedHashMap<String, Object> values = new LinkedHashMap<>(); Map<String, Object> loaded = new LinkedHashMap<>();
try (SQLQuery query = this.table.createQuery()
// try (SQLQuery query = this.table.createQuery() .addCondition("namespace", namespace)
// .addCondition("namespace", namespace) .build().execute()) {
// .build().execute()) { ResultSet rs = query.getResultSet();
// ResultSet rs = query.getResultSet(); while (rs.next()) {
// while (rs.next()) { String path = rs.getString("path");
// String path = rs.getString("path"); if (path == null) continue; // Path should be not null
// int type = rs.getInt("type"); int ver = rs.getInt("version");
// try { try {
// SQLValueResolver<?> resolver = SQLValueTypes.get(type); loaded.put(path, parse(rs.getInt("type"), rs.getString("value")));
// if (resolver == null) throw new IllegalStateException("No resolver for type #" + type); if (ver != 0) holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
// String value = rs.getString("value"); } catch (Exception e) {
// values.put(path, resolver.resolve(value)); e.printStackTrace();
// }
// loadInlineComment(path, rs.getString("inline_comment")); }
// loadHeaderComment(path, rs.getString("header_comments")); }
// } catch (Exception ex) { this.rootSection = SourcedSection.root(this, loaded);
// ex.printStackTrace();
// }
// }
// }
//
// this.rootConfiguration = new SQLConfigWrapper(this, values);
} }
protected @Nullable Object parse(int type, String value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.resolve(this, value);
}
protected @Nullable String serialize(int type, @NotNull Object value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.serialize(this, value);
}
protected int typeIdOf(@NotNull Object value) {
return this.resolvers.entrySet().stream()
.filter(entry -> entry.getValue().isInstance(value))
.findFirst().map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value.getClass().getName()));
}
} }
@@ -0,0 +1,115 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class SQLValueResolver<T> {
public static final @NotNull SQLValueResolver<String> STRING = SQLValueResolver.of(ValueType.STRING, s -> s);
public static final @NotNull SQLValueResolver<Byte> BYTE = SQLValueResolver.of(ValueType.BYTE, Byte::parseByte);
public static final @NotNull SQLValueResolver<Short> SHORT = SQLValueResolver.of(ValueType.SHORT, Short::parseShort);
public static final @NotNull SQLValueResolver<Integer> INTEGER = SQLValueResolver.of(ValueType.INTEGER, Integer::parseInt);
public static final @NotNull SQLValueResolver<Long> LONG = SQLValueResolver.of(ValueType.LONG, Long::parseLong);
public static final @NotNull SQLValueResolver<Float> FLOAT = SQLValueResolver.of(ValueType.FLOAT, Float::parseFloat);
public static final @NotNull SQLValueResolver<Double> DOUBLE = SQLValueResolver.of(ValueType.DOUBLE, Double::parseDouble);
public static final @NotNull SQLValueResolver<Boolean> BOOLEAN = SQLValueResolver.of(ValueType.BOOLEAN, Boolean::parseBoolean);
public static final @NotNull SQLValueResolver<Character> CHAR = SQLValueResolver.of(ValueType.CHAR, s -> s.charAt(0));
public static final @NotNull SQLValueResolver<List<?>> LIST = new SQLValueResolver<List<?>>(new ValueType<List<?>>() {
}) {
@Override
public @Nullable List<?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, List.class);
}
@Override
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull SQLValueResolver<Map<?, ?>> MAP = new SQLValueResolver<Map<?, ?>>(new ValueType<Map<?, ?>>() {
}) {
@Override
public @Nullable Map<?, ?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, LinkedHashMap.class);
}
@Override
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull Map<Integer, SQLValueResolver<?>> STANDARD_RESOLVERS = Collections.unmodifiableMap(standards());
static Map<Integer, SQLValueResolver<?>> standards() {
Map<Integer, SQLValueResolver<?>> map = new LinkedHashMap<>();
map.put(0, STRING);
map.put(1, BYTE);
map.put(2, SHORT);
map.put(3, INTEGER);
map.put(4, LONG);
map.put(5, FLOAT);
map.put(6, DOUBLE);
map.put(7, BOOLEAN);
map.put(8, CHAR);
map.put(10, LIST);
map.put(11, MAP);
return map;
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type, @NotNull DataFunction<String, V> resolver) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
};
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type,
@NotNull DataFunction<String, V> resolver,
@NotNull DataFunction<V, String> serializer) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
@Override
public @NotNull String serialize(@NotNull SQLSource source, Object value) throws Exception {
return serializer.handle(type.cast(value));
}
};
}
protected final @NotNull ValueType<T> type;
protected SQLValueResolver(@NotNull ValueType<T> type) {
this.type = type;
}
public @NotNull ValueType<T> getType() {
return type;
}
public boolean isInstance(@NotNull Object obj) {
return getType().isInstance(obj);
}
public abstract @Nullable T resolve(@NotNull SQLSource source, String data) throws Exception;
public @Nullable String serialize(@NotNull SQLSource source, Object value) throws Exception {
return String.valueOf(value);
}
}
@@ -1,27 +1,48 @@
package config; package config;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.sql.SQLConfigFactory;
import cc.carm.lib.easysql.EasySQL; import cc.carm.lib.easysql.EasySQL;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.beecp.BeeDataSourceConfig; import cc.carm.lib.easysql.beecp.BeeDataSourceConfig;
import org.junit.Test;
import java.io.File;
public class SQLConfigTest { public class SQLConfigTest {
boolean local = false;
@Test
public void test() { public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/sql.json")).build();
gsonHolder.initialize(DatabaseConfiguration.class);
BeeDataSourceConfig config = new BeeDataSourceConfig(); BeeDataSourceConfig config = new BeeDataSourceConfig();
config.setDriverClassName("org.h2.Driver"); config.setDriverClassName(DatabaseConfiguration.DRIVER_NAME.resolve());
config.setJdbcUrl("jdbc:h2:file:target/test;DB_CLOSE_DELAY=-1;MODE=MySQL;"); config.setJdbcUrl(DatabaseConfiguration.buildJDBC());
config.setUsername(DatabaseConfiguration.USERNAME.resolve());
config.setPassword(DatabaseConfiguration.PASSWORD.resolve());
SQLManager manager = EasySQL.createManager(config); SQLManager manager = EasySQL.createManager(config);
manager.setDebugMode(true); manager.setDebugMode(true);
// SQLConfigProvider provider = EasyConfiguration.from(manager, "conf_test", "TESTING"); ConfigurationHolder<?> holder = SQLConfigFactory.from(manager)
// .tableName("test_configs")
// ConfigurationTest.testDemo(provider); .namespace("testing")
// ConfigurationTest.testInner(provider); .build();
//
// ConfigurationTest.save(provider); ConfigurationTest.testDemo(holder);
// ConfigurationTest.testInner(holder);
// EasySQL.shutdownManager(manager);
ConfigurationTest.save(holder);
EasySQL.shutdownManager(manager);
} }
} }
-71
View File
@@ -62,74 +62,3 @@ dependencies {
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
} }
``` ```
## Example file format
```yaml
version: 1.0.0
test-save: false
test-number: 6161926779561752576
test-enum: DAYS
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: '35882'
info:
uuid: 554b79f1-7c39-4960-82d1-5514c9734417
uuid-value: 9a86663e-2fc7-4851-a423-c7e5d8e94a47 # This is an inline comment
sub:
that:
operators: []
# [ID - UUID]对照表
# 用于测试Map类型的解析与序列化保存
users:
'1': 1c055bdd-c9d1-4931-8270-3d162247f38a
'2': 934e2b05-2417-424e-80fd-fe58c6725837
'3': 442949a2-8345-4210-a87b-593d7168980e
'4': 5c015453-4b5b-42e3-ad87-b9498f2dfeab
'5': 8f9640e7-0fbd-4f73-b737-f0b707215e71
# Inner Test
inner:
inner-value: 51.223503560658166
class-value: 1.0
test:
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: Carm
info:
uuid: 3d1ef2a0-a38b-44f3-b15f-8e3b22cb8cc6
# 以下内容用于测试序列化
model-test:
some-model:
==: SomeModel
num: 855
name: 4f6b7
any-model:
==: AnyModel
name: 63d05
state: false
models:
- name: 481f3
state: true
- name: fcf3e
state: false
- name: '14e50'
state: false
model-map:
a:
name: 1fb9b
state: false
b:
name: 5486f
state: false
```
+2 -9
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.11</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -14,7 +14,7 @@
<maven.compiler.target>${project.jdk.version}</maven.compiler.target> <maven.compiler.target>${project.jdk.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.yamlcommentwriter.version>1.1.0</deps.yamlcommentwriter.version> <deps.yamlcommentwriter.version>1.2.1</deps.yamlcommentwriter.version>
</properties> </properties>
<artifactId>easyconfiguration-yaml</artifactId> <artifactId>easyconfiguration-yaml</artifactId>
@@ -29,13 +29,6 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>yamlcommentwriter</artifactId>
<version>1.1.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId> <artifactId>easyconfiguration-feature-file</artifactId>
@@ -1,5 +1,6 @@
package cc.carm.lib.configuration.source.yaml; package cc.carm.lib.configuration.source.yaml;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableMeta; import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigFactory; import cc.carm.lib.configuration.source.file.FileConfigFactory;
@@ -125,7 +126,7 @@ public class YAMLConfigFactory extends FileConfigFactory<YAMLSource, Configurati
File configFile = this.file; File configFile = this.file;
String sourcePath = this.resourcePath; String sourcePath = this.resourcePath;
CommentableMeta.register(this.initializer); // Register commentable meta types Commentable.registerMeta(this.initializer); // Register commentable meta types
return new ConfigurationHolder<YAMLSource>(this.adapters, this.options, this.metadata, this.initializer) { return new ConfigurationHolder<YAMLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath); final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath);
@@ -1,12 +1,11 @@
package cc.carm.lib.configuration.source.yaml; package cc.carm.lib.configuration.source.yaml;
import cc.carm.lib.configuration.commentable.CommentableMeta; import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.option.CommentableOptions; import cc.carm.lib.configuration.commentable.CommentableOptions;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.option.StandardOptions;
import cc.carm.lib.configuration.source.section.ConfigureSection; import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.yamlcommentupdater.CommentedSection; import cc.carm.lib.yamlcommentupdater.CommentedSection;
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter; import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -25,15 +24,16 @@ import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.regex.Pattern;
public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Object>, YAMLSource> implements CommentedSection { public class YAMLSource
extends FileConfigSource<SourcedSection, Map<String, Object>, YAMLSource>
implements CommentedSection {
protected final @NotNull YamlConstructor yamlConstructor; protected final @NotNull YamlConstructor yamlConstructor;
protected final @NotNull YamlRepresenter yamlRepresenter; protected final @NotNull YamlRepresenter yamlRepresenter;
protected final @NotNull Yaml yaml; protected final @NotNull Yaml yaml;
protected @Nullable MemorySection rootSection; protected @Nullable SourcedSection rootSection;
protected YAMLSource(@NotNull ConfigurationHolder<? extends YAMLSource> holder, protected YAMLSource(@NotNull ConfigurationHolder<? extends YAMLSource> holder,
@NotNull File file, @Nullable String resourcePath) { @NotNull File file, @Nullable String resourcePath) {
@@ -55,8 +55,8 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
protected YAMLSource self() { protected @NotNull YAMLSource self() {
return null; return this;
} }
@Override @Override
@@ -65,15 +65,10 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
} }
@Override
public char separator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public @NotNull LoaderOptions loaderOptions() { public @NotNull LoaderOptions loaderOptions() {
return holder().options().get(YAMLOptions.LOADER); return holder().options().get(YAMLOptions.LOADER);
} }
@@ -114,9 +109,9 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
public void save() throws Exception { public void save() throws Exception {
CommentedYAMLWriter writer = new CommentedYAMLWriter( CommentedYAMLWriter writer = new CommentedYAMLWriter(
String.valueOf(this.separator()), String.valueOf(this.pathSeparator()),
dumperOptions().getIndent(), dumperOptions().getIndent(),
holder.options().get(CommentableOptions.COMMENT_EMPTY_VALUE) holder.option(CommentableOptions.COMMENT_EMPTY_VALUE)
); );
try { try {
fileWriter(w -> w.write(writer.saveToString(this))); fileWriter(w -> w.write(writer.saveToString(this)));
@@ -135,18 +130,18 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
return this.saveToString(section()); return this.saveToString(section());
} }
public @NotNull MemorySection loadFromString(@NotNull String data) throws Exception { public @NotNull SourcedSection loadFromString(@NotNull String data) throws Exception {
MappingNode mappingNode; MappingNode mappingNode;
try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) { try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) {
Node rawNode = this.yaml.compose(reader); Node rawNode = this.yaml.compose(reader);
mappingNode = (MappingNode) rawNode; mappingNode = (MappingNode) rawNode;
} }
if (mappingNode == null) return MemorySection.root(this); if (mappingNode == null) return SourcedSection.root(this);
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
this.constructMap(mappingNode, map); this.constructMap(mappingNode, map);
return MemorySection.root(this, map); return SourcedSection.root(this, map);
} }
private void constructMap(@NotNull MappingNode mappingNode, @NotNull Map<String, Object> section) { private void constructMap(@NotNull MappingNode mappingNode, @NotNull Map<String, Object> section) {
@@ -175,7 +170,7 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
public String serializeValue(@NotNull String key, @NotNull Object value) { public String serializeValue(@NotNull String key, @NotNull Object value) {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
map.put(key, value); map.put(key, value);
return saveToString(MemorySection.root(this, map)); return saveToString(SourcedSection.root(this, map));
} }
@Override @Override
@@ -193,46 +188,17 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
public @Nullable String getInlineComment(@NotNull String key) { public @Nullable String getInlineComment(@NotNull String key) {
String comment = getInlineComment(key, null); return Commentable.getInlineComment(holder(), key);
if (comment != null) return comment;
String sep = String.valueOf(separator());
// If the comment is not found, try to get the comment from the parent section
String[] keys = key.split(sep);
if (keys.length == 1) return null;
// Try every possible parent key&child key combination
for (int i = 1; i < keys.length; i++) {
String parentKey = String.join(sep, Arrays.copyOfRange(keys, 0, i));
String childKey = String.join(sep, Arrays.copyOfRange(keys, i, keys.length));
comment = getInlineComment(childKey, parentKey);
if (comment != null) return comment;
}
return null;
}
public @Nullable String getInlineComment(@NotNull String key, @Nullable String sectionKey) {
Map<String, String> pathComment = holder().metadata(key).get(CommentableMeta.INLINE);
if (pathComment == null || pathComment.isEmpty()) return null;
if (sectionKey == null) return pathComment.get(null);
for (Map.Entry<String, String> entry : pathComment.entrySet()) {
if (entry.getKey().equals(sectionKey)) return entry.getValue();
Pattern pattern = Pattern.compile(entry.getKey().replace(".", "\\.").replace("*", ".*"));
if (pattern.matcher(sectionKey).matches()) return entry.getValue();
}
return null;
} }
@Override @Override
public @Nullable List<String> getHeaderComments(@Nullable String key) { public @Nullable List<String> getHeaderComments(@Nullable String key) {
return holder().metadata(key).get(CommentableMeta.HEADER); return Commentable.getHeaderComments(holder(), key);
} }
@Override @Override
public @Nullable List<String> getFooterComments(@Nullable String key) { public @Nullable List<String> getFooterComments(@Nullable String key) {
return holder().metadata(key).get(CommentableMeta.FOOTER); return Commentable.getFooterComments(holder(), key);
} }
public static class YamlRepresenter extends Representer { public static class YamlRepresenter extends Representer {
@@ -2,6 +2,7 @@ package sample;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; 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.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment; import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.value.standard.ConfiguredList; import cc.carm.lib.configuration.value.standard.ConfiguredList;
@@ -19,6 +20,8 @@ public interface SampleConfig extends Configuration {
@HeaderComments("Server configurations") // Header comment @HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class); ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
@@ -11,7 +11,7 @@ public class SampleTest {
// 1. Make a configuration provider from a file. // 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml") ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml") .resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file. .indent(2) // Optional: Set the indentation of the configuration file.
.build(); .build();
// 2. Initialize the configuration classes or instances. // 2. Initialize the configuration classes or instances.
@@ -1,9 +1,11 @@
package yaml.test; package yaml.test;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableMeta; import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest; import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory; import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import cc.carm.lib.configuration.source.yaml.YAMLSource;
import org.junit.Test; import org.junit.Test;
import java.util.List; import java.util.List;
@@ -14,13 +16,12 @@ public class YamlTests {
@Test @Test
public void test() { public void test() {
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/tests.yml") ConfigurationHolder<YAMLSource> holder = YAMLConfigFactory.from("target/tests.yml")
.resourcePath("configs/sample.yml").build(); .resourcePath("configs/sample.yml").build();
ConfigurationTest.testDemo(holder); ConfigurationTest.testDemo(holder);
ConfigurationTest.testInner(holder); ConfigurationTest.testInner(holder);
Map<String, List<String>> headers = holder.extractMetadata(CommentableMeta.HEADER); Map<String, List<String>> headers = holder.extractMetadata(CommentableMeta.HEADER);
System.out.println("Header comments: "); System.out.println("Header comments: ");
headers.forEach((k, v) -> { headers.forEach((k, v) -> {