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

Compare commits

...

60 Commits

Author SHA1 Message Date
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
80 changed files with 3743 additions and 620 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
run: |
rm -rf 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
- name: "Generate the Javadoc sitemap"
+1 -1
View File
@@ -1,3 +1,3 @@
/.idea/
.idea/
**/target/
**.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).
```java
@HeaderComment("Configurations for sample")
interface SampleConfig extends Configuration {
@ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // Inline comment
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()
.parseValue(UUID::fromString).serializeValue(UUID::toString)
.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 {
@HeaderComment("Configure your name!") // Header comment
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // Custom path
@ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
}
}
```
```java
public class Sample {
public static void main(String[] args) {
// 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.
provider.initialize(SampleConfig.class);
holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
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?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids:
- 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info:
# Configure your name!
name: Joker
year: 24
how-old-are-you: 24
```
### Dependencies
@@ -159,10 +182,10 @@ info:
<scope>compile</scope>
</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>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId>
<artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -205,8 +228,8 @@ dependencies {
// YAML file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments.
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
}
```
@@ -220,7 +243,8 @@ dependencies {
EasyConfiguration for MineCraft!
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
@@ -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)
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
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/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
@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);
@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()
.parseValue(UUID::fromString).serializeValue(UUID::toString)
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001")
@@ -59,26 +68,34 @@ interface SampleConfig extends Configuration {
interface INFO extends Configuration {
@HeaderComment("Configure your name!") // 头部注释
@HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // 自定义配置路径,若不定义,则按照默认规则生成
@ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
}
}
```
```java
public class Sample {
public static void main(String[] args) {
// 1. 生成一个 “Provider” 用于给配置类提供源配置的文件。
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml");
// 2. 通过 “Provider” 初始化配置类或配置实例。
provider.initialize(SampleConfig.class);
// 1. 生成一个 “holder” 用于给配置类提供源配置的文件。
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. 通过 “holder” 初始化配置类或配置实例。
holder.initialize(SampleConfig.class);
// 3. 现在可以享受快捷方便的配置文件使用方式了~
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
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?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids:
- 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info:
# Configure your name!
name: Joker
year: 24
how-old-are-you: 24
```
### 依赖方式
@@ -164,7 +187,7 @@ info:
<!--需要注意的是,JSON不支持文件注释。-->
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId>
<artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version>
<scope>compile</scope>
</dependency>
@@ -225,7 +248,7 @@ dependencies {
//基于JSON文件的实现版本,可用于全部Java环境。
//需要注意的是,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]"
@@ -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)
万分感谢来自 [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) 开源协议。
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,7 +158,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName());
sb.append(raw.getClass().getName());
sb.append('<');
Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) {
@@ -166,6 +167,7 @@ public abstract class ValueType<T> {
}
sb.append(args[i].getTypeName());
}
sb.append('>');
return sb.toString();
}
return type.getTypeName();
@@ -3,8 +3,8 @@ package cc.carm.lib.configuration.builder.impl;
import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.function.DataConsumer;
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.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ConfigValue;
@@ -34,28 +34,30 @@ public abstract class AbstractSectionBuilder<
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));
}
public @NotNull SELF parse(ValueHandler<ConfigureSection, PARAM> valueParser) {
public @NotNull SELF parse(@NotNull ValueHandler<ConfigureSection, PARAM> valueParser) {
this.parser = valueParser;
return self();
}
public @NotNull SELF serialize(DataFunction<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
public @NotNull SELF serialize(ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
public @NotNull SELF serialize(@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
this.serializer = serializer;
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 serializer.handle(value);
});
}
public @NotNull SELF serialize(@NotNull ValueConsumer<Map<String, Object>, PARAM> serializer) {
return serialize((h, value) -> {
Map<String, Object> map = new LinkedHashMap<>();
serializer.accept(map);
serializer.accept(h, map, value);
return map;
});
}
@@ -29,7 +29,7 @@ public abstract class AbstractSourceBuilder<
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));
}
@@ -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.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<
SOURCE extends ConfigureSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>,
@@ -139,18 +146,39 @@ public abstract class ConfigurationFactory<
return self();
}
/**
* Supply the base path generator for this configuration holder
*
* @param generator {@link PathGenerator}
* @return this
*/
public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> {
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,
@NotNull ConfigurationMetadata<M> metadata,
@NotNull Function<A, M> extractor) {
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
}
/**
* Build the configuration holder.
*
* @return The configuration holder
*/
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.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -17,6 +20,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -50,6 +55,10 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options;
}
public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
public @NotNull Map<String, ConfigurationMetaHolder> metadata() {
return this.metadata;
}
@@ -61,14 +70,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) {
return extractMetadata(type, Objects::nonNull);
}
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type, @NotNull Predicate<M> filter) {
Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type);
if (data != null) metas.put(entry.getKey(), data);
if (filter.test(data)) metas.put(entry.getKey(), data);
}
return Collections.unmodifiableMap(metas);
}
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() {
return this.adapters;
}
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface ConfigInitializeHandler<T> {
public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() {
return (provider, path, value) -> {
static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value, 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) {
return (provider, path, value) -> {
whenInitialize(provider, path, value);
after.whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value, instance) -> {
whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value, instance);
};
}
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) {
return (provider, path, value) -> {
before.whenInitialize(provider, path, value);
whenInitialize(provider, path, value);
default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value, instance);
};
}
@@ -3,6 +3,7 @@ package cc.carm.lib.configuration.source.loader;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.source.ConfigurationHolder;
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.value.ConfigValue;
import org.jetbrains.annotations.NotNull;
@@ -20,18 +21,18 @@ import java.util.function.Function;
public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer;
protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
}
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer;
this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer;
}
@@ -43,34 +44,34 @@ public class ConfigurationInitializer {
return pathGenerator;
}
public ConfigInitializeHandler<Field> fieldInitializer() {
return fieldInitializer;
public ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer() {
return valueInitializer;
}
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = fieldInitializer;
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.valueInitializer = fieldInitializer;
}
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() {
public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return classInitializer;
}
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = classInitializer;
}
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(fieldInitializer);
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.valueInitializer = this.valueInitializer.andThen(fieldInitializer);
}
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) {
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = this.classInitializer.andThen(classInitializer);
}
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> {
appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -80,7 +81,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> {
appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -107,13 +108,13 @@ public class ConfigurationInitializer {
public void initialize(@NotNull ConfigurationHolder<?> holder,
@NotNull Configuration config) throws Exception {
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,
@NotNull Class<? extends Configuration> clazz) throws Exception {
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) {
String path = getClassPath(holder, parentPath, root.getClass(), configField);
try {
this.classInitializer.whenInitialize(holder, path, root.getClass());
this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) {
e.printStackTrace();
}
@@ -139,7 +140,7 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField);
try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz);
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) {
e.printStackTrace();
}
@@ -148,7 +149,7 @@ public class ConfigurationInitializer {
initializeField(holder, clazz, field, path);
}
if (holder.options().get(StandardOptions.LOAD_SUB_CLASSES)) {
if (holder.option(StandardOptions.LOAD_SUB_CLASSES)) {
Class<?>[] classes = clazz.getDeclaredClasses();
for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
initializeStaticClass(holder, classes[i], path, null);
@@ -168,13 +169,17 @@ public class ConfigurationInitializer {
String path = getFieldPath(holder, parent, field);
if (path == null) return;
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 {
this.fieldInitializer.whenInitialize(holder, path, field);
this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) {
e.printStackTrace();
}
if (holder.options().get(StandardOptions.SET_DEFAULTS)) {
value.setDefault();
if (holder.option(StandardOptions.PRELOAD)) {
value.get(); // Preload the value by calling #get method.
}
} else if (source instanceof Configuration && object instanceof Configuration) {
// 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。
@@ -9,6 +9,12 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
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 static PathGenerator of() {
@@ -80,7 +86,7 @@ public class PathGenerator {
}
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> {
@SuppressWarnings("unchecked")
public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) {
return of((Class<T>) defaultValue.getClass(), defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Class<T> valueClazz, @NotNull T defaultValue) {
return new ConfigurationOption<>(valueClazz, defaultValue);
return new ConfigurationOption<>(defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) {
return of(defaultValue.get());
}
private final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue;
public ConfigurationOption(@NotNull Class<V> valueClazz, @NotNull V defaultValue) {
this.valueClazz = valueClazz;
public ConfigurationOption(@NotNull V defaultValue) {
this.defaultValue = defaultValue;
}
@NotNull
public Class<V> valueClass() {
return this.valueClazz;
}
public @NotNull V defaults() {
return defaultValue;
}
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/**
* Whether to pre parse the config values.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* Whether to pre parse the config values,
* may good to set true if you want to keep the config format.
* <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
*/
ConfigurationOption<Boolean> PRELOAD = of(false);
ConfigurationOption<Boolean> PRELOAD = of(true);
}
@@ -1,73 +1,451 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Contract;
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.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* Represents a section of a configuration.
*
* @author Carm
* @since 4.0.0
*/
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();
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
@UnmodifiableView
default Set<String> getKeys(boolean deep) {
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
@UnmodifiableView
Map<String, Object> getValues(boolean deep);
/**
* Gets a set containing all key-values in this section.
*
* @return Map of data values contained within this Section.
* @see #getValues(boolean)
*/
@NotNull
@UnmodifiableView
default Map<String, Object> values() {
return getValues(false);
}
/**
* Get this section as a map.
* <p>
* In this map, child {@link ConfigureSection}s will also be represented as {@link Map}s.
*
* @return Map of data values contained within this Section.
*/
@NotNull
@UnmodifiableView
Map<String, Object> asMap();
/**
* Create a stream of all values in this section.
*
* @return Stream of all values in this section.
*/
default Stream<Map.Entry<String, Object>> stream() {
return values().entrySet().stream();
}
/**
* Iterates over all key-values in this section (include child sections)
*
* @param action The action to apply to each key.
*/
default void forEach(@NotNull BiConsumer<String, Object> action) {
values().forEach(action);
}
/**
* Sets the value at the given path.
* <p>
* 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);
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) {
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) {
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) {
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
ConfigureSection getSection(@NotNull String path);
@Nullable Object get(@NotNull String path);
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> clazz) {
return get(path, null, clazz);
default ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
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);
}
/**
* 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")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, @NotNull Class<T> clazz) {
return get(path, defaultValue, DataFunction.castObject(clazz));
default @Nullable <T> T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> 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")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue,
@NotNull DataFunction<Object, T> parser) {
@@ -82,125 +460,290 @@ public interface ConfigureSection {
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) {
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) {
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")
default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
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) {
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) {
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")
default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
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) {
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) {
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")
default @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
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) {
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) {
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")
default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
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) {
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) {
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")
default @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
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) {
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) {
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")
default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
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) {
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) {
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")
default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
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) {
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) {
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")
default @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
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) {
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) {
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")
default @Nullable String getString(@NotNull String path, @Nullable String def) {
return get(path, def, String.class);
@@ -298,23 +841,53 @@ public interface ConfigureSection {
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,
@NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> 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) {
List<?> values = getList(path);
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) {
return stream(path).map(parser);
}
default String childPath(String path) {
int index = path.indexOf(pathSeparator());
return (index == -1) ? path : path.substring(index + 1);
}
static <T, C extends Collection<T>> @NotNull C parseCollection(
@Nullable List<?> data, @NotNull Supplier<C> constructor,
@Nullable List<?> data,
@NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser
) {
C values = constructor.get();
@@ -1,12 +1,21 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.*;
import java.util.List;
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<
SECTION extends ConfigureSection, ORIGINAL,
SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>>
@@ -24,28 +33,46 @@ public abstract class ConfigureSource<
return holder;
}
public void reload() throws Exception {
onReload(); // 调用重写的Reload方法
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();
/**
* @return The root {@link ConfigureSection}
* @return The root {@link ConfigureSection}, which represents the entire configuration.
*/
public abstract @NotNull SECTION section();
/**
* Save the whole configuration.
*
* @throws Exception If any error occurs while saving.
*/
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;
public char pathSeparator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public long getLastUpdateMillis() {
return this.lastUpdateMillis;
}
@@ -54,14 +81,20 @@ public abstract class ConfigureSource<
return getLastUpdateMillis() > parsedTime;
}
/**
* Source also represents the root section, so it has no parent
*
* @return null
*/
@Override
@Contract(pure = true, value = "->null")
public @Nullable ConfigureSection parent() {
return null; // Source also represents the root section, so it has no parent
return null;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return self();
public @NotNull String path() {
return "";
}
@Override
@@ -69,34 +102,29 @@ public abstract class ConfigureSource<
return section().getValues(deep);
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return section().getKeys(deep);
}
@Override
public @NotNull @UnmodifiableView Map<String, Object> asMap() {
return section().asMap();
}
@Override
public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return section().createSection(path, data);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
section().set(path, value);
}
@Override
public boolean contains(@NotNull String path) {
return section().contains(path);
}
@Override
public 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);
public void remove(@NotNull String path) {
section().remove(path);
}
@Override
@@ -113,7 +113,7 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* @param override Whether to overwrite existing configured value
*/
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
}
@@ -4,6 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -20,7 +21,6 @@ public class ValueManifest<T> {
protected @NotNull Supplier<@Nullable T> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null);
}
@@ -93,12 +93,12 @@ public class ValueManifest<T> {
public @NotNull String path() {
if (path != null) return path;
else throw new IllegalStateException("No section path provided.");
else throw new IllegalStateException("No section path provided for Value(" + type() + ").");
}
public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.holder;
throw new IllegalStateException("Value does not have a provider.");
throw new IllegalStateException("Value(" + type() + ") does not have a provider.");
}
public @NotNull ConfigureSource<?, ?, ?> config() {
@@ -109,16 +109,17 @@ public class ValueManifest<T> {
return holder().metadata(path());
}
@ApiStatus.Internal
protected Object getData() {
return config().get(path());
}
@ApiStatus.Internal
protected void setData(@Nullable Object value) {
config().set(path(), value);
}
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
};
}
+14 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
@@ -33,6 +33,19 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>4.0.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -7,7 +7,7 @@ import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration {
public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver")
@HeaderComments({
@@ -16,18 +16,19 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver",
})
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver"
);
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
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());
}
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST);
System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after);
TEST.INSTANCE.STATUS.set(after);
System.out.println("> Test Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
}
@@ -1,11 +1,7 @@
package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.annotation.*;
import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue;
@@ -29,7 +25,8 @@ import java.util.UUID;
public interface DemoConfiguration extends Configuration {
@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)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
@@ -39,15 +36,15 @@ public interface DemoConfiguration extends Configuration {
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。
// 注意: 若对应类也有注解,则优先使用类的注解。
Class<?> DATABASE = DatabaseConfiguration.class;
@HeaderComments({"空值测试"})
@InlineComment("空值Inline注释")
ConfiguredMap<String, String> EMPTY = ConfiguredMap.builderOf(String.class, String.class)
.asLinkedMap().fromString()
.build();
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = "info.*") // 通过注解给配置添加注释。
ConfiguredList<UserRecord> ALLOWLISTS = ConfiguredList.builderOf(UserRecord.class).fromSection()
.parse(UserRecord::deserialize).serialize(UserRecord::serialize)
.defaults(UserRecord.CARM).build();
@@ -81,6 +78,7 @@ public interface DemoConfiguration extends Configuration {
.build();
@HeaderComments({"内部类的内部类测试", "通过这种方式,您可以轻易实现多层次的配置文件结构"})
@FooterComments({"-------------"})
public interface That extends Configuration {
ConfiguredList<UUID> OPERATORS = ConfiguredList
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test")
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") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@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()))
.parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build();
@@ -36,6 +36,7 @@ public class UserRecord extends AbstractRecord {
}
public static UserRecord deserialize(ConfigureSection section) {
System.out.println("> Deserializing -> " + section.fullPath());
String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -33,7 +33,7 @@ public @interface InlineComment {
* If the regex is not empty, the comment will be added to
* all sub paths if the regex matches the value.
* 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>
* section:
* foo:
@@ -44,6 +44,9 @@ public @interface InlineComment {
* bar: "foobar" # Comment Contents
* </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
*/
@NotNull
@@ -9,7 +9,12 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface InlineComments {
/**
* Multiple inline comments support.
*
* @return inline comment contents.
* @see InlineComment
*/
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,
a -> Arrays.asList(a.value())
);
initializer.registerAnnotation(InlineComment.class, INLINE, a -> {
Map<String, String> map = new HashMap<>();
if (a.regex().length == 0) { // for current path
map.put(null, a.value());
return map;
}
for (String regex : a.regex()) { // for specified path
map.put(regex, a.value());
}
return map;
});
initializer.registerAnnotation(InlineComments.class, INLINE, a -> {
Map<String, String> map = new HashMap<>();
for (InlineComment comment : a.value()) {
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.option;
package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<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.Nullable;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MemorySection implements ConfigureSection {
public class MemorySection extends AbstractMapSection<MemorySection> {
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) {
return new MemorySection(source, new LinkedHashMap<>(), null);
public static MemorySection of() {
return of(new LinkedHashMap<>());
}
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@Nullable Map<?, ?> data) {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null);
public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) {
return of(() -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source;
protected final @NotNull Map<String, Object> data;
protected final @Nullable MemorySection parent;
public MemorySection(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@NotNull Map<?, ?> data, @Nullable MemorySection parent) {
this.source = source;
this.parent = parent;
this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, createChild((Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) {
list.add(createChild((Map<?, ?>) obj));
} else {
list.add(obj);
}
}
this.data.put(key, list);
} else {
this.data.put(key, entry.getValue());
}
}
public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) {
return of(data.get());
}
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this);
public static MemorySection of(@NotNull Map<?, ?> data) {
return of(data, null, "");
}
protected @NotNull MemorySection createChild() {
return createChild(new LinkedHashMap<>());
public static MemorySection of(@Nullable MemorySection parent, @NotNull String path) {
return of(new LinkedHashMap<>(), parent, path);
}
public 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
public @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() {
return this.source;
}
public @NotNull Map<String, Object> data() {
return this.data;
}
public @Nullable MemorySection parent() {
return this.parent;
public @NotNull MemorySection self() {
return this;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mapChildrenValues(this, null, true) : data());
public @NotNull MemorySection createSection(@NotNull String path, @NotNull Map<?, ?> 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.10</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-yaml</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,259 @@
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> 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+))?})?$"
);
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, 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
* @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 matcher = INSERT_PATTERN.matcher(line);
if (!matcher.matches()) {
lineConsumer.accept(parse(receiver, line));
continue;
}
String id = matcher.group("id");
List<String> values = Optional.ofNullable(this.insertion.get(id))
.map(f -> f.apply(receiver))
.orElse(null);
if (values == null || values.isEmpty()) continue;
String prefix = matcher.group("prefix");
String type = matcher.group("type");
boolean original = type.equals("@");
int offsetAbove = Optional.ofNullable(matcher.group("above"))
.map(Integer::parseInt).orElse(0);
int offsetDown = Optional.ofNullable(matcher.group("down"))
.map(Integer::parseInt).orElse(offsetAbove); // If offsetDown is not set, use offsetAbove
IntStream.range(0, Math.max(0, offsetAbove)).mapToObj(i -> "").forEach(lineConsumer);
String prefixContent = Optional.ofNullable(prefix).map(p -> parse(receiver, p)).orElse("");
if (original) {
values.stream().map(value -> prefixContent + value).forEach(lineConsumer);
} else {
values.stream().map(value -> prefixContent + parse(receiver, value)).forEach(lineConsumer);
}
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,47 @@
package cc.carm.lib.configuration.value.text.tests;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import cc.carm.lib.configuration.value.text.tests.conf.AppMessages;
import org.junit.Test;
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 = YAMLConfigFactory.from("target/messages.yml").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,49 @@
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!");
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("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>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
*
* @return the version of the configuration field
*/
@Range(from = 0, to = Integer.MAX_VALUE)
@Range(from = 0, to = 65535)
int value() default 0;
}
@@ -1,13 +0,0 @@
package cc.carm.lib.configuration.option;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface VersionedOptions {
/**
* Whether to set newer defaults when a {@link cc.carm.lib.configuration.value.ConfigValue}'s marked version
* is newer than the current in storage.
*/
ConfigurationOption<Boolean> RESET_NEWER_DEFAULTS = ConfigurationOption.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.commentable;
package cc.carm.lib.configuration.versioned;
import cc.carm.lib.configuration.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
}
static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value);
initializer.appendFieldInitializer((holder, path, field, value) -> {
ConfigVersion annotation = field.getAnnotation(ConfigVersion.class);
if (annotation == null || value == null) return;
int currentVersion = annotation.value();
int savedVersion = holder.metadata(path).get(VERSION, 0);
if (currentVersion == savedVersion) return;
if (currentVersion > savedVersion) { // This values updated.
value.setDefault(true); // Mark as default value, force write.
}
holder.metadata(path).set(VERSION, currentVersion);
});
}
}
+6 -11
View File
@@ -15,25 +15,26 @@
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<packaging>pom</packaging>
<version>4.0.0</version>
<version>4.0.10</version>
<modules>
<module>core</module>
<module>features/section</module>
<module>features/file</module>
<module>features/commentable</module>
<module>features/versioned</module>
<module>features/text</module>
<module>providers/yaml</module>
<module>providers/gson</module>
<!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>-->
<!-- <module>providers/mongodb</module>-->
<module>providers/sql</module>
<module>providers/mongodb</module>
<module>demo</module>
</modules>
<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>
<developers>
@@ -88,12 +89,6 @@
<url>https://repo1.maven.org/maven2/</url>
</repository>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/*</url>
</repository>
</repositories>
<distributionManagement>
@@ -201,7 +196,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>3.14.0</version>
<configuration>
<source>${project.jdk.version}</source>
<target>${project.jdk.version}</target>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<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.file.FileConfigSource;
import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
@@ -14,17 +14,17 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class JSONSource extends FileConfigSource<MemorySection, Map<String, Object>, JSONSource> {
public class JSONSource extends FileConfigSource<SourcedSection, Map<String, Object>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
MemorySection.class,
(JsonSerializer<MemorySection>) (src, t, c) -> c.serialize(src.data())
SourcedSection.class,
(JsonSerializer<SourcedSection>) (src, t, c) -> c.serialize(src.data())
).create();
protected final @NotNull Gson gson;
protected @Nullable MemorySection rootSection;
protected @Nullable SourcedSection rootSection;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath) {
@@ -48,7 +48,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
}
@Override
protected JSONSource self() {
protected @NotNull JSONSource self() {
return this;
}
@@ -58,7 +58,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
}
@Override
public @NotNull MemorySection section() {
public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized");
}
@@ -70,7 +70,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override
protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class));
this.rootSection = MemorySection.root(this, data);
this.rootSection = SourcedSection.root(this, data);
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
}
+18 -10
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.mongodb.version>5.3.1</deps.mongodb.version>
<log4j.version>2.24.3</log4j.version>
</properties>
<artifactId>easyconfiguration-mongodb</artifactId>
@@ -30,7 +31,7 @@
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
@@ -38,14 +39,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
<version>${deps.mongodb.version}</version>
</dependency>
<dependency>
@@ -69,6 +63,20 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>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>
<build>
@@ -0,0 +1,75 @@
package cc.carm.lib.configuration.source.mongodb;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class MongoConfigFactory extends ConfigurationFactory<MongoSource, ConfigurationHolder<MongoSource>, MongoConfigFactory> {
public static MongoConfigFactory from(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
return new MongoConfigFactory(collectionSupplier);
}
public static MongoConfigFactory from(@NotNull MongoCollection<Document> collection) {
return from(() -> collection);
}
public static MongoConfigFactory from(@NotNull MongoDatabase database, @NotNull String collectionName) {
return from(() -> database.getCollection(collectionName));
}
protected @NotNull Supplier<MongoCollection<Document>> collectionSupplier;
protected @NotNull String namespace = "config";
public MongoConfigFactory(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
super();
this.collectionSupplier = collectionSupplier;
}
public MongoConfigFactory collection(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
this.collectionSupplier = collectionSupplier;
return this;
}
public MongoConfigFactory collection(@NotNull MongoCollection<Document> collection) {
return collection(() -> collection);
}
public MongoConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return this;
}
public MongoConfigFactory namespace(@NotNull Supplier<String> namespace) {
return namespace(namespace.get());
}
@Override
protected MongoConfigFactory self() {
return this;
}
@Override
public @NotNull ConfigurationHolder<MongoSource> build() {
MongoCollection<Document> collection = this.collectionSupplier.get();
if (collection == null) {
throw new IllegalStateException("Failed to get MongoCollection<Document> from supplier");
}
return new ConfigurationHolder<MongoSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull MongoSource source = new MongoSource(this, System.currentTimeMillis(), collection, namespace);
@Override
public @NotNull MongoSource config() {
return this.source;
}
};
}
}
@@ -1,4 +1,76 @@
package cc.carm.lib.configuration.source.mongodb;
public class MongoSource {
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.ReplaceOptions;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
public class MongoSource extends ConfigureSource<SourcedSection, Map<String, Object>, MongoSource> {
protected final @NotNull MongoCollection<Document> collection;
protected final @NotNull String namespace;
protected SourcedSection rootSection;
protected MongoSource(@NotNull ConfigurationHolder<? extends MongoSource> holder, long lastUpdateMillis,
@NotNull MongoCollection<Document> collection, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.collection = collection;
this.namespace = namespace;
try {
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected @NotNull MongoSource self() {
return this;
}
@Override
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull SourcedSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized");
}
public @NotNull String namespace() {
return this.namespace;
}
public @NotNull MongoCollection<Document> collection() {
return this.collection;
}
@Override
public void save() throws Exception {
Map<String, Object> data = this.rootSection.asMap();
if (data.isEmpty()) return; // Skip saving if empty
if (data.containsKey("_id") && data.size() == 1) return; // Skip saving if only contains _id
ReplaceOptions options = new ReplaceOptions().upsert(true);
Document storage = new Document(data).append("_id", this.namespace);
this.collection.replaceOne(new Document("_id", this.namespace), storage, options);
}
@Override
protected void onReload() throws Exception {
Document storage = this.collection.find(new Document("_id", this.namespace)).first();
if (storage == null) storage = new Document();
else storage.remove("_id"); // Remove _id
this.rootSection = SourcedSection.root(this, storage);
}
}
@@ -0,0 +1,12 @@
package config;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
interface MongoConfig extends Configuration {
ConfiguredValue<String> HOST = ConfiguredValue.of("127.0.0.1");
ConfiguredValue<Integer> PORT = ConfiguredValue.of(27017);
ConfiguredValue<String> USERNAME = ConfiguredValue.of("minecraft");
ConfiguredValue<String> PASSWORD = ConfiguredValue.of("minecraft");
ConfiguredValue<String> DATABASE = ConfiguredValue.of("minecraft");
}
@@ -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)
`type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...)
`value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式)
`inline_comment` TEXT, # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT, # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT, # 配置项的描述,本质是部注释
`version` INT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`inline_comment` TEXT comment 'usage', # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT comment 'description', # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT comment 'example', # 配置项的参考,本质是部注释
`version` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`namespace`, `path`)
+20 -5
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -15,7 +15,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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>
<artifactId>easyconfiguration-sql</artifactId>
@@ -77,21 +78,35 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
<version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<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>
</dependency>
@@ -0,0 +1,133 @@
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 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.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 @NotNull Supplier<SQLManager> managerSupplier;
protected Supplier<Gson> gsonSupplier = () -> SQLSource.DEFAULT_GSON;
protected HashMap<Integer, SQLValueResolver<?>> resolvers = new HashMap<>(SQLValueResolver.STANDARD_RESOLVERS);
protected String tableName = "configs";
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 tableName(@NotNull String tableName) {
this.tableName = tableName;
return self();
}
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;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface SQLOptions {
/**
* Whether to purge the configuration's in-database data when saving.
*/
ConfigurationOption<Boolean> PURGE = ConfigurationOption.of( true);
}
@@ -1,53 +1,68 @@
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.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.SQLQuery;
import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.enums.IndexType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.ResultSet;
import java.time.LocalDateTime;
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();
.serializeNulls().disableHtmlEscaping().create();
protected final @NotNull Gson gson;
protected final @NotNull SQLManager sqlManager;
protected final @NotNull String namespace;
protected final @NotNull SQLTable table;
protected final @NotNull Set<String> updated = new HashSet<>();
protected MemorySection rootSection;
protected final @NotNull Map<Integer, SQLValueResolver<?>> resolvers;
protected SourcedSection rootSection;
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 String tableName, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.gson = gson;
this.sqlManager = sqlManager;
this.resolvers = resolvers;
this.namespace = namespace;
this.table = SQLTable.of(tableName, builder -> {
builder.addColumn("namespace", "VARCHAR(32) NOT NULL");
builder.addColumn("path", "VARCHAR(96) NOT NULL");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
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");
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(),
@@ -55,82 +70,134 @@ public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSour
);
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
});
try {
this.table.create(this.sqlManager);
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected SQLSource self() {
protected @NotNull SQLSource self() {
return this;
}
public @NotNull Gson gson() {
return gson;
}
@Override
public @NotNull Map<?, ?> original() {
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull MemorySection section() {
public @NotNull SourcedSection section() {
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
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
List<Object[]> values = new ArrayList<>();
SourcedSection section = section();
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) {
Object value = get(path);
// if (value instanceof SQLConfigWrapper) {
// value = getSourceMap(((SQLConfigWrapper) value).getSource());
// }
//
// SQLValueResolver<?> type = SQLValueTypes.get(value.getClass());
// if (type != null) {
// values.add(new Object[]{
// this.namespace, path, date,
// type.getID(), type.serializeObject(value),
// getComments().getInlineComment(path),
// GSON.toJson(getComments().getHeaderComment(path))
// });
// }
if (value instanceof SourcedSection) {
value = ((SourcedSection) value).asMap();
} else if (value instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) value) {
if (obj instanceof SourcedSection) {
list.add(((SourcedSection) obj).asMap());
} else {
list.add(obj);
}
}
value = list;
}
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()
.setColumnNames("namespace", "path", "update_time", "type", "value", "inline_comment", "header_comments")
.setAllParams(values)
.execute();
.setColumnNames(
"namespace", "path", "update_time", "version", "type", "value",
"inline_comment", "header_comments", "footer_comments"
).setAllParams(dataValues).execute();
}
@Override
protected void onReload() throws Exception {
LinkedHashMap<String, Object> values = new LinkedHashMap<>();
// try (SQLQuery query = this.table.createQuery()
// .addCondition("namespace", namespace)
// .build().execute()) {
// ResultSet rs = query.getResultSet();
// while (rs.next()) {
// String path = rs.getString("path");
// int type = rs.getInt("type");
// try {
// SQLValueResolver<?> resolver = SQLValueTypes.get(type);
// if (resolver == null) throw new IllegalStateException("No resolver for type #" + type);
// String value = rs.getString("value");
// values.put(path, resolver.resolve(value));
//
// loadInlineComment(path, rs.getString("inline_comment"));
// loadHeaderComment(path, rs.getString("header_comments"));
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// }
//
// this.rootConfiguration = new SQLConfigWrapper(this, values);
Map<String, Object> loaded = new LinkedHashMap<>();
try (SQLQuery query = this.table.createQuery()
.addCondition("namespace", namespace)
.build().execute()) {
ResultSet rs = query.getResultSet();
while (rs.next()) {
String path = rs.getString("path");
if (path == null) continue; // Path should be not null
int ver = rs.getInt("version");
try {
loaded.put(path, parse(rs.getInt("type"), rs.getString("value")));
if (ver != 0) holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
} catch (Exception e) {
e.printStackTrace();
}
}
}
this.rootSection = SourcedSection.root(this, loaded);
}
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;
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.api.SQLManager;
import cc.carm.lib.easysql.beecp.BeeDataSourceConfig;
import org.junit.Test;
import java.io.File;
public class SQLConfigTest {
boolean local = false;
@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();
config.setDriverClassName("org.h2.Driver");
config.setJdbcUrl("jdbc:h2:file:target/test;DB_CLOSE_DELAY=-1;MODE=MySQL;");
config.setDriverClassName(DatabaseConfiguration.DRIVER_NAME.resolve());
config.setJdbcUrl(DatabaseConfiguration.buildJDBC());
config.setUsername(DatabaseConfiguration.USERNAME.resolve());
config.setPassword(DatabaseConfiguration.PASSWORD.resolve());
SQLManager manager = EasySQL.createManager(config);
manager.setDebugMode(true);
// SQLConfigProvider provider = EasyConfiguration.from(manager, "conf_test", "TESTING");
//
// ConfigurationTest.testDemo(provider);
// ConfigurationTest.testInner(provider);
//
// ConfigurationTest.save(provider);
//
// EasySQL.shutdownManager(manager);
ConfigurationHolder<?> holder = SQLConfigFactory.from(manager)
.tableName("test_configs")
.namespace("testing")
.build();
ConfigurationTest.testDemo(holder);
ConfigurationTest.testInner(holder);
ConfigurationTest.save(holder);
EasySQL.shutdownManager(manager);
}
}
-71
View File
@@ -62,74 +62,3 @@ dependencies {
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>
<artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>4.0.0</version>
<version>4.0.10</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
@@ -14,7 +14,7 @@
<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>
<deps.yamlcommentwriter.version>1.1.0</deps.yamlcommentwriter.version>
<deps.yamlcommentwriter.version>1.2.1</deps.yamlcommentwriter.version>
</properties>
<artifactId>easyconfiguration-yaml</artifactId>
@@ -29,13 +29,6 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>yamlcommentwriter</artifactId>
<version>1.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId>
@@ -1,5 +1,6 @@
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.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigFactory;
@@ -125,7 +126,7 @@ public class YAMLConfigFactory extends FileConfigFactory<YAMLSource, Configurati
File configFile = this.file;
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) {
final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath);
@@ -1,12 +1,11 @@
package cc.carm.lib.configuration.source.yaml;
import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.option.CommentableOptions;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableOptions;
import cc.carm.lib.configuration.source.ConfigurationHolder;
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.MemorySection;
import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.yamlcommentupdater.CommentedSection;
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
import org.jetbrains.annotations.NotNull;
@@ -25,15 +24,16 @@ import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
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 YamlRepresenter yamlRepresenter;
protected final @NotNull Yaml yaml;
protected @Nullable MemorySection rootSection;
protected @Nullable SourcedSection rootSection;
protected YAMLSource(@NotNull ConfigurationHolder<? extends YAMLSource> holder,
@NotNull File file, @Nullable String resourcePath) {
@@ -55,8 +55,8 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
}
@Override
protected YAMLSource self() {
return null;
protected @NotNull YAMLSource self() {
return this;
}
@Override
@@ -65,15 +65,10 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
}
@Override
public @NotNull MemorySection section() {
public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
}
@Override
public char separator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public @NotNull LoaderOptions loaderOptions() {
return holder().options().get(YAMLOptions.LOADER);
}
@@ -114,9 +109,9 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override
public void save() throws Exception {
CommentedYAMLWriter writer = new CommentedYAMLWriter(
String.valueOf(this.separator()),
String.valueOf(this.pathSeparator()),
dumperOptions().getIndent(),
holder.options().get(CommentableOptions.COMMENT_EMPTY_VALUE)
holder.option(CommentableOptions.COMMENT_EMPTY_VALUE)
);
try {
fileWriter(w -> w.write(writer.saveToString(this)));
@@ -135,18 +130,18 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
return this.saveToString(section());
}
public @NotNull MemorySection loadFromString(@NotNull String data) throws Exception {
public @NotNull SourcedSection loadFromString(@NotNull String data) throws Exception {
MappingNode mappingNode;
try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) {
Node rawNode = this.yaml.compose(reader);
mappingNode = (MappingNode) rawNode;
}
if (mappingNode == null) return MemorySection.root(this);
if (mappingNode == null) return SourcedSection.root(this);
Map<String, Object> map = new LinkedHashMap<>();
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) {
@@ -175,7 +170,7 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
public String serializeValue(@NotNull String key, @NotNull Object value) {
Map<String, Object> map = new LinkedHashMap<>();
map.put(key, value);
return saveToString(MemorySection.root(this, map));
return saveToString(SourcedSection.root(this, map));
}
@Override
@@ -193,46 +188,17 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override
public @Nullable String getInlineComment(@NotNull String key) {
String comment = getInlineComment(key, null);
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;
return Commentable.getInlineComment(holder(), key);
}
@Override
public @Nullable List<String> getHeaderComments(@Nullable String key) {
return holder().metadata(key).get(CommentableMeta.HEADER);
return Commentable.getHeaderComments(holder(), key);
}
@Override
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 {
@@ -2,6 +2,7 @@ package sample;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.value.standard.ConfiguredList;
@@ -19,6 +20,8 @@ public interface SampleConfig extends Configuration {
@HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString)
.defaults(
@@ -11,7 +11,7 @@ public class SampleTest {
// 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.indent(2) // Optional: Set the indentation of the configuration file.
.build();
// 2. Initialize the configuration classes or instances.
@@ -1,9 +1,11 @@
package yaml.test;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import cc.carm.lib.configuration.source.yaml.YAMLSource;
import org.junit.Test;
import java.util.List;
@@ -14,13 +16,12 @@ public class YamlTests {
@Test
public void test() {
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/tests.yml")
ConfigurationHolder<YAMLSource> holder = YAMLConfigFactory.from("target/tests.yml")
.resourcePath("configs/sample.yml").build();
ConfigurationTest.testDemo(holder);
ConfigurationTest.testInner(holder);
Map<String, List<String>> headers = holder.extractMetadata(CommentableMeta.HEADER);
System.out.println("Header comments: ");
headers.forEach((k, v) -> {