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

Compare commits

..

30 Commits

Author SHA1 Message Date
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
52 changed files with 1934 additions and 392 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
run: | run: |
rm -rf docs rm -rf docs
mkdir -vp docs mkdir -vp docs
cp -vrf core/target/apidocs/* docs/ cp -vrf core/target/reports/apidocs/* docs/
cp -vrf .doc/JAVADOC-README.md docs/README.md cp -vrf .doc/JAVADOC-README.md docs/README.md
- name: "Generate the Javadoc sitemap" - name: "Generate the Javadoc sitemap"
+1 -1
View File
@@ -1,3 +1,3 @@
/.idea/ .idea/
**/target/ **/target/
**.iml **.iml
+38 -14
View File
@@ -49,41 +49,58 @@ Check out all code demonstrations [HERE](demo/src/main/java/cc/carm/lib/configur
For more examples, see the [Development Guide](.doc/README.md). For more examples, see the [Development Guide](.doc/README.md).
```java ```java
@HeaderComment("Configurations for sample") @ConfigPath(root = true)
interface SampleConfig extends Configuration { @HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // Inline comment @InlineComment("Enabled?") // Inline comment
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
).build(); ).build();
@ConfigPath("info") // Custom path
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // Header comment @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // Custom path @ConfigPath("how-old-are-you") // Custom path
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
} }
} }
``` ```
```java ```java
public class Sample { public class Sample {
public static void main(String[] args) { public static void main(String[] args) {
// 1. Make a configuration provider from a file. // 1. Make a configuration provider from a file.
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml"); ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. Initialize the configuration classes or instances. // 2. Initialize the configuration classes or instances.
provider.initialize(SampleConfig.class); holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration! // 3. Enjoy using the configuration!
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false); SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !"); System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. Changes not save so enable value will still be true in the next run.
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
} }
} }
@@ -94,14 +111,20 @@ public class Sample {
enabled: true #Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### Dependencies ### Dependencies
@@ -159,10 +182,10 @@ info:
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. --> <!-- JSON file-based implementation, compatible with all Java environments. -->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -205,8 +228,8 @@ dependencies {
// YAML file-based implementation, compatible with all Java environments. // YAML file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
// JSON file-based implementation, compatible with all Java environments. Note: JSON does not support file comments. // JSON file-based implementation, compatible with all Java environments.
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
} }
``` ```
@@ -220,7 +243,8 @@ dependencies {
EasyConfiguration for MineCraft! EasyConfiguration for MineCraft!
Easily manage configurations on MineCraft-related server platforms. Easily manage configurations on MineCraft-related server platforms.
Currently supports BungeeCord, Bukkit (Spigot) servers, with more platforms to be supported soon. Currently, it supports BungeeCord, Velocity, Bukkit (Spigot) servers,
with more platforms to be supported soon.
## Support and Donation ## Support and Donation
+36 -15
View File
@@ -43,15 +43,22 @@ README LANGUAGES [ [English](README.md) | [**中文**](README_CN.md) ]
您可以 [点击这里](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?") // 行内注释 ```java
@ConfigPath(root = true)
@HeaderComments("Configurations for sample")
public interface SampleConfig extends Configuration {
@InlineComment("Enabled?") // 行后注释
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
@HeaderComments("Server configurations") // 头部注释
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
@@ -59,26 +66,34 @@ interface SampleConfig extends Configuration {
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // 头部注释 @HeaderComments("Configure your name!") // Header comment
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker"); ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
@ConfigPath("year") // 自定义配置路径,若不定义,则按照默认规则生成 @ConfigPath("how-old-are-you") // 自定义路径
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24); ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
} }
} }
``` ```
```java ```java
public class Sample { public class Sample {
public static void main(String[] args) { public static void main(String[] args) {
// 1. 生成一个 “Provider” 用于给配置类提供源配置的文件。 // 1. 生成一个 “holder” 用于给配置类提供源配置的文件。
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml"); ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
// 2. 通过 “Provider” 初始化配置类或配置实例。 .resourcePath("configs/sample.yml")
provider.initialize(SampleConfig.class); .indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. 通过 “holder” 初始化配置类或配置实例。
holder.initialize(SampleConfig.class);
// 3. 现在可以享受快捷方便的配置文件使用方式了~ // 3. 现在可以享受快捷方便的配置文件使用方式了~
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
SampleConfig.ENABLED.set(false); SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !"); System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
// p.s. 在本示例里的更改未保存,因此启用值在下次运行中仍将为 true。
} }
} }
``` ```
@@ -88,14 +103,20 @@ public class Sample {
enabled: true #Enabled? enabled: true #Enabled?
# Server configurations
port:
# [ UUID >-----------------------------------
# A lot of UUIDs
uuids: uuids:
- 00000000-0000-0000-0000-000000000000 - 00000000-0000-0000-0000-000000000000
- 00000000-0000-0000-0000-000000000001 - 00000000-0000-0000-0000-000000000001
# [ UUID >-----------------------------------
info: info:
# Configure your name! # Configure your name!
name: Joker name: Joker
year: 24 how-old-are-you: 24
``` ```
### 依赖方式 ### 依赖方式
@@ -164,7 +185,7 @@ info:
<!--需要注意的是,JSON不支持文件注释。--> <!--需要注意的是,JSON不支持文件注释。-->
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-json</artifactId> <artifactId>easyconfiguration-gson</artifactId>
<version>[LATEST RELEASE]</version> <version>[LATEST RELEASE]</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -225,7 +246,7 @@ dependencies {
//基于JSON文件的实现版本,可用于全部Java环境。 //基于JSON文件的实现版本,可用于全部Java环境。
//需要注意的是,JSON不支持文件注释。 //需要注意的是,JSON不支持文件注释。
api "cc.carm.lib:easyconfiguration-json:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-gson:[LATEST RELEASE]"
api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-hocon:[LATEST RELEASE]"
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.4</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -3,8 +3,8 @@ package cc.carm.lib.configuration.builder.impl;
import cc.carm.lib.configuration.adapter.ValueAdapter; import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType; import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.CommonConfigBuilder; import cc.carm.lib.configuration.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.function.DataConsumer;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.function.ValueConsumer;
import cc.carm.lib.configuration.function.ValueHandler; import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigureSection; import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
@@ -34,28 +34,30 @@ public abstract class AbstractSectionBuilder<
this.serializer = serializer; this.serializer = serializer;
} }
public @NotNull SELF parse(DataFunction<ConfigureSection, PARAM> valueParser) { public @NotNull SELF parse(@NotNull DataFunction<ConfigureSection, PARAM> valueParser) {
return parse((p, section) -> valueParser.handle(section)); return parse((p, section) -> valueParser.handle(section));
} }
public @NotNull SELF parse(ValueHandler<ConfigureSection, PARAM> valueParser) { public @NotNull SELF parse(@NotNull ValueHandler<ConfigureSection, PARAM> valueParser) {
this.parser = valueParser; this.parser = valueParser;
return self(); return self();
} }
public @NotNull SELF serialize(DataFunction<PARAM, ? extends Map<String, Object>> serializer) { public @NotNull SELF serialize(@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
public @NotNull SELF serialize(ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
this.serializer = serializer; this.serializer = serializer;
return self(); return self();
} }
public @NotNull SELF serialize(DataConsumer<Map<String, Object>> serializer) { public @NotNull SELF serialize(@NotNull DataFunction<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> { return serialize((p, value) -> {
return serializer.handle(value);
});
}
public @NotNull SELF serialize(@NotNull ValueConsumer<Map<String, Object>, PARAM> serializer) {
return serialize((h, value) -> {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
serializer.accept(map); serializer.accept(h, map, value);
return map; return map;
}); });
} }
@@ -29,7 +29,7 @@ public abstract class AbstractSourceBuilder<
this.valueSerializer = serializer; this.valueSerializer = serializer;
} }
public @NotNull SELF parse(DataFunction<SOURCE, PARAM> parser) { public @NotNull SELF parse(@NotNull DataFunction<SOURCE, PARAM> parser) {
return parse((p, source) -> parser.handle(source)); return parse((p, source) -> parser.handle(source));
} }
@@ -0,0 +1,21 @@
package cc.carm.lib.configuration.function;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface ValueConsumer<U, T> {
void accept(@NotNull ConfigurationHolder<?> holder, @NotNull U unit, @NotNull T data) throws Exception;
default ValueConsumer<U, T> andThen(ValueConsumer<? super U, ? super T> after) {
return (holder, unit, data) -> {
accept(holder, unit, data);
after.accept(holder, unit, data);
};
}
}
@@ -20,6 +20,13 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* ConfigurationFactory, used to create configuration holder.
*
* @param <SOURCE> The {@link ConfigureSource} type
* @param <HOLDER> The {@link ConfigurationHolder} type.
* @param <SELF> Self builder, for further implement support.
*/
public abstract class ConfigurationFactory< public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>, SOURCE extends ConfigureSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>, HOLDER extends ConfigurationHolder<SOURCE>,
@@ -139,18 +146,39 @@ public abstract class ConfigurationFactory<
return self(); return self();
} }
/**
* Supply the base path generator for this configuration holder
*
* @param generator {@link PathGenerator}
* @return this
*/
public SELF pathGenerator(PathGenerator generator) { public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> { return initializer(loader -> {
loader.pathGenerator(generator); loader.pathGenerator(generator);
}); });
} }
/**
* Register a new annotation for metadata to the configuration loader
*
* @param annotation The {@link Annotation}
* @param metadata The {@link ConfigurationMetadata} type
* @param extractor The {@link Function} to extract the metadata from the annotation
* @param <M> The metadata type
* @param <A> The annotation type
* @return this
*/
public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation, public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<M> metadata, @NotNull ConfigurationMetadata<M> metadata,
@NotNull Function<A, M> extractor) { @NotNull Function<A, M> extractor) {
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor)); return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
} }
/**
* Build the configuration holder.
*
* @return The configuration holder
*/
public abstract @NotNull HOLDER build(); public abstract @NotNull HOLDER build();
} }
@@ -6,6 +6,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer; import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder; import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ValueManifest; import cc.carm.lib.configuration.value.ValueManifest;
@@ -50,6 +51,10 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options; return options;
} }
public <O> O option(@NotNull ConfigurationOption<O> option) {
return options().get(option);
}
public @NotNull Map<String, ConfigurationMetaHolder> metadata() { public @NotNull Map<String, ConfigurationMetaHolder> metadata() {
return this.metadata; return this.metadata;
} }
@@ -3,6 +3,7 @@ package cc.carm.lib.configuration.source.loader;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.StandardOptions; import cc.carm.lib.configuration.source.option.StandardOptions;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -107,13 +108,13 @@ public class ConfigurationInitializer {
public void initialize(@NotNull ConfigurationHolder<?> holder, public void initialize(@NotNull ConfigurationHolder<?> holder,
@NotNull Configuration config) throws Exception { @NotNull Configuration config) throws Exception {
initializeInstance(holder, config, null, null); initializeInstance(holder, config, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save(); if (holder.option(StandardOptions.SET_DEFAULTS)) holder.save();
} }
public void initialize(@NotNull ConfigurationHolder<?> holder, public void initialize(@NotNull ConfigurationHolder<?> holder,
@NotNull Class<? extends Configuration> clazz) throws Exception { @NotNull Class<? extends Configuration> clazz) throws Exception {
initializeStaticClass(holder, clazz, null, null); initializeStaticClass(holder, clazz, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save(); if (holder.option(StandardOptions.SET_DEFAULTS)) holder.save();
} }
@@ -148,7 +149,7 @@ public class ConfigurationInitializer {
initializeField(holder, clazz, field, path); initializeField(holder, clazz, field, path);
} }
if (holder.options().get(StandardOptions.LOAD_SUB_CLASSES)) { if (holder.option(StandardOptions.LOAD_SUB_CLASSES)) {
Class<?>[] classes = clazz.getDeclaredClasses(); Class<?>[] classes = clazz.getDeclaredClasses();
for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。 for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
initializeStaticClass(holder, classes[i], path, null); initializeStaticClass(holder, classes[i], path, null);
@@ -168,13 +169,17 @@ public class ConfigurationInitializer {
String path = getFieldPath(holder, parent, field); String path = getFieldPath(holder, parent, field);
if (path == null) return; if (path == null) return;
value.initialize(holder, path); value.initialize(holder, path);
holder.metadata(path).set(StandardMeta.UNIT, true); // Mark the minimal config value unit.
try { try {
this.fieldInitializer.whenInitialize(holder, path, field); this.fieldInitializer.whenInitialize(holder, path, field);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
if (holder.options().get(StandardOptions.SET_DEFAULTS)) { if (holder.option(StandardOptions.SET_DEFAULTS)) {
value.setDefault(); value.setDefault(); // Set default value.
}
if (holder.option(StandardOptions.PRELOAD)) {
value.get(); // Preload the value by calling #get method.
} }
} else if (source instanceof Configuration && object instanceof Configuration) { } else if (source instanceof Configuration && object instanceof Configuration) {
// 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。 // 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。
@@ -9,6 +9,12 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
/**
* Path generator for configuration.
* <p>
* Path generator is a utility class that helps to generate the path of the configuration.
* It can be used to generate the path of the field or class.
*/
public class PathGenerator { public class PathGenerator {
public static PathGenerator of() { public static PathGenerator of() {
@@ -80,7 +86,7 @@ public class PathGenerator {
} }
public static char pathSeparator(ConfigurationHolder<?> holder) { public static char pathSeparator(ConfigurationHolder<?> holder) {
return holder.options().get(StandardOptions.PATH_SEPARATOR); return holder.option(StandardOptions.PATH_SEPARATOR);
} }
/** /**
@@ -0,0 +1,10 @@
package cc.carm.lib.configuration.source.meta;
public interface StandardMeta {
/**
* To mark the {@link cc.carm.lib.configuration.value.ConfigValue} as a minimal unit path.
*/
ConfigurationMetadata<Boolean> UNIT = ConfigurationMetadata.of(false);
}
@@ -6,32 +6,20 @@ import java.util.function.Supplier;
public class ConfigurationOption<V> { public class ConfigurationOption<V> {
@SuppressWarnings("unchecked")
public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull T defaultValue) {
return of((Class<T>) defaultValue.getClass(), defaultValue); return new ConfigurationOption<>(defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Class<T> valueClazz, @NotNull T defaultValue) {
return new ConfigurationOption<>(valueClazz, defaultValue);
} }
public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) { public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) {
return of(defaultValue.get()); return of(defaultValue.get());
} }
private final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue; private @NotNull V defaultValue;
public ConfigurationOption(@NotNull Class<V> valueClazz, @NotNull V defaultValue) { public ConfigurationOption(@NotNull V defaultValue) {
this.valueClazz = valueClazz;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
@NotNull
public Class<V> valueClass() {
return this.valueClazz;
}
public @NotNull V defaults() { public @NotNull V defaults() {
return defaultValue; return defaultValue;
} }
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -12,62 +11,199 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
/**
* Represents a section of a configuration.
*
* @author Carm
* @since 4.0.0
*/
public interface ConfigureSection { public interface ConfigureSection {
@NotNull ConfigureSource<?, ?, ?> source(); /**
* Gets the parent section of this section.
* <p>
* For root sections, this will return null.
*
* @return Parent section, or null if this is a root section.
*/
@Contract(pure = true)
@Nullable ConfigureSection parent(); @Nullable ConfigureSection parent();
default char separator() { /**
return source().holder().options().get(StandardOptions.PATH_SEPARATOR); * Gets a set containing all keys in this section.
} * <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Set of keys contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
default Set<String> getKeys(boolean deep) { default Set<String> getKeys(boolean deep) {
return getValues(deep).keySet(); return getValues(deep).keySet();
} }
/**
* Gets a set containing all values in this section.
* <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigureSection}s (and their children paths).
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether to get a deep list.
* @return Map of data values contained within this Section.
*/
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
Map<String, Object> getValues(boolean deep); Map<String, Object> getValues(boolean deep);
/**
* Sets the value at the given path.
* <p>
* Null values will be kept, if you want to remove a value use {@link #remove(String)}
* Path separator depends on holder's
* {@link cc.carm.lib.configuration.source.option.StandardOptions#PATH_SEPARATOR}
*
* @param path The path to set the value at.
* @param value The value to set.
*/
void set(@NotNull String path, @Nullable Object value); void set(@NotNull String path, @Nullable Object value);
/**
* 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 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.
*/
boolean contains(@NotNull String path); boolean contains(@NotNull String path);
/**
* Predicate the value of given path is specific type.
*
* @param path The path to check.
* @param typeClass The type's class
* @param <T> The type to check.
* @return True if the value is present and is the correct type, false otherwise.
*/
default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) { default <T> boolean isType(@NotNull String path, @NotNull Class<T> typeClass) {
return typeClass.isInstance(get(path)); return typeClass.isInstance(get(path));
} }
/**
* Predicate the value of given path is a {@link List}.
*
* @param path The path to check.
* @return True if the value is present and is a list, false otherwise.
*/
default boolean isList(@NotNull String path) { default boolean isList(@NotNull String path) {
return isType(path, List.class); return isType(path, List.class);
} }
/**
* 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.
*/
@Nullable List<?> getList(@NotNull String path); @Nullable List<?> getList(@NotNull String path);
/**
* Predicate the value of given path is a {@link ConfigureSection}.
*
* @param path The path to check.
* @return True if the value is present and is a section, false otherwise.
*/
default boolean isSection(@NotNull String path) { default boolean isSection(@NotNull String path) {
return isType(path, ConfigureSection.class); return isType(path, ConfigureSection.class);
} }
/**
* Get the value as a {@link ConfigureSection} from the specified path.
*
* @param path The path to get the section.
* @return The section if the path exists and is a section, otherwise null.
*/
@Nullable @Nullable
ConfigureSection getSection(@NotNull String path); ConfigureSection getSection(@NotNull String path);
/**
* 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); @Nullable Object get(@NotNull String path);
default @Nullable <T> T get(@NotNull String path, @NotNull Class<T> clazz) { /**
return get(path, null, clazz); * 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);
} }
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<Object, T> parser) { /**
* Get the value of the path using a parser function,
* if the path does not exist, return NULL.
*
* @param path The path to get the value from.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or null if not found.
*/
default @Nullable <T> T get(@NotNull String path, @NotNull DataFunction<@Nullable Object, T> parser) {
return get(path, null, parser); return get(path, null, parser);
} }
/**
* Get the value of the path for specific type,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaults The default value to return if the path does not exist.
* @param clazz The type class of the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, @NotNull Class<T> clazz) { default @Nullable <T> T get(@NotNull String path, @Nullable T defaults, @NotNull Class<T> clazz) {
return get(path, defaultValue, DataFunction.castObject(clazz)); return get(path, defaults, DataFunction.castObject(clazz));
} }
/**
* Get the value of the path using a parser function,
* if the path does not exist, return the default value.
*
* @param path The path to get the value from.
* @param defaultValue The default value to return if the path does not exist.
* @param parser The function to parse the value
* @param <T> The type of the value
* @return The value at the path, or the default value if not found.
*/
@Contract("_,!null,_->!null") @Contract("_,!null,_->!null")
default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue, default @Nullable <T> T get(@NotNull String path, @Nullable T defaultValue,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
@@ -82,125 +218,290 @@ public interface ConfigureSection {
return defaultValue; return defaultValue;
} }
/**
* Predicate the value of given path is a {@link Boolean}.
*
* @param path The path to check.
* @return True if the value is present and is a boolean, false otherwise.
*/
default boolean isBoolean(@NotNull String path) { default boolean isBoolean(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @return The boolean if the path exists and is a boolean, otherwise false.
*/
default boolean getBoolean(@NotNull String path) { default boolean getBoolean(@NotNull String path) {
return getBoolean(path, false); return getBoolean(path, false);
} }
/**
* Get the value as a {@link Boolean} from the specified path.
*
* @param path The path to get the boolean.
* @param def The default value to return if the path does not exist.
* @return The boolean if the path exists and is a boolean, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) { default @Nullable Boolean getBoolean(@NotNull String path, @Nullable Boolean def) {
return get(path, def, DataFunction.booleanValue()); return get(path, def, DataFunction.booleanValue());
} }
/**
* Predicate the value of given path is a {@link Byte}.
*
* @param path The path to check.
* @return True if the value is present and is a byte, false otherwise.
*/
default @Nullable Boolean isByte(@NotNull String path) { default @Nullable Boolean isByte(@NotNull String path) {
return isType(path, Byte.class); return isType(path, Byte.class);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @return The byte if the path exists and is a byte, otherwise 0.
*/
default @Nullable Byte getByte(@NotNull String path) { default @Nullable Byte getByte(@NotNull String path) {
return getByte(path, (byte) 0); return getByte(path, (byte) 0);
} }
/**
* Get the value as a {@link Byte} from the specified path.
*
* @param path The path to get the byte.
* @param def The default value to return if the path does not exist.
* @return The byte if the path exists and is a byte, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) { default @Nullable Byte getByte(@NotNull String path, @Nullable Byte def) {
return get(path, def, DataFunction.byteValue()); return get(path, def, DataFunction.byteValue());
} }
/**
* Predicate the value of given path is a {@link Short}.
*
* @param path The path to check.
* @return True if the value is present and is a short, false otherwise.
*/
default boolean isShort(@NotNull String path) { default boolean isShort(@NotNull String path) {
return isType(path, Short.class); return isType(path, Short.class);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @return The short if the path exists and is a short, otherwise 0.
*/
default @Nullable Short getShort(@NotNull String path) { default @Nullable Short getShort(@NotNull String path) {
return getShort(path, (short) 0); return getShort(path, (short) 0);
} }
/**
* Get the value as a {@link Short} from the specified path.
*
* @param path The path to get the short.
* @param def The default value to return if the path does not exist.
* @return The short if the path exists and is a short, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Short getShort(@NotNull String path, @Nullable Short def) { default @Nullable Short getShort(@NotNull String path, @Nullable Short def) {
return get(path, def, DataFunction.shortValue()); return get(path, def, DataFunction.shortValue());
} }
/**
* Predicate the value of given path is a {@link Integer}.
*
* @param path The path to check.
* @return True if the value is present and is a int, false otherwise.
*/
default boolean isInt(@NotNull String path) { default boolean isInt(@NotNull String path) {
return isType(path, Integer.class); return isType(path, Integer.class);
} }
/**
* Get the value as a {@link Integer} from the specified path.
*
* @param path The path to get the int.
* @return The int if the path exists and is a int, otherwise 0.
*/
default @Nullable Integer getInt(@NotNull String path) { default @Nullable Integer getInt(@NotNull String path) {
return getInt(path, 0); return getInt(path, 0);
} }
/**
* Get the value as a {@link Integer} from the specified path.
*
* @param path The path to get the int.
* @param def The default value to return if the path does not exist.
* @return The int if the path exists and is a int, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) { default @Nullable Integer getInt(@NotNull String path, @Nullable Integer def) {
return get(path, def, DataFunction.intValue()); return get(path, def, DataFunction.intValue());
} }
/**
* Predicate the value of given path is a {@link Long}.F
*
* @param path The path to check.
* @return True if the value is present and is a long, false otherwise.
*/
default boolean isLong(@NotNull String path) { default boolean isLong(@NotNull String path) {
return isType(path, Long.class); return isType(path, Long.class);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @return The long if the path exists and is a long, otherwise 0.
*/
default @Nullable Long getLong(@NotNull String path) { default @Nullable Long getLong(@NotNull String path) {
return getLong(path, 0L); return getLong(path, 0L);
} }
/**
* Get the value as a {@link Long} from the specified path.
*
* @param path The path to get the long.
* @param def The default value to return if the path does not exist.
* @return The long if the path exists and is a long, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Long getLong(@NotNull String path, @Nullable Long def) { default @Nullable Long getLong(@NotNull String path, @Nullable Long def) {
return get(path, def, DataFunction.longValue()); return get(path, def, DataFunction.longValue());
} }
/**
* Predicate the value of given path is a {@link Float}.
*
* @param path The path to check.
* @return True if the value is present and is a float, false otherwise.
*/
default boolean isFloat(@NotNull String path) { default boolean isFloat(@NotNull String path) {
return isType(path, Float.class); return isType(path, Float.class);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @return The float if the path exists and is a float, otherwise 0.
*/
default @Nullable Float getFloat(@NotNull String path) { default @Nullable Float getFloat(@NotNull String path) {
return getFloat(path, 0.0F); return getFloat(path, 0.0F);
} }
/**
* Get the value as a {@link Float} from the specified path.
*
* @param path The path to get the float.
* @param def The default value to return if the path does not exist.
* @return The float if the path exists and is a float, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) { default @Nullable Float getFloat(@NotNull String path, @Nullable Float def) {
return get(path, def, DataFunction.floatValue()); return get(path, def, DataFunction.floatValue());
} }
/**
* Predicate the value of given path is a {@link Double}.
*
* @param path The path to check.
* @return True if the value is present and is a double, false otherwise.
*/
default boolean isDouble(@NotNull String path) { default boolean isDouble(@NotNull String path) {
return isType(path, Double.class); return isType(path, Double.class);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @return The double if the path exists and is a double, otherwise 0.
*/
default @Nullable Double getDouble(@NotNull String path) { default @Nullable Double getDouble(@NotNull String path) {
return getDouble(path, 0.0D); return getDouble(path, 0.0D);
} }
/**
* Get the value as a {@link Double} from the specified path.
*
* @param path The path to get the double.
* @param def The default value to return if the path does not exist.
* @return The double if the path exists and is a double, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) { default @Nullable Double getDouble(@NotNull String path, @Nullable Double def) {
return get(path, def, DataFunction.doubleValue()); return get(path, def, DataFunction.doubleValue());
} }
/**
* Predicate the value of given path is a {@link Character}.
*
* @param path The path to check.
* @return True if the value is present and is a char, false otherwise.
*/
default boolean isChar(@NotNull String path) { default boolean isChar(@NotNull String path) {
return isType(path, Boolean.class); return isType(path, Boolean.class);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @return The char if the path exists and is a char, otherwise null.
*/
default @Nullable Character getChar(@NotNull String path) { default @Nullable Character getChar(@NotNull String path) {
return getChar(path, null); return getChar(path, null);
} }
/**
* Get the value as a {@link Character} from the specified path.
*
* @param path The path to get the char.
* @param def The default value to return if the path does not exist.
* @return The char if the path exists and is a char, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable Character getChar(@NotNull String path, @Nullable Character def) { default @Nullable Character getChar(@NotNull String path, @Nullable Character def) {
return get(path, def, Character.class); return get(path, def, Character.class);
} }
/**
* Predicate the value of given path is a {@link String}.
*
* @param path The path to check.
* @return True if the value is present and is a string, false otherwise.
*/
default boolean isString(@NotNull String path) { default boolean isString(@NotNull String path) {
return isType(path, String.class); return isType(path, String.class);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @return The string if the path exists and is a string, otherwise null.
*/
default @Nullable String getString(@NotNull String path) { default @Nullable String getString(@NotNull String path) {
return getString(path, null); return getString(path, null);
} }
/**
* Get the value as a {@link String} from the specified path.
*
* @param path The path to get the string.
* @param def The default value to return if the path does not exist.
* @return The string if the path exists and is a string, otherwise the default value.
*/
@Contract("_, !null -> !null") @Contract("_, !null -> !null")
default @Nullable String getString(@NotNull String path, @Nullable String def) { default @Nullable String getString(@NotNull String path, @Nullable String def) {
return get(path, def, String.class); return get(path, def, String.class);
@@ -298,17 +599,41 @@ public interface ConfigureSection {
return getList(path, DataFunction.castObject(Character.class)); return getList(path, DataFunction.castObject(Character.class));
} }
/**
* Get the specific type of collection from the section.
*
* @param path The path to get the collection from
* @param constructor The constructor of the collection
* @param parser The function to parse the values
* @param <T> The type of the values
* @param <C> The type of the collection
* @return The collection of values
*/
default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path, default <T, C extends Collection<T>> @NotNull C getCollection(@NotNull String path,
@NotNull Supplier<C> constructor, @NotNull Supplier<C> constructor,
@NotNull DataFunction<Object, T> parser) { @NotNull DataFunction<Object, T> parser) {
return parseCollection(getList(path), constructor, parser); return parseCollection(getList(path), constructor, parser);
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @return The stream of values
*/
default @NotNull Stream<?> stream(@NotNull String path) { default @NotNull Stream<?> stream(@NotNull String path) {
List<?> values = getList(path); List<?> values = getList(path);
return values == null ? Stream.empty() : values.stream(); return values == null ? Stream.empty() : values.stream();
} }
/**
* Get the specific type of steam from the section.
*
* @param path The path to get the stream from
* @param parser The function to parse the values
* @param <T> The type of the values
* @return The stream of values
*/
default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) { default <T> @NotNull Stream<T> stream(@NotNull String path, @NotNull Function<Object, T> parser) {
return stream(path).map(parser); return stream(path).map(parser);
} }
@@ -1,12 +1,23 @@
package cc.carm.lib.configuration.source.section; package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/**
* 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.
*/
public abstract class ConfigureSource< public abstract class ConfigureSource<
SECTION extends ConfigureSection, ORIGINAL, SECTION extends ConfigureSection, ORIGINAL,
SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>> SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>>
@@ -24,28 +35,46 @@ public abstract class ConfigureSource<
return holder; return holder;
} }
public void reload() throws Exception { public void reload() throws Exception {
onReload(); // 调用重写的Reload方法 onReload(); // 调用重写的Reload方法
this.lastUpdateMillis = System.currentTimeMillis(); this.lastUpdateMillis = System.currentTimeMillis();
} }
protected abstract SELF self(); @Contract(pure = true)
@ApiStatus.Internal
protected abstract @NotNull SELF self();
/** /**
* @return Original configuration object * @return The original configuration object.
*/ */
@Contract(pure = true)
public abstract @NotNull ORIGINAL original(); public abstract @NotNull ORIGINAL original();
/** /**
* @return The root {@link ConfigureSection} * @return The root {@link ConfigureSection}, which represents the entire configuration.
*/ */
public abstract @NotNull SECTION section(); public abstract @NotNull SECTION section();
/**
* Save the whole configuration.
*
* @throws Exception If any error occurs while saving.
*/
public abstract void save() throws Exception; public abstract void save() throws Exception;
/**
* Reload the configuration.
* <br>This used for implementation, for external usage, use {@link #reload()}
*
* @throws Exception If any error occurs while reloading.
*/
@ApiStatus.OverrideOnly
protected abstract void onReload() throws Exception; protected abstract void onReload() throws Exception;
public char pathSeparator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public long getLastUpdateMillis() { public long getLastUpdateMillis() {
return this.lastUpdateMillis; return this.lastUpdateMillis;
} }
@@ -54,14 +83,15 @@ public abstract class ConfigureSource<
return getLastUpdateMillis() > parsedTime; return getLastUpdateMillis() > parsedTime;
} }
/**
* Source also represents the root section, so it has no parent
*
* @return null
*/
@Override @Override
@Contract(pure = true, value = "->null")
public @Nullable ConfigureSection parent() { public @Nullable ConfigureSection parent() {
return null; // Source also represents the root section, so it has no parent return null;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return self();
} }
@Override @Override
@@ -80,8 +110,8 @@ public abstract class ConfigureSource<
} }
@Override @Override
public boolean isList(@NotNull String path) { public void remove(@NotNull String path) {
return section().isList(path); section().remove(path);
} }
@Override @Override
@@ -89,11 +119,6 @@ public abstract class ConfigureSource<
return section().getList(path); return section().getList(path);
} }
@Override
public boolean isSection(@NotNull String path) {
return section().isSection(path);
}
@Override @Override
public @Nullable ConfigureSection getSection(@NotNull String path) { public @Nullable ConfigureSection getSection(@NotNull String path) {
return section().getSection(path); return section().getSection(path);
@@ -113,7 +113,7 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* @param override Whether to overwrite existing configured value * @param override Whether to overwrite existing configured value
*/ */
public void setDefault(boolean override) { public void setDefault(boolean override) {
if (!override && config().contains(path())) return; if (config().contains(path()) && !override) return; // Skip if the value is already set
set(defaults()); // Set the default value set(defaults()); // Set the default value
} }
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.4</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -46,8 +46,6 @@ public interface DemoConfiguration extends Configuration {
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。 @HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。 @InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = "info.*") // 通过注解给配置添加注释。
ConfiguredList<UserRecord> ALLOWLISTS = ConfiguredList.builderOf(UserRecord.class).fromSection() ConfiguredList<UserRecord> ALLOWLISTS = ConfiguredList.builderOf(UserRecord.class).fromSection()
.parse(UserRecord::deserialize).serialize(UserRecord::serialize) .parse(UserRecord::deserialize).serialize(UserRecord::serialize)
.defaults(UserRecord.CARM).build(); .defaults(UserRecord.CARM).build();
@@ -81,6 +79,7 @@ public interface DemoConfiguration extends Configuration {
.build(); .build();
@HeaderComments({"内部类的内部类测试", "通过这种方式,您可以轻易实现多层次的配置文件结构"}) @HeaderComments({"内部类的内部类测试", "通过这种方式,您可以轻易实现多层次的配置文件结构"})
@FooterComments({"-------------"})
public interface That extends Configuration { public interface That extends Configuration {
ConfiguredList<UUID> OPERATORS = ConfiguredList ConfiguredList<UUID> OPERATORS = ConfiguredList
@@ -18,6 +18,8 @@ public class RegistryConfig implements Configuration {
@ConfigPath("test.user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("test.user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@FooterComments({"12313213212"}) @FooterComments({"12313213212"})
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。
public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection() public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection()
.defaults(new UserRecord("Carm", UUID.randomUUID())) .defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section)) .parse((holder, section) -> UserRecord.deserialize(section))
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -33,7 +33,7 @@ public @interface InlineComment {
* If the regex is not empty, the comment will be added to * If the regex is not empty, the comment will be added to
* all sub paths if the regex matches the value. * all sub paths if the regex matches the value.
* If the regex is empty, the comment will be added to the current path. * If the regex is empty, the comment will be added to the current path.
* <p> e.g. for section, set <b>{"^foo", "*", "bar"}</b> will be set like * <p> e.g. for section, set <b>"foo.*.bar"</b> will be set like
* <blockquote><pre> * <blockquote><pre>
* section: * section:
* foo: * foo:
@@ -44,6 +44,9 @@ public @interface InlineComment {
* bar: "foobar" # Comment Contents * bar: "foobar" # Comment Contents
* </pre></blockquote> * </pre></blockquote>
* *
* <p><i>Some implements may not support this feature
* in list values or other complex structures.</i>
*
* @return The path regexes of this comment * @return The path regexes of this comment
*/ */
@NotNull @NotNull
@@ -9,7 +9,12 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface InlineComments { public @interface InlineComments {
/**
* Multiple inline comments support.
*
* @return inline comment contents.
* @see InlineComment
*/
InlineComment[] value(); InlineComment[] value();
} }
@@ -0,0 +1,66 @@
package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
public class Commentable {
private Commentable() {
throw new UnsupportedOperationException();
}
public static void registerMeta(@NotNull ConfigurationInitializer initializer) {
CommentableMeta.register(initializer);
}
public static @Nullable String getInlineComment(@NotNull ConfigurationHolder<?> holder, @NotNull String path) {
String comment = getInlineComment(holder, path, null);
if (comment != null) return comment;
String sep = String.valueOf(holder.option(StandardOptions.PATH_SEPARATOR));
// If the comment is not found, try to get the comment from the parent section
String[] keys = path.split(Pattern.quote(sep));
if (keys.length == 1) return null;
// Try every possible parent key&child key combination
for (int i = 1; i < keys.length; i++) {
String parentKey = String.join(sep, Arrays.copyOfRange(keys, 0, i));
String childKey = String.join(sep, Arrays.copyOfRange(keys, i, keys.length));
comment = getInlineComment(holder, parentKey, childKey);
if (comment != null) return comment;
}
return null;
}
public static @Nullable String getInlineComment(@NotNull ConfigurationHolder<?> holder, @NotNull String path, @Nullable String sectionKey) {
Map<String, String> pathComment = holder.metadata(path).get(CommentableMeta.INLINE);
if (pathComment == null || pathComment.isEmpty()) return null;
if (sectionKey == null) return pathComment.get(null);
for (Map.Entry<String/*regex*/, String/*content*/> entry : pathComment.entrySet()) {
if (entry.getKey() == null) continue;
if (Objects.equals(entry.getKey(), sectionKey)) return entry.getValue();
Pattern pattern = Pattern.compile(entry.getKey().replace(".", "\\.").replace("*", "(.*)"));
if (pattern.matcher(sectionKey).matches()) return entry.getValue();
}
return null;
}
public static @Nullable List<String> getHeaderComments(@NotNull ConfigurationHolder<?> holder, @Nullable String path) {
return holder.metadata(path).get(CommentableMeta.HEADER);
}
public static @Nullable List<String> getFooterComments(@NotNull ConfigurationHolder<?> holder, @Nullable String path) {
return holder.metadata(path).get(CommentableMeta.FOOTER);
}
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.option; package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.source.option.ConfigurationOption; import cc.carm.lib.configuration.source.option.ConfigurationOption;
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -0,0 +1,149 @@
package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public abstract class MapSection<R extends MapSection<R>> implements ConfigureSection {
protected final @NotNull Map<String, Object> data;
protected final @Nullable R parent;
protected MapSection(@Nullable R parent) {
this.parent = parent;
this.data = new LinkedHashMap<>();
}
public void migrate(Map<?, ?> data) {
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 abstract @NotNull R self();
protected abstract @NotNull R createChild(@NotNull Map<?, ?> data);
protected @NotNull R createChild() {
return createChild(new LinkedHashMap<>());
}
public @NotNull Map<String, Object> data() {
return this.data;
}
public @Nullable R parent() {
return this.parent;
}
/**
* Get the path separator for the section.
*
* @return The path separator
*/
public char pathSeparator() {
return '.';
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mappingValues(this, null, true) : data());
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createChild((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 boolean contains(@NotNull String path) {
return get(path) != null;
}
@Override
public @Nullable Object get(@NotNull String path) {
R 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;
}
@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) data().computeIfAbsent(root, k -> createChild());
}
private String childPath(String path) {
int index = path.indexOf(pathSeparator());
return (index == -1) ? path : path.substring(index + 1);
}
/**
* Map the values of the children of the section to the output map.
*
* @param section The section to map the values from
* @param parent The parent path
* @param deep If the mapping should be deep
*/
protected Map<String, Object> mappingValues(@NotNull MapSection<?> section, @Nullable String parent, boolean deep) {
Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator()) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof MapSection<?>) {
output.putAll(mappingValues((MapSection<?>) entry.getValue(), path, true));
}
}
return output;
}
}
@@ -3,148 +3,46 @@ package cc.carm.lib.configuration.source.section;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.LinkedHashMap;
import java.util.Map;
public class MemorySection implements ConfigureSection { public class MemorySection extends MapSection<MemorySection> {
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) { public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) {
return new MemorySection(source, new LinkedHashMap<>(), null); return new MemorySection(source, new LinkedHashMap<>(), null);
} }
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source, public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@Nullable Map<?, ?> data) { @Nullable Map<?, ?> raw) {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null); return new MemorySection(source, raw == null ? new LinkedHashMap<>() : raw, null);
} }
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source; 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, protected MemorySection(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@NotNull Map<?, ?> data, @Nullable MemorySection parent) { @NotNull Map<?, ?> raw, @Nullable MemorySection parent) {
super(parent);
this.source = source; this.source = source;
this.parent = parent; migrate(raw);
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 @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() {
return source;
}
@Override
public char pathSeparator() {
return source().pathSeparator();
}
@Override
public @NotNull MemorySection self() {
return this;
}
@Override
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) { protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this); return new MemorySection(source(), data, this);
} }
protected @NotNull MemorySection createChild() {
return createChild(new LinkedHashMap<>());
}
@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;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mapChildrenValues(this, null, true) : data());
}
@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;
}
} }
+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.4</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> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+3 -2
View File
@@ -15,13 +15,14 @@
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>4.0.0</version> <version>4.0.4</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>features/section</module> <module>features/section</module>
<module>features/file</module> <module>features/file</module>
<module>features/commentable</module> <module>features/commentable</module>
<module>features/versioned</module> <module>features/versioned</module>
<module>features/text</module>
<module>providers/yaml</module> <module>providers/yaml</module>
<module>providers/gson</module> <module>providers/gson</module>
@@ -33,7 +34,7 @@
</modules> </modules>
<name>EasyConfiguration</name> <name>EasyConfiguration</name>
<description>轻松(做)配置,简单便捷的通用配置文件加载、读取与更新工具,可自定义配置格式。</description> <description>A simple, easy-to-use and universal solution for managing configuration files.</description>
<url>https://github.com/CarmJos/EasyConfiguration</url> <url>https://github.com/CarmJos/EasyConfiguration</url>
<developers> <developers>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -48,7 +48,7 @@ public class JSONSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
protected JSONSource self() { protected @NotNull JSONSource self() {
return this; return this;
} }
-71
View File
@@ -62,74 +62,3 @@ dependencies {
api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]" api "cc.carm.lib:easyconfiguration-yaml:[LATEST RELEASE]"
} }
``` ```
## Example file format
```yaml
version: 1.0.0
test-save: false
test-number: 6161926779561752576
test-enum: DAYS
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: '35882'
info:
uuid: 554b79f1-7c39-4960-82d1-5514c9734417
uuid-value: 9a86663e-2fc7-4851-a423-c7e5d8e94a47 # This is an inline comment
sub:
that:
operators: []
# [ID - UUID]对照表
# 用于测试Map类型的解析与序列化保存
users:
'1': 1c055bdd-c9d1-4931-8270-3d162247f38a
'2': 934e2b05-2417-424e-80fd-fe58c6725837
'3': 442949a2-8345-4210-a87b-593d7168980e
'4': 5c015453-4b5b-42e3-ad87-b9498f2dfeab
'5': 8f9640e7-0fbd-4f73-b737-f0b707215e71
# Inner Test
inner:
inner-value: 51.223503560658166
class-value: 1.0
test:
# Section类型数据测试
user: # Section数据也支持InlineComment注释
name: Carm
info:
uuid: 3d1ef2a0-a38b-44f3-b15f-8e3b22cb8cc6
# 以下内容用于测试序列化
model-test:
some-model:
==: SomeModel
num: 855
name: 4f6b7
any-model:
==: AnyModel
name: 63d05
state: false
models:
- name: 481f3
state: true
- name: fcf3e
state: false
- name: '14e50'
state: false
model-map:
a:
name: 1fb9b
state: false
b:
name: 5486f
state: false
```
+3 -3
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.4</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -14,7 +14,7 @@
<maven.compiler.target>${project.jdk.version}</maven.compiler.target> <maven.compiler.target>${project.jdk.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.yamlcommentwriter.version>1.1.0</deps.yamlcommentwriter.version> <deps.yamlcommentwriter.version>1.2.0</deps.yamlcommentwriter.version>
</properties> </properties>
<artifactId>easyconfiguration-yaml</artifactId> <artifactId>easyconfiguration-yaml</artifactId>
@@ -32,7 +32,7 @@
<dependency> <dependency>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>yamlcommentwriter</artifactId> <artifactId>yamlcommentwriter</artifactId>
<version>1.1.0</version> <version>1.2.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -1,5 +1,6 @@
package cc.carm.lib.configuration.source.yaml; package cc.carm.lib.configuration.source.yaml;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.commentable.CommentableMeta; import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigFactory; import cc.carm.lib.configuration.source.file.FileConfigFactory;
@@ -125,7 +126,7 @@ public class YAMLConfigFactory extends FileConfigFactory<YAMLSource, Configurati
File configFile = this.file; File configFile = this.file;
String sourcePath = this.resourcePath; String sourcePath = this.resourcePath;
CommentableMeta.register(this.initializer); // Register commentable meta types Commentable.registerMeta(this.initializer); // Register commentable meta types
return new ConfigurationHolder<YAMLSource>(this.adapters, this.options, this.metadata, this.initializer) { return new ConfigurationHolder<YAMLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath); final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath);
@@ -1,10 +1,9 @@
package cc.carm.lib.configuration.source.yaml; package cc.carm.lib.configuration.source.yaml;
import cc.carm.lib.configuration.commentable.CommentableMeta; import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.option.CommentableOptions; import cc.carm.lib.configuration.commentable.CommentableOptions;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.option.StandardOptions;
import cc.carm.lib.configuration.source.section.ConfigureSection; import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.MemorySection;
import cc.carm.lib.yamlcommentupdater.CommentedSection; import cc.carm.lib.yamlcommentupdater.CommentedSection;
@@ -25,9 +24,10 @@ import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.regex.Pattern;
public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Object>, YAMLSource> implements CommentedSection { public class YAMLSource
extends FileConfigSource<MemorySection, Map<String, Object>, YAMLSource>
implements CommentedSection {
protected final @NotNull YamlConstructor yamlConstructor; protected final @NotNull YamlConstructor yamlConstructor;
protected final @NotNull YamlRepresenter yamlRepresenter; protected final @NotNull YamlRepresenter yamlRepresenter;
@@ -55,7 +55,7 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
} }
@Override @Override
protected YAMLSource self() { protected @NotNull YAMLSource self() {
return null; return null;
} }
@@ -69,11 +69,6 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
} }
@Override
public char separator() {
return holder().options().get(StandardOptions.PATH_SEPARATOR);
}
public @NotNull LoaderOptions loaderOptions() { public @NotNull LoaderOptions loaderOptions() {
return holder().options().get(YAMLOptions.LOADER); return holder().options().get(YAMLOptions.LOADER);
} }
@@ -114,9 +109,9 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
public void save() throws Exception { public void save() throws Exception {
CommentedYAMLWriter writer = new CommentedYAMLWriter( CommentedYAMLWriter writer = new CommentedYAMLWriter(
String.valueOf(this.separator()), String.valueOf(this.pathSeparator()),
dumperOptions().getIndent(), dumperOptions().getIndent(),
holder.options().get(CommentableOptions.COMMENT_EMPTY_VALUE) holder.option(CommentableOptions.COMMENT_EMPTY_VALUE)
); );
try { try {
fileWriter(w -> w.write(writer.saveToString(this))); fileWriter(w -> w.write(writer.saveToString(this)));
@@ -193,46 +188,17 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<String, Obje
@Override @Override
public @Nullable String getInlineComment(@NotNull String key) { public @Nullable String getInlineComment(@NotNull String key) {
String comment = getInlineComment(key, null); return Commentable.getInlineComment(holder(), key);
if (comment != null) return comment;
String sep = String.valueOf(separator());
// If the comment is not found, try to get the comment from the parent section
String[] keys = key.split(sep);
if (keys.length == 1) return null;
// Try every possible parent key&child key combination
for (int i = 1; i < keys.length; i++) {
String parentKey = String.join(sep, Arrays.copyOfRange(keys, 0, i));
String childKey = String.join(sep, Arrays.copyOfRange(keys, i, keys.length));
comment = getInlineComment(childKey, parentKey);
if (comment != null) return comment;
}
return null;
}
public @Nullable String getInlineComment(@NotNull String key, @Nullable String sectionKey) {
Map<String, String> pathComment = holder().metadata(key).get(CommentableMeta.INLINE);
if (pathComment == null || pathComment.isEmpty()) return null;
if (sectionKey == null) return pathComment.get(null);
for (Map.Entry<String, String> entry : pathComment.entrySet()) {
if (entry.getKey().equals(sectionKey)) return entry.getValue();
Pattern pattern = Pattern.compile(entry.getKey().replace(".", "\\.").replace("*", ".*"));
if (pattern.matcher(sectionKey).matches()) return entry.getValue();
}
return null;
} }
@Override @Override
public @Nullable List<String> getHeaderComments(@Nullable String key) { public @Nullable List<String> getHeaderComments(@Nullable String key) {
return holder().metadata(key).get(CommentableMeta.HEADER); return Commentable.getHeaderComments(holder(), key);
} }
@Override @Override
public @Nullable List<String> getFooterComments(@Nullable String key) { public @Nullable List<String> getFooterComments(@Nullable String key) {
return holder().metadata(key).get(CommentableMeta.FOOTER); return Commentable.getFooterComments(holder(), key);
} }
public static class YamlRepresenter extends Representer { public static class YamlRepresenter extends Representer {
@@ -2,6 +2,7 @@ package sample;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments; import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment; import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.value.standard.ConfiguredList; import cc.carm.lib.configuration.value.standard.ConfiguredList;
@@ -19,6 +20,8 @@ public interface SampleConfig extends Configuration {
@HeaderComments("Server configurations") // Header comment @HeaderComments("Server configurations") // Header comment
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class); ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
@HeaderComments({"[ UUID >-----------------------------------", "A lot of UUIDs"})
@FooterComments("[ UUID >-----------------------------------")
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parse(UUID::fromString).serialize(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
@@ -11,7 +11,7 @@ public class SampleTest {
// 1. Make a configuration provider from a file. // 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml") ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml") .resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file. .indent(2) // Optional: Set the indentation of the configuration file.
.build(); .build();
// 2. Initialize the configuration classes or instances. // 2. Initialize the configuration classes or instances.
@@ -4,17 +4,19 @@ import cc.carm.lib.configuration.commentable.CommentableMeta;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest; import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory; import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import cc.carm.lib.configuration.source.yaml.YAMLSource;
import org.junit.Test; import org.junit.Test;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
public class YamlTests { public class YamlTests {
@Test @Test
public void test() { public void test() {
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/tests.yml") ConfigurationHolder<YAMLSource> holder = YAMLConfigFactory.from("target/tests.yml")
.resourcePath("configs/sample.yml").build(); .resourcePath("configs/sample.yml").build();
ConfigurationTest.testDemo(holder); ConfigurationTest.testDemo(holder);